网站首页 > 技术文章 正文
对于一个云开发平台来说,一个好的 Web IDE 能很大程度地提高用户的编码体验,而一个 Web IDE 的一个重要组成部分就是代码编辑器。
目前有着多款 web 上的代码编辑器可供选择,比如 Ace、CodeMirror、Monaco,这三款编辑器的比较在这篇文章中有着详细的介绍,在此就不作过多赘述。这篇文章我们选择 Monaco Editor 来对 LSP 进行集成,从而在理论上能够支持所有的编程语言。
什么是 LSP
LSP(Language Server Protocol),也就是语言服务协议,更具体更通俗地说就是定义了在代码编辑器和语言服务器之间的一套规范,从而让原本
m 个编辑器与 n 个编程语言之间的对应关系
变为
m 个编辑器与 LSP 的关系和 n 个编程语言与 LSP 之间的关系,
从而将开发的复杂度由 m*n 降到了 m+n。
除了对编辑器开发者和编程语言开发者友好,对我们这种尝试让一个编辑器支持多种语言的开发者也更是友好,有 vscode 这样的编辑器珠玉在前,便能轻松地根据 vscode 的设计思路实现我们的需求。
预览
在这篇文章中,我们会开发一个最小最轻量的编辑器 Demo 作为演示,架构非常简单,就是前端创建一个 Monaco Editor,后端创建一个语言服务器,二者之间通过 vscode-ws-jsonrpc 和 WebSocket 服务进行传输,实际实现的 Web 端 Python 编辑器如下:
Server 端开发
在 Web 端能接入语言服务前,我们得先在服务端运行一个语言服务,https://langserver.org/ 这个网站收录了许多语言服务的实现,
这里我们选择微软官方维护的 pyright 提供语言服务
首先创建 Express 服务器,配置静态文件服务,使用 fileURLToPath 和 dirname 来获取当前文件的路径,并将服务设置在 30000 端口
const app = express();
const __filename = fileURLToPath(import.meta.url);
const dir = dirname(__filename);
app.use(express.static(dir));
const server = app.listen(30000);
然后我们需要创建一个 WebSocket Server,注意这里的 noServer 参数,如果没有指定 noServer,那么 WebSocketServer 会自动创建一个 http server 来处理浏览器的 HTTP 请求到 WebSocket 请求的 upgrade。
const wss = new WebSocketServer({
noServer: true,
});
而这里我们需要创建自己的 HTTP 服务器,并手动处理浏览器的 upgrade 请求。下面代码便是如何监听 upgrade 事件并进行处理。
server.on('upgrade',()=>{});
在处理函数中,按照下面的代码将 WebSocket 使用到 jsonrpc 协议中,并启动语言服务器让二者相连。
先构建语言服务器的路径,找到 pyright 包所在的位置。
const baseDir = resolve(getLocalDirectory(import.meta.url));
const relativeDir = '../../../node_modules/pyright/dist/pyright-langserver.js';
const ls = resolve(baseDir, relativeDir);
再创建语言服务器的连接 和 创建 WebSocket 的数据连接
const serverConnection = createServerProcess(serverName, 'node', [ls, '--stdio']);
const reader = new WebSocketMessageReader(socket);
const writer = new WebSocketMessageWriter(socket);
const socketConnection = createConnection(reader, writer, () => socket.dispose());
最后用 forward 函数将消息从 soketConnection 转发到 serverConnection,如下:
forward(socketConnection, serverConnection, message => {
if (Message.isRequest(message)) {
console.log(`Received:`);
console.log(message);
}
if (Message.isResponse(message)) {
console.log(`Sent:`);
console.log(message);
}
return message;
});
于是我们将语言服务器跑起来,并在文章后面阶段会写的前端编辑器中随便输入一点东西,可以看到终端里输出了 message,
这样我们就使用 pyright 完成了语言服务器的开发。
Web 端开发
接下来我们开发前端的内容,还是在上面的那个网站中,
可以看到 Monaco Editor 也是有支持 LSP 的方案的,所以我们使用 TypeFox 开发的 monaco-languageclient 对monaco 进行集成 lsp 的开发。
首先使用 monaco-languageclient 的 initServices 函数初始化一些服务,其中最重要的就是下面四个配置,定义了 Monaco 的语言服务与主题显示。
await initServices({
enableModelService: true,
enableThemeService: true,
enableTextmateService: true,
enableLanguagesService: true,
})
然后创建能够与语言服务器相连的 WebSocket 连接。
createWebSocket("ws://localhost:30000/pyright");
而创建这个连接也需要用到 monaco-languageclient。
const createWebSocket = (url: string): WebSocket => {
const webSocket = new WebSocket(url);
webSocket.onopen = async () => {
const socket = toSocket(webSocket);
const reader = new WebSocketMessageReader(socket);
const writer = new WebSocketMessageWriter(socket);
languageClient = createLanguageClient({
reader,
writer
});
await languageClient.start();
reader.onClose(() => languageClient.stop());
};
return webSocket;
};
const createLanguageClient = (transports: MessageTransports): MonacoLanguageClient => {
return new MonacoLanguageClient({
name: 'Pyright Language Client',
clientOptions: {
documentSelector: [languageId],
errorHandler: {
error: () => ({ action: ErrorAction.Continue }),
closed: () => ({ action: CloseAction.DoNotRestart })
},
workspaceFolder: {
index: 0,
name: 'workspace',
uri: monaco.Uri.parse('/tmp')
},
synchronize: {
fileEvents: [vscode.workspace.createFileSystemWatcher('**')]
}
},
connectionProvider: {
get: () => {
return Promise.resolve(transports);
}
}
});
};
接下来这里需要创建一个虚拟文件系统,作为 Monaco Editor 实例的输入输出。
const fileSystemProvider = new RegisteredFileSystemProvider(false);
fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/test.py'), 'print("Hello, laf!")'));
registerFileSystemOverlay(1, fileSystemProvider);
const modelRef = await createModelReference(monaco.Uri.file('/test.py'));
最后创建 Monaco Editor 实例即可,还能进行些许的配置。
createConfiguredEditor(document.getElementById('container')!, {
model: modelRef.object.textEditorModel,
automaticLayout: true,
minimap: {
enabled: false
},
scrollbar: {
verticalScrollbarSize: 4,
horizontalScrollbarSize: 8,
},
overviewRulerLanes: 0,
lineNumbersMinChars: 4,
scrollBeyondLastLine: false,
theme: 'vs',
});
就这样我们也完成了前端。
import Editor from './python/Editor';
function App() {
return (
<>
<h2>monaco python lsp</h2>
<div style={{height:"500px", width:"800px", border:"1px solid black", padding:"8px 0"}}>
<Editor />
</div>
</>
)
}
export default App
效果如下
小结
要深入理解 LSP 以及其背后的工作原理还是有很大的难度的,但是好在有 languageserver,languageclient 这类优秀的开源项目提供支持,能够让我们在仅仅拼凑了几段代码后拥有不错的代码编辑器效果。下一步计划用 LSP 改造 Laf 的 Web IDE。
由于我也只是刚刚接触这块知识,文章中难免有错漏,希望能与读到这里的各位共同交流进步。
作者:米开朗基杨
链接:
https://www.imooc.com/article/336832
来源:慕课网
本文原创发布于慕课网 ,转载请注明出处,谢谢合作
猜你喜欢
- 2025-07-07 Python 异常处理方式汇总,建议收藏!
- 2025-07-07 一篇带你了解透SpringBoot(spring boot port)
- 2025-07-07 如何系统全面地自学 Java 语言?(如何自学javaweb)
- 2025-07-07 广东东软学院课程获“全国慕课教育创新大会暨高校在线开放课程联盟联席会2024年会”优秀案例
- 2025-07-07 大连东软信息学院案例获评“智能MOOC(IMOOC)”典型场景
- 2025-07-07 又到毕业季,送给你一些职场"充电"网站!
- 2025-07-07 08《Python 原生爬虫教程》最简单的爬虫案例开发
- 2025-07-07 推荐几个编程入门学习网站(适合编程入门的网站)
- 2024-08-03 推荐几个很实用的Java自学网站「很全面很实用」
- 2024-08-03 那些年高手大神秘密收藏的16个高质量免费自学网站!月薪二万必备
- 1509℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 530℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 493℃MySQL service启动脚本浅析(r12笔记第59天)
- 473℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 470℃启用MySQL查询缓存(mysql8.0查询缓存)
- 451℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 430℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 427℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)