Skip to content

语义高亮指南

语义高亮是对语法高亮的一种补充。VS Code 的主要分词引擎是 TextMate 语法器。TextMate 使用正则表达式,并根据词法规则将文件分割成一系列的符号。

语言服务器语言服务器插件模式中使用C/S结构的的服务器端,用于高消耗的特殊插件场景,如语言解析、智能提示等。与之相对,客户端则是普通的插件,两者通过VS Code 的API进行通信。将项目上下文的符号进行解析,使得语义服务器也能提供符号的相关信息。主题可以选择性地使用语义符号来提升语法高亮的体验。编辑器使用语义高亮优先于语法高亮。

下面是一个语义高亮的例子:

没有语义高亮:

no-semantic-highlighting

有语义高亮:

with-semantic-highlighting

注意下列颜色差异:

  • 第 10 行:languageMode 被着色为参数
  • 第 11 行:RangePosition 被着色为“类”,document 被着色为参数
  • 第 13 行:getFoldingRanges 着色为函数

语义分词供应器函数

要实现语义高亮,插件需要使用文档的语言类型或文件名注册一个 semantic token provider。编辑器发现需要进行语义分词时,会向该供应器供应器也叫提供者,一种微软主导的设计模式,类似于策略模式。主要用于服务的提供和注入,在 VS Code 插件开发中主要用于注册功能函数。发起请求。

typescript
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);
    // 第一行的1-5个词,是类型声明
    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' }; // 为所有本地的 Java 文档注册

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

语义分词供应器供应器也叫提供者,一种微软主导的设计模式,类似于策略模式。主要用于服务的提供和注入,在 VS Code 插件开发中主要用于注册功能函数。函数 API 为插件实现提供了两种方式:

  1. DocumentSemanticTokensProvider - 将整个文档作为输入内容
    • provideDocumentSemanticTokens - 为文档提供所有符号
    • provideDocumentSemanticTokensEdits - 对上次响应提供文档的所有符号
  2. DocumentRangeSemanticTokensProvider - 只作用于局部范围
    • provideDocumentRangeSemanticTokens - 为局部文档提供所有符号

每个被供应器供应器也叫提供者,一种微软主导的设计模式,类似于策略模式。主要用于服务的提供和注入,在 VS Code 插件开发中主要用于注册功能函数。函数返回的符号都会含有分类信息,分类信息中包含了符号的类型、修改的符号数量、符号的具体语言等。这个信息和 语法高亮 中的 TextMate 作用域生成的信息很像,不过它有自己专属的更干净的分类系统。

就如上例所示,这个供应器供应器也叫提供者,一种微软主导的设计模式,类似于策略模式。主要用于服务的提供和注入,在 VS Code 插件开发中主要用于注册功能函数。命名了类型和具体修改,这些东西随后就会被SemanticTokensLegend使用,provide api 将类型和具体修改作为下标返回给 legend。

语义分词种类

下面是一些VS Code 预定义的标准语义符号类型语义符号修饰器

标准类型和修饰器覆盖了很多语言中常见的概念,虽然很多语言都有自己的类型和修饰器术语,但是有了标准分类之后,主题开发者也能够根据这套标准实现跨语言的主题了。

标准语义符号类型:

Id描述
namespace声明或引用了一个命名空间、模块或包
class声明或引用了一个“类”类型
enum声明或引用了一个枚举类型
interface声明或引用了一个接口类型
struct声明或引用了一个结构类型
typeParameter声明或引用了一个类型参数
type声明或引用了一个未被上述类型提及的其他类型
parameter声明或引用了一个函数或方法的参数
variable声明或引用了一个本地或全局变量
property声明或引用了一个成员属性,成员域或成员变量
enumMember声明或引用了一个可枚举的属性、常量或成员
event声明或引用了一个可枚举的属性
function声明或引用了一个函数
method声明或引用了一个函数或方法
macro声明或引用了一个宏
label声明或引用了一个标签
comment表示注释
string表示字符串字面量
keyword表示语言关键字
number表示数字字面量
regexp表示正则表达式字面量
operator表示操作符

标准语义符号修饰符:

Id描述
declaration符号的声明
definition符号的定义,比如文件头
readonly只读变量或成员域,或称常量
static类成员中的静态成员对象
deprecated表示不再使用的符号
abstract表示类型或成员方法是抽象的
async被标记为异步的函数
modification被引用变量的原始赋值位置
documentation符号表示文档的位置
defaultLibrary符号表示标准库

必要之时,插件也可以定义新的类型和修饰符,或者通过semanticTokenTypessemanticTokenModifiers 配置点配置点package.json的一部分,用于配置插件启动命令、用户可更改的插件配置,可以理解为插件的主要配置文件。创建现有类型的子类型。

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

已配置的类型,可以声明它的父级类型,并继承其所有样式规则。

启用语义高亮

语义符号是计算出来的,是否高亮还取决于设置中的 editor.semanticHighlighting.enabled 是否开启。这个配置值有 truefalseconfiguredByTheme

  • truefalse 会开关所有主题中的语义高亮
  • configuredByTheme 默认值,它会让每个主题自己空值是否打开语义高亮。VS Code (比如 "Dark+")内的所有主题默认都开启了语义高亮。

语言插件可以通过 package.json 中的语言配置来覆盖默认语义高亮配置

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

主题化

主题化是指将颜色和样式应用到 符号 的过程。色彩主题确定了主题化的规则,但是用户也可以通过用户设置来自定义主题规则。

使用 semanticHighlighting 时,色彩主题可以告诉编辑器是否展示语义符号

如果启用的话,语义符号会优先于semanticTokenColors定义的语义符号规则使用:

json
{
  "semanticTokenColors": {
    "variable.readonly:java": "#ff0000"
  }
}

variable.readonly:java被称为选择器,它的格式是(*|tokenType)(.tokenModifier)*(:tokenLanguage)?

这是一个样式选择器的例子:

  • "*.declaration": { "fontStyle": "bold" } : 所有声明都标记为粗体
  • "class:java": { "foreground": "#00ff00" "fontStyle": "bold" } :java 中的类

如果没有匹配到任何规则,VS Code 会使用 语义分词作用域映射 将当前语义符号解析为 TextMate 的作用域。然后这个作用域会根据TextMate 主题规则中的 tokenColors 进行匹配。

语义分词作用域映射

为了使语义高亮在没有任何定义语义规则的主题中能够降级使用,VS Code 维护了一个映射表,将语义符号选择器映射到 TextMate 作用域。

语义符号选择器降级 TextMate Scope
namespaceentity.name.namespace
typeentity.name.type
type.defaultLibrarysupport.type
structstorage.type.struct
classentity.name.type.class
class.defaultLibrarysupport.class
interfaceentity.name.type.interface
enumentity.name.type.enum
functionentity.name.function
function.defaultLibrarysupport.function
methodentity.name.function.member
macroentity.name.function.macro
variablevariable.other.readwrite , entity.name.variable
variable.readonlyvariable.other.constant
variable.readonly.defaultLibrarysupport.constant
parametervariable.parameter
propertyvariable.other.property
property.readonlyvariable.other.constant.property
enumMembervariable.other.enummember
eventvariable.other.event

这份表可通过semanticTokenScopes配置点配置点package.json的一部分,用于配置插件启动命令、用户可更改的插件配置,可以理解为插件的主要配置文件。进行扩展。

我们来看两个例子

  • 当主题未定义语义符号的主题规则时,插件定义了自定义的符号类型和符号修饰器给 TextMate 作用域作为降级方案。
json
{
  "contributes": {
    "semanticTokenScopes": [
      {
        "scopes": {
          "templateType": ["entity.name.type.template"]
        }
      }
    ]
  }
}
  • TextMate 的供应器供应器也叫提供者,一种微软主导的设计模式,类似于策略模式。主要用于服务的提供和注入,在 VS Code 插件开发中主要用于注册功能函数。函数可以添加特定语言作用域,这样主题就可以针对特对语言实现具体的主题规则。
json
{
  "contributes": {
    "semanticTokenScopes": [
      {
        "language": "typescript",
        "scopes": {
          "property.readonly": ["variable.other.constant.property.ts"]
        }
      }
    ]
  }
}

试试看

我们有一份 语义符号示例 展示了如何创建一个语义符号供应器供应器也叫提供者,一种微软主导的设计模式,类似于策略模式。主要用于服务的提供和注入,在 VS Code 插件开发中主要用于注册功能函数。函数。

作用域检查器 可以帮你查看一份源文件中展示了哪些语义符号,以及他们应用了什么规则。想要看到语义符号,你可以打开一个 TyepScript 文件,然后使用内置主题(比如 Dark+)。