對(duì)于一個(gè)云開發(fā)平臺(tái)來說,一個(gè)好的 Web IDE
能很大程度地提高用戶的編碼體驗(yàn),而一個(gè) Web IDE
的一個(gè)重要組成部分就是代碼編輯器。
目前有著多款 web 上的代碼編輯器可供選擇,比如 Ace
、CodeMirror
、Monaco
,這三款編輯器的比較在這篇文章中有著詳細(xì)的介紹,在此就不作過多贅述。這篇文章我們選擇 Monaco Editor
來對(duì) LSP 進(jìn)行集成,從而在理論上能夠支持所有的編程語言。
原文鏈接:https://forum.laf.run/d/1027
什么是 LSP
LSP(Language Server Protocol),也就是語言服務(wù)協(xié)議,更具體更通俗地說就是定義了在代碼編輯器和語言服務(wù)器之間的一套規(guī)范,從而讓原本
m
個(gè)編輯器與 n
個(gè)編程語言之間的對(duì)應(yīng)關(guān)系
變?yōu)?/p>
m
個(gè)編輯器與 LSP
的關(guān)系和 n
個(gè)編程語言與 LSP
之間的關(guān)系,
從而將開發(fā)的復(fù)雜度由 m*n
降到了 m+n
。
除了對(duì)編輯器開發(fā)者和編程語言開發(fā)者友好,對(duì)我們這種嘗試讓一個(gè)編輯器支持多種語言的開發(fā)者也更是友好,有 vscode
這樣的編輯器珠玉在前,便能輕松地根據(jù) vscode
的設(shè)計(jì)思路實(shí)現(xiàn)我們的需求。
預(yù)覽
在這篇文章中,我們會(huì)開發(fā)一個(gè)最小最輕量的編輯器 Demo
作為演示,架構(gòu)非常簡(jiǎn)單,就是前端創(chuàng)建一個(gè) Monaco Editor
,后端創(chuàng)建一個(gè)語言服務(wù)器,二者之間通過 vscode-ws-jsonrpc
和 WebSocket
服務(wù)進(jìn)行傳輸,實(shí)際實(shí)現(xiàn)的 Web
端 Python
編輯器如下:
Server 端開發(fā)
在 Web
端能接入語言服務(wù)前,我們得先在服務(wù)端運(yùn)行一個(gè)語言服務(wù),https://langserver.org/ 這個(gè)網(wǎng)站收錄了許多語言服務(wù)的實(shí)現(xiàn),
這里我們選擇微軟官方維護(hù)的 pyright
提供語言服務(wù)
首先創(chuàng)建 Express
服務(wù)器,配置靜態(tài)文件服務(wù),使用 fileURLToPath
和 dirname
來獲取當(dāng)前文件的路徑,并將服務(wù)設(shè)置在 30000
端口
const app = express();
const __filename = fileURLToPath(import.meta.url);
const dir = dirname(__filename);
app.use(express.static(dir));
const server = app.listen(30000);
然后我們需要?jiǎng)?chuàng)建一個(gè) WebSocket Server,注意這里的 noServer 參數(shù),如果沒有指定 noServer,那么 WebSocketServer 會(huì)自動(dòng)創(chuàng)建一個(gè) http server 來處理瀏覽器的 HTTP 請(qǐng)求到 WebSocket 請(qǐng)求的 upgrade。
const wss = new WebSocketServer({
noServer: true,
});
而這里我們需要?jiǎng)?chuàng)建自己的 HTTP 服務(wù)器,并手動(dòng)處理瀏覽器的 upgrade 請(qǐng)求。下面代碼便是如何監(jiān)聽 upgrade 事件并進(jìn)行處理。
server.on('upgrade',()=>{});
在處理函數(shù)中,按照下面的代碼將 WebSocket 使用到 jsonrpc 協(xié)議中,并啟動(dòng)語言服務(wù)器讓二者相連。
先構(gòu)建語言服務(wù)器的路徑,找到 pyright 包所在的位置。
const baseDir = resolve(getLocalDirectory(import.meta.url));
const relativeDir = '../../../node_modules/pyright/dist/pyright-langserver.js';
const ls = resolve(baseDir, relativeDir);
再創(chuàng)建語言服務(wù)器的連接 和 創(chuàng)建 WebSocket 的數(shù)據(jù)連接
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 函數(shù)將消息從 soketConnection 轉(zhuǎn)發(fā)到 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;
});
于是我們將語言服務(wù)器跑起來,并在文章后面階段會(huì)寫的前端編輯器中隨便輸入一點(diǎn)東西,可以看到終端里輸出了 message,
這樣我們就使用 pyright 完成了語言服務(wù)器的開發(fā)。
Web 端開發(fā)
接下來我們開發(fā)前端的內(nèi)容,還是在上面的那個(gè)網(wǎng)站中,
可以看到 Monaco Editor 也是有支持 LSP 的方案的,所以我們使用 TypeFox 開發(fā)的 monaco-languageclient 對(duì)monaco 進(jìn)行集成 lsp 的開發(fā)。
首先使用 monaco-languageclient 的 initServices 函數(shù)初始化一些服務(wù),其中最重要的就是下面四個(gè)配置,定義了 Monaco 的語言服務(wù)與主題顯示。
await initServices({
enableModelService: true,
enableThemeService: true,
enableTextmateService: true,
enableLanguagesService: true,
})
然后創(chuàng)建能夠與語言服務(wù)器相連的 WebSocket 連接。
createWebSocket("ws://localhost:30000/pyright");
而創(chuàng)建這個(gè)連接也需要用到 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);
}
}
});
};
接下來這里需要?jiǎng)?chuàng)建一個(gè)虛擬文件系統(tǒng),作為 Monaco Editor 實(shí)例的輸入輸出。
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'));
最后創(chuàng)建 Monaco Editor 實(shí)例即可,還能進(jìn)行些許的配置。
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
效果如下
小結(jié)
要深入理解 LSP 以及其背后的工作原理還是有很大的難度的,但是好在有 languageserver,languageclient 這類優(yōu)秀的開源項(xiàng)目提供支持,能夠讓我們?cè)趦H僅拼湊了幾段代碼后擁有不錯(cuò)的代碼編輯器效果。下一步計(jì)劃用 LSP 改造 Laf 的 Web IDE。文章來源:http://www.zghlxwxcb.cn/news/detail-705994.html
由于我也只是剛剛接觸這塊知識(shí),文章中難免有錯(cuò)漏,希望能與讀到這里的各位共同交流進(jìn)步。文章來源地址http://www.zghlxwxcb.cn/news/detail-705994.html
參考資料
- https://github.com/microsoft/language-server-protocol/wiki/Protocol-History
- https://medium.com/@malintha1996/understanding-the-language-server-protocol-5c0ba3ac83d2
- https://ubug.io/blog/workpad-part-6
- https://www.typefox.io/blog/how-to-embed-a-monaco-editor-in-a-browser-as-a-part-of-my-first-task-at-typefox
- https://www.typefox.io/blog/teaching-the-language-server-protocol-to-microsofts-monaco-editor
到了這里,關(guān)于如何創(chuàng)建集成 LSP 支持多語言的 Web 代碼編輯器的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!