來開發 Visual Studio Code SystemVerilog plugin 吧!開發紀錄(四)

Share on:

本文內容採用創用 CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款授權.

在前一篇文章說到了,如何在 Visual Studio Code 中使用 programmatic language features,加入滑鼠游標移動到程式碼時顯示一些提示的功能,上一篇文章中我們透過在 plugin 初始化的時候,使用了 registerHoverProvider 顯示 "Hover Content" 的提示。然而,在官方的文件中並不建議這樣作,而是使用 Lanugage Server 將 plugin 跟程式碼解析的功能分開。

在這篇文章中,將會展示跟前一篇文章一樣的功能,但是改為用 Language Server 實做,使用到的主要參考資料是對新手還是很難用的 官方新手範例

建立 Language Server 所需之檔案

要分離程式碼解析的功能,就是把程式碼解析這一個功能拆開,放到 Language Server 實做。為此,我們要新增一個 server/ 的資料夾,這個資料夾將會作為一個 sub-project 來實做 Language Server,最低限度需要的檔案有這些:

1server/
2 |- src/
3 |   |- server.ts
4 |- package.json
5 |- tsconfig.json
好了,那麼那幾個 JSON 檔案要怎麼寫呢?答案是~把現在的 project 的複製過去就差不多了!唯三要修改的點有如下:

第一,在 tsconfig.json 要加上 "composite": true 的 flag。作為新手,我當然不知道這個功能是什麼,總之放上去編譯器才不會抱怨。

1{
2"compilerOptions": {
3    "composite": true
4},
5}

第二,package.json 的相依性要換成這兩個,這倒是很好理解。

1{
2"dependencies": {
3    "vscode-languageserver": "^7.0.0",
4    "vscode-languageserver-textdocument": "^1.0.1"
5}
6}

最後,現在的 projecttsconfig.json 要引入這行作為 sub-project。作到這步驟,我忽然像起來很久以前我朋友抱怨 JavaScript 的編譯管理功能比手寫 Makefile 還難用,我終於理解他在說什麼了。

1{ "references": [ { "path": "./server"} ] }

改寫原本之 Plugin

在原本的 plugin 中,我們用一個 callback 實做 hover 的功能:

1vscode.languages.registerHoverProvider('javascript', {
2    provideHover(document, position, token) {
3        return {
4            contents: ['Hover Content']
5        };
6    }
7});

現在,我們把這些程式碼都刪掉,改為啟動 Language Server 就好了,這段程式碼幾乎是從 官方新手範例 直接複製過來的,這個大概是官方新手範例最有用處的地方了……

 1let serverModule = context.asAbsolutePath(
 2    path.join('server', 'out', 'server.js')
 3);
 4let debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] };
 5let serverOptions: ServerOptions = {
 6    run: { module: serverModule, transport: TransportKind.ipc },
 7    debug: {
 8        module: serverModule,
 9        transport: TransportKind.ipc,
10        options: debugOptions
11    }
12};
13let clientOptions: LanguageClientOptions = {
14    documentSelector: [{ scheme: 'file', language: 'systemverilog' }]
15};
16client = new LanguageClient(
17    'languageServerExample',
18    'Language Server Example',
19    serverOptions,
20    clientOptions
21);
22client.start();

實做 Language Server

官方新手範例提供的例子中相當的複雜,具有讀檔案、讀設定檔等等的強大功能,但是對於新手來說實在是不好懂。經過一番努力之後,我終於找出了,一個可動的 Language Server 最低限度需要寫的程式如下:

1const connection = createConnection(ProposedFeatures.all);
2const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
3connection.onInitialize(/* TODO */);
4connection.onHover(/* TODO */);
5documents.listen(connection);
6connection.listen();

裡面的 onInitialize()onHover() 是兩個 callbacks,聽名字也知道是「初始化」的時候跟「滑鼠游標移動到程式碼上面」的時候會作的事情。首先,先來看 onHover() 的內容,其程式碼跟前面不用 Language Server 的時候幾乎一樣。

1connection.onHover(
2    (_params: HoverParams): Hover => {
3        return {
4            contents: 'Hover',
5        };
6    }
7);

這邊想抱怨一下,關於 HoverParams 的文件,在網路上幾乎找不到,甚至也沒有太多 onHover() 裡面要用這個型別的文件。好在 TypeScript 是個強型別的程式語言,靠 Visual Studio Code 程式碼瀏覽的功能,多少能猜出要這樣寫……

onInitialize() 則是將範例的程式碼拿來簡化一下得來,這邊我還是要繼續抱怨,一開始這個 return 的設定裡面沒有 hoverProvider: true 這個選項,所以 hover 的功能一直沒有作用,我也是靠程式碼瀏覽的功能,猜出要這樣寫的。

 1connection.onInitialize((_params: InitializeParams) => {
 2    const result: InitializeResult = {
 3        capabilities: {
 4            textDocumentSync: TextDocumentSyncKind.Incremental,
 5            hoverProvider: true,
 6            workspace: {
 7                workspaceFolders: {
 8                    supported: true
 9                }
10            }
11        }
12    };
13    return result;
14});

完成之程式碼

本篇文章完成之 Github 連結在此