如何编写一个 Vscode 语言插件

2,813 阅读3分钟

编写一个 Vscode 语言插件

语言插件能做什么?

  1.  语法提示
    
  2.  错误标注
    
  3.  代码格式化
    
  4.  ...
    

我们要做什么?

处理输入的文本,调用VSCode的API来实现具体的功能。 例如:我们要实现代码格式化,可以分为三步。 第一步:获取用户输入的文本。 第二步:格式化用户输入的文本。 第三步,调用VSCode的API来替换文本。

VSCode将提供处理用户输入,格式化文本的部分称为 Language Server。我们的各种语言功能都在Language Server中实现。 如下图所示:

在上图中,Language Server 直接调用VSCode的API来操作编辑器,两者之间是强耦合的。这样就产生了一个新的问题,假如我们想要在Sublime也实现相同功能,就要再写一个以来Sublime API的Server。所以,VSCode进一步改进了这个方案,提供了基于LSP(Language Server Protocol)Client-Server模式。在这个模式中,Language Server 和 编辑器只需要各自实现 LSP 协议就可以了。

LSP 主要结构如下

Content-Length: ...\r\n
\r\n
{
	"jsonrpc": "2.0",
	"id": 1,
	"method": "textDocument/didOpen",
	"params": {
		...
	}
}

可以看到,主体部分是一个 JSON,这个是基于 JSON-RPC 协议的 JSON 结构。 其中 jsonrpc 代表 JSON-RPC 的协议版本,LSP 是基于2.0的。 id 代表一个消息的表示 method 代表消息的功能类型 params 表示消息的具体参数

基于这一套结构。我们需要做两件事情。

  1. 实现一个基于 LSP 与外界通信的 Language Server。
  2. 实现一个 VSCode 插件来注册具体语言,并且和 Language Server 通信。

介绍完基础内容,下面我们从官方的例子来讲解如何实现一个插件。 官方例子:github.com/Microsoft/v…

1. 注册插件事件

在 package.json 中添加

"activationEvents": [
		"onLanguage:plaintext"
]

这个配置使得插件在 plaintext 语言被使用的时候被激活。

2. 实现Server。

VSCode 提供了 NodeJs 平台的 Server 包 vscode-languageserver,这个包封装了很多工具类,可以节约大量时间。

2.1 创建 Connection 和 Documens

let connection = createConnection(ProposedFeatures.all);// 采用node ipc通信
let documents: TextDocuments = new TextDocuments();

connection 负责网络之间的通信 documents 负责用户文档的管理。

2.2 监听初始化事件

connection.onInitialize((params: InitializeParams) => {
    let capabilities = params.capabilities;

    // Does the client support the `workspace/configuration` request?
    // If not, we will fall back using global settings
    hasConfigurationCapability =
        capabilities.workspace && !!capabilities.workspace.configuration;
    hasWorkspaceFolderCapability =
        capabilities.workspace && !!capabilities.workspace.workspaceFolders;
    hasDiagnosticRelatedInformationCapability =
        capabilities.textDocument &&
        capabilities.textDocument.publishDiagnostics &&
        capabilities.textDocument.publishDiagnostics.relatedInformation;

    return {
        capabilities: {
            textDocumentSync: documents.syncKind,
            // Tell the client that the server supports code completion
            completionProvider: {
                resolveProvider: true
            }
        }
    };
});

这边获取客户端的一些基本信息,并且返回服务器自身的一些信息。

2.3 监听文档事件

documents.onDidClose(e => {
    ...
});
documents.onDidChangeContent(change => {
    // 可以在这边检查文档的语法
    ...
});

2.4 实现代码补全功能

connection.onCompletion(
    (_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
        return [
            {
                label: 'TypeScript',
                kind: CompletionItemKind.Text,
                data: 1
            },
            {
                label: 'JavaScript',
                kind: CompletionItemKind.Text,
                data: 2
            }
        ];
    }
);

这边我们实现了一个简单的补全功能,只返回两个补全项。

2.5 启动服务

documents.listen(connection);// 监听connection中的文档打开,关闭事件等
connection.listen();// 监听客户端的请求
// 两者关系:你保护世界,我保护你。:)

3. 实现客户端

客户端就是VSCode的插件,所以基本内容和插件是一样的,只是里面功能不一样。 和服务器一样,客户端也有一个工具包 vscode-languageclient

3.1 创建client对象,设置server地址和一些配置,启动并监听 Server。

export fucntion activate () {
// Create the language client and start the client.
    client = new LanguageClient(
        'languageServerExample',
        'Language Server Example',
        serverOptions,
        clientOptions
    );

    // Start the client. This will also launch the server
    client.start();
}

3.2 注册销毁操作

export fucntion activate () {
// Create the language client and start the client.
    client = new LanguageClient(
        'languageServerExample',
        'Language Server Example',
        serverOptions,
        clientOptions
    );

    // Start the client. This will also launch the server
    client.start();
}

一个简单的语言插件到此就完成了,详细的内容可以查看 VSCode 提供的例子。