VSCode插件开发实战:LSP文本同步第8讲
vscode插件快餐教程(8) - LSP文本同步
从这一节开始,我们来聊一聊如何通过LSP协议做文本同步。说白了,就是让语言服务器知道编辑器里文件内容的变化——打开、修改、关闭这些操作,都需要实时同步过去。
文件打开
先从最简单的入手:监听文件打开事件。这背后的LSP协议里,对应的参数是DidChangeTextDocumentParams结构。先看一张协议结构的截图:
微软的SDK已经在这个协议基础上做了封装,接口更友好。我们来看封装后的API:
当前,TextDocument提供了4个属性:
uri: 文件的URIversion: 文件的版本号languageId: 编程语言lineCount: 有多少行
另外还有3个函数:getText()获取全文,positionAt和offsetAt用于Position和offset之间的转换。
光说不练假把式,直接看代码示例:
documents.onDidOpen((event: TextDocumentChangeEvent) => {
logger.debug(`on open:${event.document.uri}`);
logger.debug(`file version:${event.document.version}`);
logger.debug(`file content:${event.document.getText()}`);
logger.debug(`language id:${event.document.languageId}`);
logger.debug(`line count:${event.document.lineCount}`);
});
跑起来的效果呢?给大家贴一段真实日志:
[2019-06-04T18:11:31.999] [DEBUG] lsp_demo - on open:file:///Users/ziyingliuziying/test.vb
[2019-06-04T18:11:31.999] [DEBUG] lsp_demo - file version:1
[2019-06-04T18:11:31.999] [DEBUG] lsp_demo - file content:dim a as integer;TextView1Ja vascriptButton3Test2
[2019-06-04T18:11:31.999] [DEBUG] lsp_demo - language id:vb
[2019-06-04T18:11:32.000] [DEBUG] lsp_demo - line count:6
监听文件变化
监听文件变化与监听打开文件,写法几乎一模一样。代码长这样:
documents.onDidChangeContent((e: TextDocumentChangeEvent) => {
logger.debug('document change received.');
logger.debug(`document version:${e.document.version}`);
logger.debug(`text:${e.document.getText()}`);
logger.debug(`language id:${e.document.languageId}`);
logger.debug(`line count:${e.document.lineCount}`);
});
看看运行时日志,能清楚看到每次修改后版本号递增、内容变化:
[2019-06-04T18:30:34.329] [DEBUG] lsp_demo - document change received.
[2019-06-04T18:30:34.329] [DEBUG] lsp_demo - document version:1
[2019-06-04T18:30:34.329] [DEBUG] lsp_demo - text:dim a as integer;TextView1Ja vascriptButton3Test2
[2019-06-04T18:30:34.329] [DEBUG] lsp_demo - language id:vb
[2019-06-04T18:30:34.329] [DEBUG] lsp_demo - line count:6
[2019-06-04T18:30:39.457] [DEBUG] lsp_demo - document change received.
[2019-06-04T18:30:39.457] [DEBUG] lsp_demo - document version:2
[2019-06-04T18:30:39.457] [DEBUG] lsp_demo - text:
[2019-06-04T18:30:39.457] [DEBUG] lsp_demo - language id:vb
[2019-06-04T18:30:39.458] [DEBUG] lsp_demo - line count:2
[2019-06-04T18:30:41.576] [DEBUG] lsp_demo - document change received.
[2019-06-04T18:30:41.576] [DEBUG] lsp_demo - document version:3
[2019-06-04T18:30:41.577] [DEBUG] lsp_demo - text:b
[2019-06-04T18:30:41.577] [DEBUG] lsp_demo - language id:vb
[2019-06-04T18:30:41.577] [DEBUG] lsp_demo - line count:1
[2019-06-04T18:30:41.949] [DEBUG] lsp_demo - document change received.
[2019-06-04T18:30:41.949] [DEBUG] lsp_demo - document version:4
[2019-06-04T18:30:41.949] [DEBUG] lsp_demo - text:u
[2019-06-04T18:30:41.949] [DEBUG] lsp_demo - language id:vb
[2019-06-04T18:30:41.949] [DEBUG] lsp_demo - line count:1
[2019-06-04T18:30:42.447] [DEBUG] lsp_demo - document change received.
[2019-06-04T18:30:42.447] [DEBUG] lsp_demo - document version:5
[2019-06-04T18:30:42.447] [DEBUG] lsp_demo - text:Button5
[2019-06-04T18:30:42.447] [DEBUG] lsp_demo - language id:vb
[2019-06-04T18:30:42.447] [DEBUG] lsp_demo - line count:1
文本监听模式
上面使用的监听方式是增量监听,对应的模式是TextDocumentSyncKind.Incremental。具体初始化代码里这样配置:
connection.onInitialize((params: InitializeParams) => {
return {
capabilities: {
textDocumentSync: {
openClose: true,
change: TextDocumentSyncKind.Incremental
},
completionProvider: {
resolveProvider: true
}
}
};
});
增量模式好理解:每次只把变化了的部分传过去,网络开销小。但如果需要完整状态,也可以改成全量模式。区别只在于把Incremental换成Full:
connection.onInitialize((params: InitializeParams) => {
return {
capabilities: {
textDocumentSync: {
openClose: true,
change: TextDocumentSyncKind.Full
},
completionProvider: {
resolveProvider: true
}
}
};
});
全量模式下,每次变化后都会把整个文件内容原封不动传过来。看日志就清楚了:
[2019-06-04T19:52:12.305] [DEBUG] lsp_demo - document change received.
[2019-06-04T19:52:12.305] [DEBUG] lsp_demo - document version:1
[2019-06-04T19:52:12.305] [DEBUG] lsp_demo - text:dim a as integer;TextView1Ja vascriptButton3Test2Button5
[2019-06-04T19:52:12.305] [DEBUG] lsp_demo - language id:vb
[2019-06-04T19:52:12.305] [DEBUG] lsp_demo - line count:7
[2019-06-04T19:52:19.442] [DEBUG] lsp_demo - document change received.
[2019-06-04T19:52:19.442] [DEBUG] lsp_demo - document version:2
[2019-06-04T19:52:19.442] [DEBUG] lsp_demo - text:dim a as integer;TextView1Ja vascriptButton3Test2Button5T
[2019-06-04T19:52:19.443] [DEBUG] lsp_demo - language id:vb
[2019-06-04T19:52:19.443] [DEBUG] lsp_demo - line count:7
[2019-06-04T19:52:19.443] [DEBUG] lsp_demo - onCompletion
[2019-06-04T19:52:19.787] [DEBUG] lsp_demo - document change received.
[2019-06-04T19:52:19.787] [DEBUG] lsp_demo - document version:5
[2019-06-04T19:52:19.787] [DEBUG] lsp_demo - text:dim a as integer;TextView1Ja vascriptButton3Test2Button5Test
[2019-06-04T19:52:19.787] [DEBUG] lsp_demo - language id:vb
[2019-06-04T19:52:19.787] [DEBUG] lsp_demo - line count:7
[2019-06-04T19:52:19.788] [DEBUG] lsp_demo - onCompletion
[2019-06-04T19:52:21.877] [DEBUG] lsp_demo - document change received.
[2019-06-04T19:52:21.877] [DEBUG] lsp_demo - document version:6
[2019-06-04T19:52:21.877] [DEBUG] lsp_demo - text:dim a as integer;TextView1Ja vascriptButton3Test2Button5Test
[2019-06-04T19:52:21.877] [DEBUG] lsp_demo - language id:vb
[2019-06-04T19:52:21.877] [DEBUG] lsp_demo - line count:8
除了增量和全量,还有一种TextDocumentSyncKind.None模式,选了它就连文本都不同步了——基本用不上,除非你的语言服务器完全不需要文件内容,只靠URI做标记。但大多数场景下,还是增量和全量二选一,至于选哪个,看你服务器对完整内容的依赖程度和网络带宽的考量。

