Maison  >  Article  >  outils de développement  >  Analyse approfondie des principes de mise en évidence du code VSCode

Analyse approfondie des principes de mise en évidence du code VSCode

青灯夜游
青灯夜游avant
2021-06-08 11:12:243664parcourir

Cet article vous donnera une analyse approfondie des principes de mise en évidence du code VSCode. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il sera utile à tout le monde.

Analyse approfondie des principes de mise en évidence du code VSCode

Le texte intégral contient 5 000 mots, expliquant le code mettant en évidence le principe de mise en œuvre derrière vscode. Bienvenue pour aimer, suivre et transmettre.

La mise en évidence du code, la complétion du code, le diagnostic d'erreur, la définition de saut et d'autres fonctions linguistiques de Vscode sont implémentées de manière collaborative par deux solutions d'extension, notamment :

  • Basé sur la technologie d'analyse lexicale, reconnaître les jetons de segmentation de mots et appliquer des styles de surbrillance
  • Basé sur l'interface des fonctionnalités de langage programmable, reconnaître la sémantique du code et appliquer des styles de surbrillance. En outre, il peut également implémenter des fonctions telles que le diagnostic d'erreur, les invites intelligentes, le formatage, etc.

[Etude recommandée : "Tutoriel vscode"]

La portée fonctionnelle des deux solutions augmente progressivement, et par conséquent la complexité technique et le coût de mise en œuvre augmente également étape par étape.Cet article présentera brièvement le processus de travail et les caractéristiques des deux solutions, le travail que chacune accomplit, comment elles s'écrivent mutuellement, et combiné avec des cas réels pour révéler étape par étape le principe de mise en œuvre de la mise en évidence du code vscode. fonction :

Analyse approfondie des principes de mise en évidence du code VSCode

Bases du plug-in Vscode

Avant d'introduire le principe de mise en évidence du code vscode, il est nécessaire de se familiariser avec l'architecture sous-jacente de vscode. Semblable à Webpack, vscode lui-même n'implémente qu'un ensemble d'étagères. Les commandes, styles, statut, débogage et autres fonctions à l'intérieur de l'étagère sont tous fournis sous la forme de plug-ins. vscode fournit cinq capacités d'extension externes :

Analyse approfondie des principes de mise en évidence du code VSCode

Parmi eux, la fonction de mise en évidence du code est implémentée par le plug-in

extension de langage Selon la méthode d'implémentation, elle peut être subdivisée en :

    Déclaratif
  •  : utilisez une structure JSON spécifique pour déclarer un ensemble de correspondances lexicales régulières. Vous pouvez ajouter des fonctionnalités de langage telles que la correspondance au niveau des blocs, l'indentation automatique, la coloration syntaxique, etc. sans écrire de code logique. . Les extensions/css, extensions/html et autres plug-ins intégrés de vscode sont tous
  • programmatiques
  • implémentés sur la base d'une interface déclarative : vscode surveillera le comportement de l'utilisateur pendant l'exécution et déclenchera un événement. les rappels après que des comportements spécifiques se produisent. Les extensions du langage de programmation doivent surveiller ces événements et effectuer une analyse dynamique du contenu du texte et des informations de code de retour dans un format spécifique
  • Le style déclaratif a des performances élevées mais le style de programmation est faible ; performances mais fortes capacités. Les développeurs de plug-ins de langage peuvent généralement utiliser une combinaison de méthodes, en utilisant des interfaces déclaratives pour identifier les jetons lexicaux dans les plus brefs délais et fournir des fonctions de mise en évidence de la syntaxe de base, puis utiliser des interfaces de programmation pour analyser dynamiquement le contenu et fournir des fonctionnalités plus avancées telles que le diagnostic des erreurs ; , invites intelligentes, etc.

L'extension du langage déclaratif dans Vscode est implémentée sur la base du moteur d'analyse lexicale TextMate ; l'extension du langage de programmation est implémentée sur la base de l'interface d'analyse sémantique, de l'interface

et du protocole Language Server. présenté ci-dessous. La logique de base du système.

vscode.language.*

La mise en évidence lexicale

L'analyse lexicale

est la conversion de séquences de caractères en jetons, et tag (jeton) est la plus petite unité qui constitue le code source. La technologie d'analyse lexicale est largement utilisée dans la compilation, l'IDE et d'autres domaines. Par exemple, le moteur lexical de vscode analyse la séquence de jetons puis applique le style de surbrillance en fonction du type de jeton. Ce processus peut être simplement divisé en deux étapes : la segmentation des mots et l'application du style.

Référence :

https://macromates.com/manual/en/langue%5C_grammars


  • https : / /code.visualstudio.com/api/lingual-extensions/syntax-highlight-guide
Segmentation des mots

L'essence de processus de segmentation de mots La méthode ci-dessus décompose de manière récursive une longue chaîne de codes en fragments de chaîne avec des significations et des classifications spécifiques, tels que des opérateurs tels que  ; des mots-clés tels que

 ; >, etc., ce qui est simple. Il s'agit d'identifier où se trouve un certain mot dans un morceau de texte.

+-*/%L'analyse lexicale de Vscode est basée sur le moteur var/constTextMate1234. Les fonctions sont relativement complexes et peuvent être simplement divisées en trois aspects : la segmentation de mots basée sur des bases régulières, les règles de segmentation de mots composés et les mots imbriqués. règles de segmentation. "tecvan"

基本规则

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 类型,但无法识别其它关键字:

Analyse approfondie des principes de mise en évidence du code VSCode

在 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></style> 语句,end 用于匹配 语句,且 <style></style> 整个语句被赋予 scope 为 tag.style.vue 。此外,语句中字符被 beginCapturesendCaptures 属性分配成不同的 scope 类型:

Analyse approfondie des principes de mise en évidence du code VSCode

这里从 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 等样式属性。

实例解析

看完原理我们来拆解一个实际案例: github.com/mrmlnc/vsco…json5 是 JSON 扩展协议,旨在使人类更易于手动编写和维护,支持备注、单引号、十六进制数字等特性,这些拓展特性需要使用 vscode-json5 插件实现高亮效果:

Analyse approfondie des principes de mise en évidence du code VSCode

上图中,左边是没有启动 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 命令并回车:

Analyse approfondie des principes de mise en évidence du code VSCode

命令运行后就可以看到分词 token 的语言、scope、样式等信息。

编程式语言扩展

词法分析引擎 TextMate 本质上是一种基于正则的静态词法分析器,优点是接入方式标准化,成本低且运行效率较高,缺点是静态代码分析很难实现某些上下文相关的 IDE 功能,例如对于下面的代码:

Analyse approfondie des principes de mise en évidence du code VSCode

注意代码第一行函数参数 languageModes 与第二行函数体内的 languageModes 是同一实体但是没有实现相同的样式,视觉上没有形成联动。

为此,vscode 在 TextMate 引擎之外提供了三种更强大也更复杂的语言特性扩展机制:

  • 使用 DocumentSemanticTokensProvider 实现可编程的语义分析
  • 使用 vscode.languages.* 下的接口监听各类编程行为事件,在特定时间节点实现语义分析
  • 根据 Language Server Protocol 协议实现一套完备的语言特性分析服务器

相比于上面介绍的声明式的词法高亮,语言特性接口更灵活,能够实现诸如错误诊断、候选词、智能提示、定义跳转等高级功能。

参考资料:

  • https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide
  • https://code.visualstudio.com/api/language-extensions/programmatic-language-features
  • https://code.visualstudio.com/api/language-extensions/language-server-extension-guide

DocumentSemanticTokensProvider 分词

简介

Sematic Tokens Provider 是 vscode 内置的一种对象协议,它需要自行扫描代码文件内容,然后以整数数组形式返回语义 token 序列,告诉 vscode 在文件的哪一行、那一列、多长的区间内是一个什么类型的 token。

注意区分一下,TextMate 中的扫描是引擎驱动的,逐行匹配正则,而 Sematic Tokens Provider 场景下扫描规则、匹配规则都交由插件开发者自行实现,灵活性增强但相对的开发成本也会更高。

实现上,Sematic Tokens Providervscode.DocumentSemanticTokensProvider 接口定义,开发者可以按需实现两个方法:

  • provideDocumentSemanticTokens :全量分析代码文件语义
  • provideDocumentSemanticTokensEdits :增量分析正在编辑模块的语义

我们来看个完整的示例:

import * as vscode from &#39;vscode&#39;;

const tokenTypes = [&#39;class&#39;, &#39;interface&#39;, &#39;enum&#39;, &#39;function&#39;, &#39;variable&#39;];
const tokenModifiers = [&#39;declaration&#39;, &#39;documentation&#39;];
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: &#39;javascript&#39;, scheme: &#39;file&#39; };

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 = [&#39;class&#39;, &#39;interface&#39;, &#39;enum&#39;, &#39;function&#39;, &#39;variable&#39;];
const tokenModifiers = [&#39;declaration&#39;, &#39;documentation&#39;];
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,运行效果:

Analyse approfondie des principes de mise en évidence du code VSCode

除了这一段被识别出的 token 外,其它字符都被认为不可识别。

小结

本质上,DocumentSemanticTokensProvider 只是提供了一套粗糙的 IOC 接口,开发者能做的事情比较有限,所以现在大多数插件都没有采用这种方案,读者理解即可,不必深究。

Language API

简介

相对而言,vscode.languages.* 系列 API 所提供的语言扩展能力可能更符合前端开发者的思维习惯。vscode.languages.* 托管了一系列用户交互行为的处理、归类逻辑,并以事件接口方式开放出来,插件开发者只需监听这些事件,根据参数推断语言特性,并按规则返回结果即可。

Vscode Language API 提供了很多事件接口,比如说:

  • registerCompletionItemProvider: 提供代码补齐提示

Analyse approfondie des principes de mise en évidence du code VSCode

  • registerHoverProvider:光标停留在 token 上时触发

Analyse approfondie des principes de mise en évidence du code VSCode

  • registerSignatureHelpProvider:提供函数签名提示

Analyse approfondie des principes de mise en évidence du code VSCode

完整的列表请查阅 https://code.visualstudio.com/api/language-extensions/programmatic-language-features#show-hovers 一文。

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(&#39;language name&#39;, {
        provideHover(document, position, token) {
            return { contents: [&#39;aweome tecvan&#39;] };
        }
    });
    ...
}

运行结果:

1Analyse approfondie des principes de mise en évidence du code VSCode

其它特性功能的写法与此相似,感兴趣的同学建议到官网自行查阅。

Language Server Protocol

简介

上述基于语言扩展插件的代码高亮方法有一个相似的问题:难以在编辑器间复用,同一个语言,需要根据编辑器环境、语言重复编写功能相似的支持插件,那么对于 n 种语言,m 中编辑器,这里面的开发成本就是 n * m

为了解决这个问题,微软提出了一种叫做 Language Server Protocol 的标准协议,语言功能插件与编辑器之间不再直接通讯,而是通过 LSP 做一层隔离:

1Analyse approfondie des principes de mise en évidence du code VSCode

增加 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 执行代码分析动作,并提供高亮、补全、提示等功能,如下图:

1Analyse approfondie des principes de mise en évidence du code VSCode

简单示例

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 入口文件

样例代码中有几个关键点:

  • package.json 中声明激活条件与插件入口

  • 编写入口文件 client/src/extension.ts,启动 LSP 服务

  • 编写 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(&#39;server&#39;, &#39;out&#39;, &#39;server.js&#39;)
            ), 
            // 通讯协议,支持 stdio、ipc、pipe、socket
            transport: TransportKind.ipc 
        },
    };

    // Client 配置
    const clientOptions: LanguageClientOptions = {
        // 与 packages.json 文件的 activationEvents 类似
        // 插件的激活条件
        documentSelector: [{ scheme: &#39;file&#39;, language: &#39;plaintext&#39; }],
        // ...
    };

    // 使用 Server、Client 配置创建代理对象
    const client = new LanguageClient(
        &#39;languageServerExample&#39;,
        &#39;Language Server Example&#39;,
        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: &#39;ex&#39;
        };
        diagnostics.push(diagnostic);
    }

    // 发送错误诊断信息
    // vscode 会自动完成错误提示渲染
    connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
}

LSP Server 代码的主要流程:

  • 调用 createConnection 建立与 vscode 主进程的通讯链路,后续所有的信息交互都基于 connection 对象实现。
  • 创建 documents 对象,并根据需要监听文档事件如上例中的 onDidChangeContent
  • 在事件回调中分析代码内容,根据语言规则返回错误诊断信息,例如示例中使用正则判断单词是否全部为大写字母,是的话使用 connection.sendDiagnostics 接口发送错误提示信息

运行效果:

Analyse approfondie des principes de mise en évidence du code VSCode

小结

通览样例代码,LSP 客户端服务器之间的通讯过程都已经封装在 LanguageClientconnection 等对象中,插件开发者并不需要关心底层实现细节,也不需要深入理解 LSP 协议即可基于这些对象暴露的接口、事件等实现简单的代码高亮效果。

总结

Vscode 用插件方式提供了多种语言扩展接口,分声明式、编程式两类,在实际项目中通常会混合使用这两种技术,用基于 TextMate 的声明式接口迅速识别出代码中的词法;再用编程式接口如 LSP 补充提供诸如错误提示、代码补齐、跳转定义等高级功能。

这段时间看了不少开源 vscode 插件,其中 Vue 官方提供的 Vetur 插件学习是这方面的典型案例,学习价值极高,建议对这方面有兴趣的读者可以自行前往分析学习 vscode 语言扩展类插件的写法。

更多编程相关知识,请访问:编程入门!!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer