在 VS Code 中试试

语义高亮指南

语义高亮是对语法高亮指南中所述的语法高亮的补充。Visual Studio Code 使用 TextMate 语法作为主要的标记化引擎。TextMate 语法以单个文件作为输入,并根据正则表达式表示的词法规则对其进行分解。

语义标记化允许语言服务器根据其在项目上下文中如何解析符号的知识来提供额外的令牌信息。主题可以选择使用语义令牌来改进和优化语法的高亮显示。编辑器在语法高亮的基础上应用语义令牌的高亮显示。

这是一个语义高亮可以添加的示例

未启用语义高亮

without semantic highlighting

启用语义高亮

with semantic highlighting

注意基于语言服务符号理解的颜色差异

  • 第 10 行:languageModes 被着色为参数
  • 第 11 行:RangePosition 被着色为类,document 被着色为参数。
  • 第 13 行:getFoldingRanges 被着色为函数。

语义令牌提供程序

要实现语义高亮,语言扩展可以根据文档语言和/或文件名注册一个 semantic token provider(语义令牌提供程序)。当需要语义令牌时,编辑器将向这些提供程序发出请求。

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> {
    // analyze the document and return semantic tokens

    const tokensBuilder = new vscode.SemanticTokensBuilder(legend);
    // on line 1, characters 1-5 are a class declaration
    tokensBuilder.push(
      new vscode.Range(new vscode.Position(1, 1), new vscode.Position(1, 5)),
      'class',
      ['declaration']
    );
    return tokensBuilder.build();
  }
};

const selector = { language: 'java', scheme: 'file' }; // register for all Java documents from the local file system

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

语义令牌提供程序 API 有两种类型,以适应语言服务器的功能

  • DocumentSemanticTokensProvider - 始终将完整文档作为输入。

    • provideDocumentSemanticTokens - 提供文档中的所有令牌。
    • provideDocumentSemanticTokensEdits - 提供文档中的所有令牌,作为对先前响应的增量。
  • DocumentRangeSemanticTokensProvider - 仅作用于某个范围。

    • provideDocumentRangeSemanticTokens - 提供文档范围内的所有令牌。

提供程序返回的每个令牌都带有一个分类,该分类包括一个令牌类型、任意数量的令牌修饰符以及一个令牌语言。

如上例所示,提供程序在 SemanticTokensLegend 中指定了它将使用的类型和修饰符。这允许 provide API 将令牌类型和修饰符作为图例的索引返回。

语义令牌分类

语义令牌提供程序的输出由令牌组成。每个令牌都有一个范围和一个令牌分类,用于描述该令牌表示哪种语法元素。如果令牌是嵌入语言的一部分,分类还可以选择性地指定一种语言。

为了描述语法元素的类型,使用了语义令牌类型和修饰符。这些信息类似于语法高亮指南中描述的 TextMate 作用域,但我们希望提出一个专用且更清晰的分类系统。

VS Code 提供了一组标准的语义令牌类型和修饰符供所有语义令牌提供程序使用。此外,语义令牌提供程序可以自由定义新的类型和修饰符,并创建标准类型的子类型。

标准令牌类型和修饰符

标准类型和修饰符涵盖了许多语言使用的通用概念。虽然每种语言可能对某些类型和修饰符使用不同的术语,但通过遵循标准分类,主题作者将能够定义适用于各种语言的主题规则。

以下是 VS Code 预定义的标准语义令牌类型和语义令牌修饰符

标准令牌类型

ID 描述
namespace(命名空间) 用于声明或引用命名空间、模块或包的标识符。
class(类) 用于声明或引用类类型的标识符。
enum(枚举) 用于声明或引用枚举类型的标识符。
interface(接口) 用于声明或引用接口类型的标识符。
struct(结构体) 用于声明或引用结构体类型的标识符。
typeParameter(类型参数) 用于声明或引用类型参数的标识符。
type(类型) 用于声明或引用上述未涵盖的类型的标识符。
parameter(参数) 用于声明或引用函数或方法参数的标识符。
variable(变量) 用于声明或引用局部或全局变量的标识符。
property(属性) 用于声明或引用成员属性、成员字段或成员变量的标识符。
enumMember(枚举成员) 用于声明或引用枚举属性、常量或成员的标识符。
decorator(装饰器) 用于声明或引用装饰器和注解的标识符。
event(事件) 用于声明事件属性的标识符。
function(函数) 用于声明函数的标识符。
method(方法) 用于声明成员函数或方法的标识符。
macro(宏) 用于声明宏的标识符。
label(标签) 用于声明标签的标识符。
comment(注释) 表示注释的令牌。
string(字符串) 表示字符串字面量的令牌。
keyword(关键字) 表示语言关键字的令牌。
number(数字) 表示数字字面量的令牌。
regexp(正则表达式) 表示正则表达式字面量的令牌。
operator(运算符) 表示运算符的令牌。

标准令牌修饰符

ID 描述
declaration(声明) 用于符号的声明。
definition(定义) 用于符号的定义,例如在头文件中。
readonly(只读) 用于只读变量和成员字段(常量)。
static(静态) 用于类成员(静态成员)。
deprecated(已弃用) 用于不应再使用的符号。
abstract(抽象) 用于抽象的类型和成员函数。
async(异步) 用于标记为异步的函数。
modification(修改) 用于变量被赋值的变量引用。
documentation(文档) 用于符号在文档中的出现。
defaultLibrary(默认库) 用于属于标准库的符号。

除了标准类型和修饰符外,VS Code 还定义了类型和修饰符与类似 TextMate 作用域的映射。这在语义令牌作用域映射部分进行了介绍。

自定义令牌类型和修饰符

如有必要,扩展可以在其扩展的 package.json 文件中通过 semanticTokenTypessemanticTokenModifiers 贡献点声明新的类型和修饰符,或创建现有类型的子类型

{
  "contributes": {
    "semanticTokenTypes": [
      {
        "id": "templateType",
        "superType": "type",
        "description": "A template type."
      }
    ],
    "semanticTokenModifiers": [
      {
        "id": "native",
        "description": "Annotates a symbol that is implemented natively"
      }
    ]
  }
}

在上面的示例中,一个扩展声明了一个新的类型 templateType 和一个新的修饰符 native。通过将 type 指定为超类型,针对 type 的主题样式规则也将应用于 templateType

{
  "name": "Red Theme",
  "semanticTokenColors": {
    "type": "#ff0011"
  }
}

上面显示的 semanticTokenColors"#ff0011" 同时适用于 type 及其所有子类型,包括 templateType

除了自定义令牌类型外,扩展还可以定义如何将这些类型映射到 TextMate 作用域。这在自定义映射部分进行了描述。请注意,自定义映射规则不会自动从超类型继承。相反,子类型需要重新定义映射,最好映射到更具体的作用域。

启用语义高亮

是否计算和高亮显示语义令牌由设置 editor.semanticHighlighting.enabled 决定。它可以取值 truefalseconfiguredByTheme

  • truefalse 为所有主题开启或关闭语义高亮。
  • configuredByTheme 是默认值,它允许每个主题控制是否启用语义高亮。VS Code 附带的所有主题(例如,“Dark+”默认主题)默认都启用了语义高亮。

依赖于语义令牌的语言扩展可以在其 package.json 文件中覆盖其语言的默认设置

{
  "configurationDefaults": {
    "[languageId]": {
      "editor.semanticHighlighting.enabled": true
    }
  }
}

主题

主题化是关于为令牌分配颜色和样式。主题化规则在颜色主题文件(JSON 格式)中指定。用户也可以在用户设置中自定义主题化规则。

颜色主题中的语义着色

颜色主题文件格式中添加了两个新属性,以支持基于语义令牌的高亮显示。

semanticHighlighting 属性定义了主题是否准备好使用语义令牌进行高亮显示。默认情况下它为 false,但我们鼓励所有主题启用它。当设置 editor.semanticHighlighting.enabled 设置为 configuredByTheme 时,将使用此属性。

semanticTokenColors 属性允许主题定义新的着色规则,这些规则与语义令牌提供程序发出的语义令牌类型和修饰符相匹配。

{
  "name": "Red Theme",
  "tokenColors": [
    {
      "scope": "comment",
      "settings": {
        "foreground": "#dd0000",
        "fontStyle": "italic"
      }
    }
  ],
  "semanticHighlighting": true,
  "semanticTokenColors": {
    "variable.readonly:java": "#ff0011"
  }
}

variable.readonly:java 被称为选择器,其形式为 (*|tokenType)(.tokenModifier)*(:tokenLanguage)?

如果规则匹配,该值描述了样式。它可以是一个字符串,表示前景色,或者一个对象,形式为 { foreground: string, bold: boolean, italic: boolean, underline: boolean }{ foreground: string, fontStyle: string },就像在 tokenColors 中用于 TextMate 主题规则一样。

前景色需要遵循颜色格式中描述的颜色格式。不支持透明度。

以下是选择器和样式的其他示例

  • "*.declaration": { "bold": true } // 所有声明都加粗
  • "class:java": { "foreground": "#0f0", "italic": true } // Java 中的类

如果没有规则匹配,或者主题没有 semanticTokenColors 部分(但启用了 semanticHighlighting),VS Code 会使用语义令牌作用域映射来评估给定语义令牌的 TextMate 作用域。该作用域将与主题在 tokenColors 中的 TextMate 主题规则进行匹配。

语义令牌作用域映射

为了让没有定义任何特定语义规则的主题也能使用语义高亮,并作为自定义令牌类型和修饰符的后备方案,VS Code 维护了一个从语义令牌选择器到 TextMate 作用域的映射。

如果主题启用了语义高亮,但没有包含针对给定语义令牌的规则,则会使用这些 TextMate 作用域来查找 TextMate 主题规则。

预定义的 TextMate 作用域映射

下表列出了当前预定义的映射。

语义令牌选择器 后备 TextMate 作用域
namespace(命名空间) entity.name.namespace
type(类型) entity.name.type
type.defaultLibrary support.type
struct(结构体) storage.type.struct
class(类) entity.name.type.class
class.defaultLibrary support.class
interface(接口) entity.name.type.interface
enum(枚举) entity.name.type.enum
function(函数) entity.name.function
function.defaultLibrary support.function
method(方法) entity.name.function.member
macro(宏) entity.name.function.preprocessor
variable(变量) variable.other.readwrite , entity.name.variable
variable.readonly variable.other.constant
variable.readonly.defaultLibrary support.constant
parameter(参数) variable.parameter
property(属性) variable.other.property
property.readonly variable.other.constant.property
enumMember(枚举成员) variable.other.enummember
event(事件) variable.other.event

自定义 TextMate 作用域映射

扩展可以通过在其 package.json 中使用 semanticTokenScopes 贡献点来扩展此映射。

扩展这样做有两个用例

  • 定义自定义令牌类型和令牌修饰符的扩展提供 TextMate 作用域作为后备方案,当主题没有为添加的语义令牌类型或修饰符定义主题规则时使用

    {
      "contributes": {
        "semanticTokenScopes": [
          {
            "scopes": {
              "templateType": ["entity.name.type.template"]
            }
          }
        ]
      }
    }
    
  • TextMate 语法提供程序可以描述特定于语言的作用域。这有助于包含特定于语言的主题规则的主题。

    {
      "contributes": {
        "semanticTokenScopes": [
          {
            "language": "typescript",
            "scopes": {
              "property.readonly": ["variable.other.constant.property.ts"]
            }
          }
        ]
      }
    }
    

试试看

我们有一个语义令牌示例,它展示了如何创建一个语义令牌提供程序。

>作用域检查器工具允许您探索源文件中存在哪些语义令牌以及它们匹配哪些主题规则。要查看语义令牌,请在 TypeScript 文件上使用内置主题(例如,Dark+)。