你不知道的 VSCode 代碼高亮原理

全文5000字,解讀 vscode 背後的代碼高亮實現原理,歡迎點贊關注轉發。

Vscode 的代碼高亮、代碼補齊、錯誤診斷、跳轉定義等語言功能由兩種擴展方案協同實現,包括:javascript

  • 基於詞法分析技術,識別分詞 token 並應用高亮樣式
  • 基於可編程語言特性接口,識別代碼語義並應用高亮樣式,此外還能實現錯誤診斷、智能提示、格式化等功能

兩種方案的功能範疇逐級遞增,相應地技術複雜度與實現成本也逐級升高,本文將概要介紹兩種方案的工做過程與特色,各自完成什麼工做,互相這麼寫做,並結合實際案例一步步揭開 vscode 代碼高亮功能的實現原理:css

Vscode 插件基礎

介紹 vscode 代碼高亮原理以前,有必要先熟悉一下 vscode 的底層架構。與 Webpack 類似,vscode 自己只是實現了一套架子,架子內部的命令、樣式、狀態、調試等功能都以插件形式提供,vscode 對外提供了五種拓展能力:html

其中,代碼高亮功能由 語言擴展 類插件實現,根據實現方式又能夠細分爲:前端

  • 聲明式 :以特定 JSON 結構聲明一堆匹配詞法的正則,無需編寫邏輯代碼便可添加如塊級匹配、自動縮進、語法高亮等語言特性,vscode 內置的 extendsions/css、extendsions/html 等插件都是基於聲明式接口實現的
  • 編程式 :vscode 運行過程當中會監聽用戶行爲,在特定行爲發生後觸發事件回調,編程式語言擴展須要監聽這些事件,動態分析文本內容並按特定格式返回代碼信息

聲明式性能高,能力弱;編程式性能低,能力強。語言插件開發者一般能夠混用,用聲明式接口在最短期內識別出詞法 token,提供基本的語法高亮功能;以後用編程式接口動態分析內容,提供更高級特性好比錯誤診斷、智能提示等。vue

Vscode 中的聲明式語言擴展基於 TextMate 詞法分析引擎實現;編程式語言擴展則基於語義分析接口、vscode.language.* 接口、Language Server Protocol 協議三種方式實現,下面展開介紹每種技術方案的基本邏輯。java

詞法高亮

詞法分析(Lexical Analysis) 是計算機學科中將字符序列轉換爲 標記(token) 序列的過程,而 標記(token) 是構成源代碼的最小單位,詞法分析技術在編譯、IDE等領域有很是普遍的應用。git

好比 vscode 的詞法引擎分析出 token 序列後再根據 token 的類型應用高亮樣式,這個過程能夠簡單劃分爲分詞、樣式應用兩個步驟。github

參考資料:express

分詞

分詞過程本質上將一長串代碼遞歸地拆解爲具備特定含義、分類的字符串片斷,好比 +-*/% 等操做符;var/const 等關鍵字;1234"tecvan" 類型的常量值等,簡單說就是從一段文本中識別出,什麼地方有一個什麼詞。編程

Vscode 的詞法分析基於 TextMate 引擎實現,功能比較複雜,能夠簡單劃分爲三個方面:基於正則的分詞、複合分詞規則、嵌套分詞規則。

基本規則

Vscode 底層的 TextMate 引擎基於 正則 匹配實現分詞功能,運行時逐行掃描文本內容,用預約義的 rule 集合測試文本行中是否包含匹配特定正則的內容,例如對於下面的規則配置:

{
    "patterns": [
        {
            "name": "keyword.control",
            "match": "\b(if|while|for|return)\b"
        }
    ]
}

示例中,patterns 用於定義規則集合, match 屬性定於用於匹配 token 的正則,name 屬性聲明該 token 的分類(scope),TextMate 分詞過程遇到匹配 match 正則的內容時,會將其看做單獨 token 處理並分類爲 name 聲明的 keyword.control 類型。

上述示例會將 if/while/for/return 關鍵詞識別爲 keyword.control 類型,但沒法識別其它關鍵字:

在 TextMate 語境中,scope 是一種 . 分割的層級結構,例如 keywordkeyword.control 造成父子層級,這種層級結構在樣式處理邏輯中能實現一種相似 css 選擇器的匹配,後面會講到細節。

複合分詞

上述示例配置對象在 TextMate 語境下被稱做 Language Rule,除了 match 用於匹配單行內容,還可使用 begin + end 屬性對匹配更復雜的跨行場景。從 beginend 所識別到的範圍內,都認爲是 name 類型的 token,好比在 vuejs/vetur 插件的 syntaxes/vue.tmLanguage.json 文件中有這麼一段配置:

{
    "name": "Vue",
    "scopeName": "source.vue",
    "patterns": [
        {
          "begin": "(<)(style)(?![^/>]*/>\\s*$)",
          // 虛構字段,方便解釋
          "name": "tag.style.vue",
          "beginCaptures": {
            "1": {
              "name": "punctuation.definition.tag.begin.html"
            },
            "2": {
              "name": "entity.name.tag.style.html"
            }
          },
          "end": "(</)(style)(>)",
          "endCaptures": {
            "1": {
              "name": "punctuation.definition.tag.begin.html"
            },
            "2": {
              "name": "entity.name.tag.style.html"
            },
            "3": {
              "name": "punctuation.definition.tag.end.html"
            }
          }
        }
    ]
}

配置中,begin 用於匹配 <style> 語句,end 用於匹配 </style> 語句,且 <style></style> 整個語句被賦予 scope 爲 tag.style.vue 。此外,語句中字符被 beginCapturesendCaptures 屬性分配成不一樣的 scope 類型:

這裏從 beginbeginCaptures ,從 endendCaptures 造成了某種程度的複合結構,從而實現一次匹配多行內容。

規則嵌套

在上述 begin + end 基礎上,TextMate 還支持以子 patterns 方式定義嵌套的語言規則,例如:

{
    "name": "lng",
    "patterns": [
        {
            "begin": "^lng`",
            "end": "`",
            "name": "tecvan.lng.outline",
            "patterns": [
                {
                    "match": "tec",
                    "name": "tecvan.lng.prefix"
                },
                {
                    "match": "van",
                    "name": "tecvan.lng.name"
                }
            ]
        }
    ],
    "scopeName": "tecvan"
}

配置識別 lng` ` 之間的字符串,並分類爲 tecvan.lng.outline 。以後,遞歸處理二者之間的內容並按照子 patterns 規則匹配出更具體的 token ,例如對於:

lng`awesome tecvan`

可識別出分詞:

  • lng`awesome tecvan` ,scope 爲 tecvan.lng.outline
  • tec ,scope 爲 tecvan.lng.prefix
  • van ,scope 爲 tecvan.lng.name

TextMate 還支持語言級別的嵌套,例如:

{
    "name": "lng",
    "patterns": [
        {
            "begin": "^lng`",
            "end": "`",
            "name": "tecvan.lng.outline",
            "contentName": "source.js"
        }
    ],
    "scopeName": "tecvan"
}

基於上述配置, lng` ` 之間的內容都會識別爲 contentName 指定的 source.js 語句。

樣式

詞法高亮本質上就是先按上述規則將原始文本拆解成多個具類的 token 序列,以後按照 token 的類型適配不一樣的樣式。TextMate 在分詞基礎上提供了一套按照 token 類型字段 scope 配置樣式的功能結構,例如:

{
    "tokenColors": [
        {
            "scope": "tecvan",
            "settings": {
                "foreground": "#eee"
            }
        },
        {
            "scope": "tecvan.lng.prefix",
            "settings": {
                "foreground": "#F44747"
            }
        },
        {
            "scope": "tecvan.lng.name",
            "settings": {
                "foreground": "#007acc",
            }
        }
    ]
}

示例中,scope 屬性支持一種被稱做 Scope Selectors 的匹配模式,這種模式與 css 選擇器相似,支持:

  • 元素選擇,例如 scope = tecvan.lng.prefix 可以匹配 tecvan.lng.prefix 類型的token;特別的 scope = tecvan 可以匹配 tecvan.lngtecvan.lng.prefix 等子類型的 token
  • 後代選擇,例如 scope = text.html source.js 用於匹配 html 文檔中的 JavaScript 代碼
  • 分組選擇,例如 scope = string, comment 用於匹配字符串或備註

插件開發者能夠自定義 scope 也能夠選擇複用 TextMate 內置的許多 scope ,包括 comment、constant、entity、invalid、keyword 等,完整列表請查閱 官網

settings 屬性則用於設置該 token 的表現樣式,支持foreground、background、bold、italic、underline 等樣式屬性。

實例解析

看完原理咱們來拆解一個實際案例: https://github.com/mrmlnc/vsc...json5 是 JSON 擴展協議,旨在令人類更易於手動編寫和維護,支持備註、單引號、十六進制數字等特性,這些拓展特性須要使用 vscode-json5 插件實現高亮效果:

上圖中,左邊是沒有啓動 vscode-json5 的效果,右邊是啓動後的效果。

vscode-json5 插件源碼很簡單,兩個關鍵點:

  • package.json 文件中聲明插件的 contributes 屬性,能夠理解爲插件的入口:
"contributes": {
    // 語言配置
    "languages": [{
      "id": "json5",
      "aliases": ["JSON5", "json5"],
      "extensions": [".json5"],
      "configuration": "./json5.configuration.json"
    }],
    // 語法配置
    "grammars": [{
      "language": "json5",
      "scopeName": "source.json5",
      "path": "./syntaxes/json5.json"
    }]
  }
  • 在語法配置文件 ./syntaxes/json5.json 中按照 TextMate 的要求定義 Language Rule:
{
    "scopeName": "source.json5",
    "fileTypes": ["json5"],
    "name": "JSON5",
    "patterns": [
        { "include": "#array" },
        { "include": "#constant" }
        // ...
    ],
    "repository": {
        "array": {
            "begin": "\\[",
            "beginCaptures": {
                "0": { "name": "punctuation.definition.array.begin.json5" }
            },
            "end": "\\]",
            "endCaptures": {
                "0": { "name": "punctuation.definition.array.end.json5" }
            },
            "name": "meta.structure.array.json5"
            // ...
        },
        "constant": {
            "match": "\\b(?:true|false|null|Infinity|NaN)\\b",
            "name": "constant.language.json5"
        } 
        // ...
    }
}

OK,結束了,沒了,就是這麼簡單,以後 vscode 就能夠根據這份配置適配 json5 的語法高亮規則。

調試工具

Vscode 內置了一套 scope inspect 工具,用於調試 TextMate 檢測出的 token、scope 信息,使用時只須要將編輯器光標 focus 到特定 token 上,快捷鍵 ctrl + shift + p 打開 vscode 命令面板後輸出 Developer: Inspect Editor Tokens and Scopes 命令並回車:

命令運行後就能夠看到分詞 token 的語言、scope、樣式等信息。

編程式語言擴展

詞法分析引擎 TextMate 本質上是一種基於正則的靜態詞法分析器,優勢是接入方式標準化,成本低且運行效率較高,缺點是靜態代碼分析很難實現某些上下文相關的 IDE 功能,例如對於下面的代碼:

注意代碼第一行函數參數 languageModes 與第二行函數體內的 languageModes 是同一實體可是沒有實現相同的樣式,視覺上沒有造成聯動。

爲此,vscode 在 TextMate 引擎以外提供了三種更強大也更復雜的語言特性擴展機制:

  • 使用 DocumentSemanticTokensProvider 實現可編程的語義分析
  • 使用 vscode.languages.* 下的接口監聽各種編程行爲事件,在特定時間節點實現語義分析
  • 根據 Language Server Protocol 協議實現一套完備的語言特性分析服務器

相比於上面介紹的聲明式的詞法高亮,語言特性接口更靈活,可以實現諸如錯誤診斷、候選詞、智能提示、定義跳轉等高級功能。

參考資料:

DocumentSemanticTokensProvider 分詞

簡介

Sematic Tokens Provider 是 vscode 內置的一種對象協議,它須要自行掃描代碼文件內容,而後以整數數組形式返回語義 token 序列,告訴 vscode 在文件的哪一行、那一列、多長的區間內是一個什麼類型的 token。

注意區分一下,TextMate 中的掃描是引擎驅動的,逐行匹配正則,而 Sematic Tokens Provider 場景下掃描規則、匹配規則都交由插件開發者自行實現,靈活性加強但相對的開發成本也會更高。

實現上,Sematic Tokens Providervscode.DocumentSemanticTokensProvider 接口定義,開發者能夠按需實現兩個方法:

  • provideDocumentSemanticTokens :全量分析代碼文件語義
  • provideDocumentSemanticTokensEdits :增量分析正在編輯模塊的語義

咱們來看個完整的示例:

import * as vscode from 'vscode';

const tokenTypes = ['class', 'interface', 'enum', 'function', 'variable'];
const tokenModifiers = ['declaration', 'documentation'];
const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers);

const provider: vscode.DocumentSemanticTokensProvider = {
  provideDocumentSemanticTokens(
    document: vscode.TextDocument
  ): vscode.ProviderResult<vscode.SemanticTokens> {
    const tokensBuilder = new vscode.SemanticTokensBuilder(legend);
    tokensBuilder.push(      
      new vscode.Range(new vscode.Position(0, 3), new vscode.Position(0, 8)),
      tokenTypes[0],
      [tokenModifiers[0]]
    );
    return tokensBuilder.build();
  }
};

const selector = { language: 'javascript', scheme: 'file' };

vscode.languages.registerDocumentSemanticTokensProvider(selector, provider, legend);

相信大多數讀者對這段代碼都會以爲陌生,我想了好久,以爲仍是從函數輸出的角度開始講起比較容易理解,也就是上例代碼第 17 行 tokensBuilder.build()

輸出結構

provideDocumentSemanticTokens 函數要求返回一個整數數組,數組項按 5 位爲一組分別表示:

  • 5 * i 位,token 所在行相對於上一個 token 的偏移
  • 5 * i + 1 位,token 所在列相對於上一個 token 的偏移
  • 5 * i + 2 位,token 長度
  • 5 * i + 3 位,token 的 type 值
  • 5 * i + 4 位,token 的 modifier 值

咱們須要理解這是一個位置強相關的整數數組,數組中每 5 個項描述一個 token 的位置、類型。token 位置由所在行、列、長度三個數字組成,而爲了壓縮數據的大小 vscode 有意設計成相對位移的形式,例如對於這樣的代碼:

const name as

假如只是簡單地按空格分割,那麼這裏能夠解析出三個 token:constnameas ,對應的描述數組爲:

[
// 對應第一個 token:const
0, 0, 5, x, x,
// 對應第二個 token: name
0, 6, 4, x, x,
// 第三個 token:as
0, 5, 2, x, x
]

注意這裏是以相對前一個 token 位置的形式描述的,好比 as 字符對應的 5 個數字的語義爲:相對前一個 token 偏移 0 行、5 列,長度爲 2 ,類型爲 xx。

剩下的第 5 * i + 3 位與第 5 * i + 4 位分別描述 token 的 type 與 modifier,其中 type 指示 token 的類型,例如 comment、class、function、namespace 等等;modifier 是類型基礎上的修飾器,能夠近似理解爲子類型,好比對於 class 有多是 abstract 的,也有多是從標準庫導出 defaultLibrary。

type、modifier 的具體數值須要開發者自行定義,例如上例中:

const tokenTypes = ['class', 'interface', 'enum', 'function', 'variable'];
const tokenModifiers = ['declaration', 'documentation'];
const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers);

// ...

vscode.languages.registerDocumentSemanticTokensProvider(selector, provider, legend);

首先經過 vscode. SemanticTokensLegend 類構建 type、modifier 的內部表示 legend 對象,以後使用 vscode.languages.registerDocumentSemanticTokensProvider 接口與 provider 一塊兒註冊到 vscode 中。

語義分析

上例中 provider 的主要做用就是遍歷分析文件內容,返回符合上述規則的整數數組,vscode 對具體的分析方法並無作限定,只是提供了用於構建 token 描述數組的工具 SemanticTokensBuilder,例如上例中:

const provider: vscode.DocumentSemanticTokensProvider = {
  provideDocumentSemanticTokens(
    document: vscode.TextDocument
  ): vscode.ProviderResult<vscode.SemanticTokens> {
    const tokensBuilder = new vscode.SemanticTokensBuilder(legend);
    tokensBuilder.push(      
      new vscode.Range(new vscode.Position(0, 3), new vscode.Position(0, 8)),
      tokenTypes[0],
      [tokenModifiers[0]]
    );
    return tokensBuilder.build();
  }
};

代碼使用 SemanticTokensBuilder 接口構建並返回了一個 [0, 3, 5, 0, 0] 的數組,即第 0 行,第 3 列,長度爲 5 的字符串,type =0,modifier = 0,運行效果:

除了這一段被識別出的 token 外,其它字符都被認爲不可識別。

小結

本質上,DocumentSemanticTokensProvider 只是提供了一套粗糙的 IOC 接口,開發者能作的事情比較有限,因此如今大多數插件都沒有采用這種方案,讀者理解便可,沒必要深究。

Language API

簡介

相對而言,vscode.languages.* 系列 API 所提供的語言擴展能力可能更符合前端開發者的思惟習慣。vscode.languages.* 託管了一系列用戶交互行爲的處理、歸類邏輯,並以事件接口方式開放出來,插件開發者只需監聽這些事件,根據參數推斷語言特性,並按規則返回結果便可。

Vscode Language API 提供了不少事件接口,好比說:

  • registerCompletionItemProvider: 提供代碼補齊提示

  • registerHoverProvider:光標停留在 token 上時觸發

  • registerSignatureHelpProvider:提供函數簽名提示

完整的列表請查閱 https://code.visualstudio.com... 一文。

Hover 示例

Hover 功能實現分兩步,首先須要在 package.json 中聲明 hover 特性:

{
    ...
    "main": "out/extensions.js",
    "capabilities" : {
        "hoverProvider" : "true",
        ...
    }
}

以後,須要在 activate 函數中調用 registerHoverProvider 註冊 hover 回調:

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    vscode.languages.registerHoverProvider('language name', {
        provideHover(document, position, token) {
            return { contents: ['aweome tecvan'] };
        }
    });
    ...
}

運行結果:

其它特性功能的寫法與此類似,感興趣的同窗建議到官網自行查閱。

Language Server Protocol

簡介

上述基於語言擴展插件的代碼高亮方法有一個類似的問題:難以在編輯器間複用,同一個語言,須要根據編輯器環境、語言重複編寫功能類似的支持插件,那麼對於 n 種語言,m 中編輯器,這裏面的開發成本就是 n * m

爲了解決這個問題,微軟提出了一種叫作 Language Server Protocol 的標準協議,語言功能插件與編輯器之間再也不直接通信,而是經過 LSP 作一層隔離:

增長 LSP 層帶來兩個好處:

  • LSP 層的開發語言、環境等與具體 IDE 所提供的 host 環境脫耦
  • 語言插件的核心功能只須要編寫一次,就能夠複用到支持 LSP 協議的 IDE 中

雖然 LSP 與上述 Language API 能力上幾乎相同,但藉助這兩個優勢大大提高了插件的開發效率,目前不少 vscode 語言類插件都已經遷移到 LSP 實現,包括 vetur、eslint、Python for VSCode 等知名插件。

Vscode 中的 LSP 架構包含兩部分:

  • Language Client: 一個標準 vscode 插件,實現與 vscode 環境的交互,例如 hover 事件首先會傳遞到 client,再由 client 傳遞到背後的 server
  • Language Server: 語言特性的核心實現,經過 LSP 協議與 Language Client 通信,注意 Server 實例會以單獨進程方式運行

作個類比,LSP 就是通過架構優化的 Language API,原來由單個 provider 函數實現的功能拆解爲 Client + Server 兩端跨語言架構,Client 與 vscode 交互並實現請求轉發;Server 執行代碼分析動做,並提供高亮、補全、提示等功能,以下圖:

簡單示例

LSP 稍微有一點點複雜,建議讀者先拉下 vscode 官方示例對比學習:

git clone https://github.com/microsoft/vscode-extension-samples.git
cd vscode-extension-samples/lsp-sample
yarn
yarn compile
code .

vscode-extension-samples/lsp-sample 的主要代碼文件有:

.
├── client // Language Client
│   ├── src
│   │   └── extension.ts // Language Client 入口文件
├── package.json 
└── server // Language Server
    └── src
        └── server.ts // Language Server 入口文件

樣例代碼中有幾個關鍵點:

  1. package.json 中聲明激活條件與插件入口
  2. 編寫入口文件 client/src/extension.ts,啓動 LSP 服務
  3. 編寫 LSP 服務即 server/src/server.ts ,實現 LSP 協議

邏輯上,vscode 會在加載插件時根據 package.json 的配置判斷激活條件,以後加載、運行插件入口,啓動 LSP 服務器。插件啓動後,後續用戶在 vscode 的交互行爲會以標準事件,如 hover、completion、signature help 等方式觸發插件的 client ,client 再按照 LSP 協議轉發到 server 層。

下面咱們拆開看看三個模塊的細節。

入口配置

示例 vscode-extension-samples/lsp-sample 中的 package.json 有兩個關鍵配置:

{
    "activationEvents": [
        "onLanguage:plaintext"
    ],
    "main": "./client/out/extension",
}

其中:

  • activationEvents: 聲明插件的激活條件,代碼中的 onLanguage:plaintext 意爲打開 txt 文本文件時激活
  • main: 插件的入口文件

Client 樣例

示例 vscode-extension-samples/lsp-sample 中的 Client 入口代碼,關鍵部分以下:

export function activate(context: ExtensionContext) {
    // Server 配置信息
    const serverOptions: ServerOptions = {
        run: { 
            // Server 模塊的入口文件
            module: context.asAbsolutePath(
                path.join('server', 'out', 'server.js')
            ), 
            // 通信協議,支持 stdio、ipc、pipe、socket
            transport: TransportKind.ipc 
        },
    };

    // Client 配置
    const clientOptions: LanguageClientOptions = {
        // 與 packages.json 文件的 activationEvents 相似
        // 插件的激活條件
        documentSelector: [{ scheme: 'file', language: 'plaintext' }],
        // ...
    };

    // 使用 Server、Client 配置建立代理對象
    const client = new LanguageClient(
        'languageServerExample',
        'Language Server Example',
        serverOptions,
        clientOptions
    );

    client.start();
}

代碼脈絡很清晰,先是定義 Server、Client 配置對象,以後建立並啓動了 LanguageClient 實例。從實例能夠看到,Client 這一層能夠作的很薄,在 Node 環境下大部分轉發邏輯都被封裝在 LanguageClient 類中,開發者無需關心細節。

Server 樣例

示例 vscode-extension-samples/lsp-sample 中的 Server 代碼實現了錯誤診斷、代碼補全功能,做爲學習樣例來講稍顯複雜,因此我只摘抄出錯誤診斷部分的代碼:

// Server 層全部通信都使用 createConnection 建立的 connection 對象實現
const connection = createConnection(ProposedFeatures.all);

// 文檔對象管理器,提供文檔操做、監聽接口
// 匹配 Client 激活規則的文檔對象都會自動添加到 documents 對象中
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);

// 監聽文檔內容變動事件
documents.onDidChangeContent(change => {
    validateTextDocument(change.document);
});

// 校驗
async function validateTextDocument(textDocument: TextDocument): Promise<void> {
    const text = textDocument.getText();
    // 匹配全大寫的單詞
    const pattern = /\b[A-Z]{2,}\b/g;
    let m: RegExpExecArray | null;

    // 這裏判斷,若是一個單詞裏面全都是大寫字符,則報錯
    const diagnostics: Diagnostic[] = [];
    while ((m = pattern.exec(text))) {
        const diagnostic: Diagnostic = {
            severity: DiagnosticSeverity.Warning,
            range: {
                start: textDocument.positionAt(m.index),
                end: textDocument.positionAt(m.index + m[0].length)
            },
            message: `${m[0]} is all uppercase.`,
            source: 'ex'
        };
        diagnostics.push(diagnostic);
    }

    // 發送錯誤診斷信息
    // vscode 會自動完成錯誤提示渲染
    connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
}

LSP Server 代碼的主要流程:

  • 調用 createConnection 創建與 vscode 主進程的通信鏈路,後續全部的信息交互都基於 connection 對象實現。
  • 建立 documents 對象,並根據須要監聽文檔事件如上例中的 onDidChangeContent
  • 在事件回調中分析代碼內容,根據語言規則返回錯誤診斷信息,例如示例中使用正則判斷單詞是否所有爲大寫字母,是的話使用 connection.sendDiagnostics 接口發送錯誤提示信息

運行效果:

小結

通覽樣例代碼,LSP 客戶端服務器之間的通信過程都已經封裝在 LanguageClientconnection 等對象中,插件開發者並不須要關心底層實現細節,也不須要深刻理解 LSP 協議便可基於這些對象暴露的接口、事件等實現簡單的代碼高亮效果。

總結

Vscode 用插件方式提供了多種語言擴展接口,分聲明式、編程式兩類,在實際項目中一般會混合使用這兩種技術,用基於 TextMate 的聲明式接口迅速識別出代碼中的詞法;再用編程式接口如 LSP 補充提供諸如錯誤提示、代碼補齊、跳轉定義等高級功能。

這段時間看了很多開源 vscode 插件,其中 Vue 官方提供的 Vetur 插件學習是這方面的典型案例,學習價值極高,建議對這方面有興趣的讀者能夠自行前往分析學習 vscode 語言擴展類插件的寫法。

相關文章
相關標籤/搜索