🚀 在 VS Code 中取得

語法突顯指南

語法突顯決定了 Visual Studio Code 編輯器中顯示的原始碼色彩和樣式。它負責將 JavaScript 中的關鍵字 (如 iffor) 以不同於字串、註解和變數名稱的方式著色。

語法突顯有兩個組成部分

  • 符號化:將文字分解為符號清單
  • 佈景主題化:使用佈景主題或使用者設定將符號對應到特定色彩和樣式

在深入探討細節之前,一個好的開始是使用作用域檢視器工具,並探索原始碼檔案中存在哪些符號,以及它們符合哪些佈景主題規則。若要查看語意和語法符號,請在 TypeScript 檔案上使用內建佈景主題 (例如 Dark+)。

符號化

文字的符號化是將文字分解成區段,並使用符號類型對每個區段進行分類。

VS Code 的符號化引擎由 TextMate 語法 提供支援。TextMate 語法是規則運算式的結構化集合,並以 plist (XML) 或 JSON 檔案的形式撰寫。VS Code 擴充功能可以透過 grammars 貢獻點貢獻語法。

TextMate 符號化引擎在與轉譯器相同的程序中執行,並且符號會隨著使用者輸入而更新。符號用於語法突顯,但也用於將原始碼分類到註解、字串、規則運算式等區域。

從 1.43 版開始,VS Code 也允許擴充功能透過 語意符號提供者 提供符號化。語意提供者通常由語言伺服器實作,這些伺服器對原始碼檔案有更深入的了解,並且可以在專案的內容中解析符號。例如,常數變數名稱可以在整個專案中使用常數突顯來呈現,而不僅僅是在其宣告位置。

基於語意符號的突顯被認為是對基於 TextMate 的語法突顯的補充。語意突顯位於語法突顯之上。由於語言伺服器可能需要一段時間才能載入和分析專案,因此語意符號突顯可能會在短暫延遲後出現。

本文重點介紹基於 TextMate 的符號化。語意符號化和佈景主題化在語意突顯指南中說明。

TextMate 語法

VS Code 使用 TextMate 語法 作為語法符號化引擎。它們是為 TextMate 編輯器發明的,由於開放原始碼社群建立和維護的大量語言套件,它們已被許多其他編輯器和 IDE 採用。

TextMate 語法依賴 Oniguruma 規則運算式,並且通常以 plist 或 JSON 形式撰寫。您可以在這裡找到 TextMate 語法的良好介紹,並且您可以查看現有的 TextMate 語法,以了解更多關於它們如何運作的資訊。

TextMate 符號和作用域

符號是一個或多個字元,它們是同一個程式元素的一部分。範例符號包括運算子 (例如 +*)、變數名稱 (例如 myVar) 或字串 (例如 "my string")。

每個符號都與一個作用域相關聯,該作用域定義了符號的內容。作用域是以點分隔的識別碼清單,用於指定目前符號的內容。例如,JavaScript 中的 + 運算具有作用域 keyword.operator.arithmetic.js

佈景主題將作用域對應到色彩和樣式,以提供語法突顯。TextMate 提供了許多佈景主題針對的常見作用域清單。為了讓您的語法獲得盡可能廣泛的支援,請嘗試以現有的作用域為基礎,而不是定義新的作用域。

作用域會巢狀結構化,因此每個符號也與父作用域清單相關聯。下面的範例使用作用域檢視器來顯示簡單 JavaScript 函式中 + 運算子的作用域階層。最特定的作用域列在最上面,而更一般的父作用域列在下面

syntax highlighting scopes

父作用域資訊也用於佈景主題化。當佈景主題針對作用域時,所有具有該父作用域的符號都將被著色,除非佈景主題也為其個別作用域提供了更特定的著色。

貢獻基本語法

VS Code 支援 json TextMate 語法。這些是透過 grammars 貢獻點 貢獻的。

每個語法貢獻都指定:語法套用的語言識別碼、語法符號的頂層作用域名稱,以及語法檔案的相對路徑。下面的範例顯示了虛構的 abc 語言的語法貢獻

{
  "contributes": {
    "languages": [
      {
        "id": "abc",
        "extensions": [".abc"]
      }
    ],
    "grammars": [
      {
        "language": "abc",
        "scopeName": "source.abc",
        "path": "./syntaxes/abc.tmGrammar.json"
      }
    ]
  }
}

語法檔案本身由頂層規則組成。這通常分為 patterns 區段,其中列出了程式的頂層元素,以及 repository,其中定義了每個元素。語法中的其他規則可以使用 { "include": "#id" } 參考 repository 中的元素。

範例 abc 語法將字母 abc 標記為關鍵字,並將括號的巢狀結構標記為運算式。

{
  "scopeName": "source.abc",
  "patterns": [{ "include": "#expression" }],
  "repository": {
    "expression": {
      "patterns": [{ "include": "#letter" }, { "include": "#paren-expression" }]
    },
    "letter": {
      "match": "a|b|c",
      "name": "keyword.letter"
    },
    "paren-expression": {
      "begin": "\\(",
      "end": "\\)",
      "beginCaptures": {
        "0": { "name": "punctuation.paren.open" }
      },
      "endCaptures": {
        "0": { "name": "punctuation.paren.close" }
      },
      "name": "expression.group",
      "patterns": [{ "include": "#expression" }]
    }
  }
}

語法引擎將嘗試連續將 expression 規則套用到文件中的所有文字。對於像這樣的簡單程式

a
(
    b
)
x
(
    (
        c
        xyz
    )
)
(
a

範例語法產生以下作用域 (從最特定到最不特定作用域從左到右列出)

a               keyword.letter, source.abc
(               punctuation.paren.open, expression.group, source.abc
    b           keyword.letter, expression.group, source.abc
)               punctuation.paren.close, expression.group, source.abc
x               source.abc
(               punctuation.paren.open, expression.group, source.abc
    (           punctuation.paren.open, expression.group, expression.group, source.abc
        c       keyword.letter, expression.group, expression.group, source.abc
        xyz     expression.group, expression.group, source.abc
    )           punctuation.paren.close, expression.group, expression.group, source.abc
)               punctuation.paren.close, expression.group, source.abc
(               punctuation.paren.open, expression.group, source.abc
a               keyword.letter, expression.group, source.abc

請注意,未被其中一個規則比對的文字 (例如字串 xyz) 包含在目前的作用域中。檔案結尾的最後一個括號是 expression.group 的一部分,即使未比對 end 規則,因為在 end 規則之前找到了 end-of-document

嵌入式語言

如果您的語法包含父語言內的嵌入式語言 (例如 HTML 中的 CSS 樣式區塊),您可以使用 embeddedLanguages 貢獻點來告知 VS Code 將嵌入式語言視為與父語言不同。這可確保括號比對、註解和其他基本語言功能在嵌入式語言中如預期般運作。

embeddedLanguages 貢獻點將嵌入式語言中的作用域對應到頂層語言作用域。在下面的範例中,meta.embedded.block.javascript 作用域中的任何符號都將被視為 JavaScript 內容

{
  "contributes": {
    "grammars": [
      {
        "path": "./syntaxes/abc.tmLanguage.json",
        "scopeName": "source.abc",
        "embeddedLanguages": {
          "meta.embedded.block.javascript": "javascript"
        }
      }
    ]
  }
}

現在,如果您嘗試在標記為 meta.embedded.block.javascript 的一組符號內註解程式碼或觸發程式碼片段,它們將獲得正確的 // JavaScript 樣式註解和正確的 JavaScript 程式碼片段。

開發新的語法擴充功能

若要快速建立新的語法擴充功能,請使用 VS Code 的 Yeoman 範本 執行 yo code 並選取 New Language 選項

Selecting the 'new language' template in 'yo code'

Yeoman 將引導您完成一些基本問題,以搭建新的擴充功能。建立新語法的重要問題是

  • Language id - 您的語言的唯一識別碼。
  • Language name - 您的語言的人類可讀名稱。
  • Scope names - 您的語法的根 TextMate 作用域名稱。

Filling in the 'new language' questions

產生器假設您想要為該語言定義新的語言和新的語法。如果您要為現有語言建立語法,只需填寫目標語言的資訊,並務必刪除產生的 package.json 中的 languages 貢獻點。

回答完所有問題後,Yeoman 將建立一個具有以下結構的新擴充功能

A new language extension

請記住,如果您要為 VS Code 已經知道的語言貢獻語法,請務必刪除產生的 package.json 中的 languages 貢獻點。

轉換現有的 TextMate 語法

yo code 也可以協助將現有的 TextMate 語法轉換為 VS Code 擴充功能。同樣,從執行 yo code 並選取 Language extension 開始。當詢問現有的語法檔案時,請提供 .tmLanguage.json TextMate 語法檔案的完整路徑

Converting an existing TextMate grammar

使用 YAML 撰寫語法

隨著語法變得越來越複雜,以 json 形式理解和維護它可能會變得困難。如果您發現自己正在撰寫複雜的規則運算式或需要新增註解來解釋語法的各個方面,請考慮使用 yaml 來定義您的語法。

Yaml 語法與基於 json 的語法具有完全相同的結構,但允許您使用 yaml 更簡潔的語法,以及多行字串和註解等功能。

A yaml grammar using multiline strings and comments

VS Code 只能載入 json 語法,因此基於 yaml 的語法必須轉換為 json。js-yaml 套件 和命令列工具使這變得容易。

# Install js-yaml as a development only dependency in your extension
$ npm install js-yaml --save-dev

# Use the command-line tool to convert the yaml grammar to json
$ npx js-yaml syntaxes/abc.tmLanguage.yaml > syntaxes/abc.tmLanguage.json

注入語法

注入語法可讓您擴充現有的語法。注入語法是常規 TextMate 語法,它被注入到現有語法中的特定作用域內。注入語法的範例應用

  • 突顯註解中的關鍵字,例如 TODO
  • 為現有語法新增更具體的作用域資訊。
  • 為 Markdown 圍籬式程式碼區塊新增新語言的突顯。

建立基本注入語法

注入語法是透過 package.json 貢獻的,就像常規語法一樣。但是,注入語法不是指定 language,而是使用 injectTo 來指定要將語法注入到的目標語言作用域清單。

對於此範例,我們將建立一個簡單的注入語法,以突顯 JavaScript 註解中的 TODO 作為關鍵字。若要在 JavaScript 檔案中套用我們的注入語法,我們在 injectTo 中使用 source.js 目標語言作用域

{
  "contributes": {
    "grammars": [
      {
        "path": "./syntaxes/injection.json",
        "scopeName": "todo-comment.injection",
        "injectTo": ["source.js"]
      }
    ]
  }
}

語法本身是標準 TextMate 語法,除了頂層 injectionSelector 條目。injectionSelector 是一個作用域選取器,用於指定應在哪些作用域中套用注入的語法。對於我們的範例,我們想要突顯所有 // 註解中的單字 TODO。使用作用域檢視器,我們發現 JavaScript 的雙斜線註解具有作用域 comment.line.double-slash,因此我們的注入選取器是 L:comment.line.double-slash

{
  "scopeName": "todo-comment.injection",
  "injectionSelector": "L:comment.line.double-slash",
  "patterns": [
    {
      "include": "#todo-keyword"
    }
  ],
  "repository": {
    "todo-keyword": {
      "match": "TODO",
      "name": "keyword.todo"
    }
  }
}

注入選取器中的 L: 表示注入會新增到現有語法規則的左側。這基本上表示我們的注入語法的規則將在任何現有語法規則之前套用。

嵌入式語言

注入語法也可以為其父語法貢獻嵌入式語言。就像常規語法一樣,注入語法可以使用 embeddedLanguages 將嵌入式語言中的作用域對應到頂層語言作用域。

例如,一個突顯 JavaScript 字串中 SQL 查詢的擴充功能可以使用 embeddedLanguages 來確保標記為 meta.embedded.inline.sql 的字串內的所有符號都被視為 SQL,以用於括號比對和程式碼片段選取等基本語言功能。

{
  "contributes": {
    "grammars": [
      {
        "path": "./syntaxes/injection.json",
        "scopeName": "sql-string.injection",
        "injectTo": ["source.js"],
        "embeddedLanguages": {
          "meta.embedded.inline.sql": "sql"
        }
      }
    ]
  }
}

符號類型和嵌入式語言

對於注入語言嵌入式語言,還有一個額外的複雜問題:預設情況下,VS Code 將字串內的所有符號視為字串內容,並將具有註解的所有符號視為符號內容。由於括號比對和自動關閉配對等功能在字串和註解內被停用,因此如果嵌入式語言出現在字串或註解內,這些功能也將在嵌入式語言中被停用。

若要覆寫此行為,您可以使用 meta.embedded.* 作用域來重設 VS Code 將符號標記為字串或註解內容。最好始終將嵌入式語言包裝在 meta.embedded.* 作用域中,以確保 VS Code 正確處理嵌入式語言。

如果您無法將 meta.embedded.* 作用域新增到您的語法,您可以選擇在語法的貢獻點中使用 tokenTypes 將特定作用域對應到內容模式。下面的 tokenTypes 區段確保 my.sql.template.string 作用域中的任何內容都被視為原始碼

{
  "contributes": {
    "grammars": [
      {
        "path": "./syntaxes/injection.json",
        "scopeName": "sql-string.injection",
        "injectTo": ["source.js"],
        "embeddedLanguages": {
          "my.sql.template.string": "sql"
        },
        "tokenTypes": {
          "my.sql.template.string": "other"
        }
      }
    ]
  }
}

佈景主題

佈景主題化是將色彩和樣式指派給符號。佈景主題規則在色彩佈景主題中指定,但使用者可以在使用者設定中自訂佈景主題規則。

TextMate 佈景主題規則在 tokenColors 中定義,並且具有與常規 TextMate 佈景主題相同的語法。每個規則都定義一個 TextMate 作用域選取器以及產生的色彩和樣式。

在評估符號的色彩和樣式時,會將目前符號的作用域與規則的選取器比對,以找到每個樣式屬性 (前景、粗體、斜體、底線) 最特定的規則

色彩佈景主題指南 說明如何建立色彩佈景主題。語意符號的佈景主題化在語意突顯指南中說明。

作用域檢視器

VS Code 的內建作用域檢視器工具可協助偵錯語法和語意符號。它會顯示檔案中目前位置的符號和語意符號的作用域,以及有關哪些佈景主題規則套用於該符號的中繼資料。

從命令面板使用 Developer: Inspect Editor Tokens and Scopes 命令觸發作用域檢視器,或為其建立鍵盤快速鍵

{
  "key": "cmd+alt+shift+i",
  "command": "editor.action.inspectTMScopes"
}

scope inspector

作用域檢視器顯示以下資訊

  1. 目前的符號。
  2. 有關符號的中繼資料以及有關其計算外觀的資訊。如果您正在使用嵌入式語言,則此處的重要條目是 languagetoken type
  3. 當語意符號提供者可用於目前語言且目前佈景主題支援語意突顯時,會顯示語意符號區段。它會顯示目前的語意符號類型和修飾詞,以及符合語意符號類型和修飾詞的佈景主題規則。
  4. TextMate 區段顯示目前 TextMate 符號的作用域清單,其中最特定的作用域位於頂部。它也顯示符合作用域的最特定佈景主題規則。這僅顯示負責符號目前樣式的佈景主題規則,它不顯示覆寫的規則。如果存在語意符號,則僅當佈景主題規則與符合語意符號的規則不同時,才會顯示佈景主題規則。