答案:通过编写语言服务器并集成LSP协议,可为VS Code添加自定义语义标记;需在package.json中定义token类型,使用vscode-languageserver-node等库实现服务器逻辑,并优化性能以处理大型文件。

为 VS Code 设置自定义语义标记提供程序,核心在于扩展 VS Code 的语言服务能力,让编辑器能更智能地理解你的代码。这需要你编写一个语言服务器,并将其与 VS Code 集成。
选择合适的语言服务器协议(LSP)库: LSP 定义了编辑器和语言服务器之间的通信协议。有多种 LSP 库可供选择,例如:
vscode-languageserver-node (官方库)pygls
lsp4j
选择你熟悉的语言和对应的库。这里以 Node.js 和 vscode-languageserver-node 为例。
创建语言服务器:
npm install vscode-languageserver vscode-languageserver-protocol vscode-uri
import {
createConnection,
TextDocuments,
Diagnostic,
DiagnosticSeverity,
ProposedFeatures,
InitializeParams,
DidChangeConfigurationNotification,
CompletionItem,
CompletionItemKind,
TextDocumentPositionParams,
TextDocumentSyncKind,
InitializeResult,
SemanticTokensBuilder,
SemanticTokensLegend,
SemanticTokensParams
} from 'vscode-languageserver/node';
import { TextDocument } from 'vscode-languageserver-textdocument';
// 创建连接和文档管理器
const connection = createConnection(ProposedFeatures.all);
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
let hasConfigurationCapability: boolean = false;
let hasWorkspaceFolderCapability: boolean = false;
let hasDiagnosticRelatedInformationCapability: boolean = false;
connection.onInitialize((params: InitializeParams) => {
const capabilities = params.capabilities;
hasConfigurationCapability = !!(
capabilities.workspace && !!capabilities.workspace.configuration
);
hasWorkspaceFolderCapability = !!(
capabilities.workspace && !!capabilities.workspace.workspaceFolders
);
hasDiagnosticRelatedInformationCapability = !!(
capabilities.textDocument &&
capabilities.textDocument.publishDiagnostics &&
capabilities.textDocument.publishDiagnostics.relatedInformation
);
const result: InitializeResult = {
capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental,
completionProvider: {
resolveProvider: true
},
semanticTokensProvider: {
legend: {
tokenTypes: ['class', 'interface', 'enum', 'function', 'variable'],
tokenModifiers: []
},
range: false,
full: true
}
}
};
if (hasWorkspaceFolderCapability) {
result.capabilities.workspace = {
workspaceFolders: {
supported: true
}
};
}
return result;
});
connection.onInitialized(() => {
if (hasConfigurationCapability) {
connection.client.register(DidChangeConfigurationNotification.type, undefined);
}
if (hasWorkspaceFolderCapability) {
connection.workspace.onDidChangeWorkspaceFolders(_event => {
connection.console.log('Workspace folder change event received.');
});
}
});
interface ExampleSettings {
maxNumberOfProblems: number;
}
const defaultSettings: ExampleSettings = { maxNumberOfProblems: 1000 };
let globalSettings: ExampleSettings = defaultSettings;
const documentSettings: Map<string, Thenable<ExampleSettings>> = new Map();
connection.onDidChangeConfiguration(change => {
if (hasConfigurationCapability) {
documentSettings.clear();
} else {
globalSettings = <ExampleSettings>(
(change.settings.languageServerExample || defaultSettings)
);
}
documents.all().forEach(validateTextDocument);
});
function getDocumentSettings(resource: string): Thenable<ExampleSettings> {
if (!hasConfigurationCapability) {
return Promise.resolve(globalSettings);
}
let result = documentSettings.get(resource);
if (!result) {
result = connection.workspace.getConfiguration({
scopeUri: resource,
section: 'languageServerExample'
});
documentSettings.set(resource, result);
}
return result;
}
async function validateTextDocument(textDocument: TextDocument): Promise<void> {
const settings = await getDocumentSettings(textDocument.uri);
const text = textDocument.getText();
const pattern = /\b[A-Z][a-z]+\b/g;
let m: RegExpExecArray | null;
let problems = 0;
const diagnostics: Diagnostic[] = [];
while ((m = pattern.exec(text)) && problems < settings.maxNumberOfProblems) {
problems++;
const diagnostic: Diagnostic = {
severity: DiagnosticSeverity.Warning,
range: {
start: textDocument.positionAt(m.index),
end: textDocument.positionAt(m.index + m[0].length)
},
message: `${m[0]} is using PascalCase.`,
source: 'ex'
};
if (hasDiagnosticRelatedInformationCapability) {
diagnostic.relatedInformation = [
{
location: {
uri: textDocument.uri,
range: Object.assign({}, diagnostic.range)
},
message: 'Spelling matters'
}
];
}
diagnostics.push(diagnostic);
}
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
}
documents.onDidChangeContent(change => {
validateTextDocument(change.document);
});
connection.onDidChangeWatchedFiles(_change => {
connection.console.log('We received an file change event');
});
connection.onCompletion(
(_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
return [
{
label: 'TypeScript',
kind: CompletionItemKind.Text,
data: 1
},
{
label: 'JavaScript',
kind: CompletionItemKind.Text,
data: 2
}
];
}
);
connection.onCompletionResolve(
(item: CompletionItem): CompletionItem => {
if (item.data === 1) {
item.detail = 'TypeScript details';
item.documentation = 'Documentation for TypeScript';
} else if (item.data === 2) {
item.detail = 'JavaScript details';
item.documentation = 'Documentation for JavaScript';
}
return item;
}
);
connection.onSemanticTokens((params: SemanticTokensParams) => {
const builder = new SemanticTokensBuilder();
const text = documents.get(params.textDocument.uri)?.getText();
if (!text) {
return { data: [] };
}
// 示例:标记所有 "class" 关键字为 "class" tokenType
let match: RegExpExecArray | null;
const regex = /\bclass\b/g;
while ((match = regex.exec(text)) !== null) {
const start = match.index;
const length = match[0].length;
const position = documents.get(params.textDocument.uri)?.positionAt(start);
if (position) {
builder.push(
position.line,
position.character,
length,
0, // tokenType (0 corresponds to 'class' in the legend)
0 // tokenModifiers (none)
);
}
}
return builder.build();
});
documents.listen(connection);
connection.listen();tsc server.ts --target es6 --module commonjs --outDir out
创建 VS Code 扩展:
mkdir my-extension && cd my-extension
yo code (需要安装 npm install -g yo generator-code){
"name": "my-extension",
"displayName": "My Extension",
"description": "A language server example",
"version": "0.0.1",
"engines": {
"vscode": "^1.63.0"
},
"categories": [
"Languages"
],
"activationEvents": [
"onLanguage:yourLanguageId"
],
"main": "./client/out/extension",
"contributes": {
"languages": [
{
"id": "yourLanguageId",
"aliases": [
"Your Language",
"yourLanguageId"
],
"extensions": [
".yourExtension"
],
"configuration": "./language-configuration.json"
}
],
"configurationDefaults": {
"[yourLanguageId]": {}
},
"semanticTokenTypes": [
"class",
"interface",
"enum",
"function",
"variable"
],
"semanticTokenScopes": [
{
"language": "yourLanguageId",
"scopes": {
"class": [ "entity.name.class.yourLanguageId" ],
"interface": [ "entity.name.interface.yourLanguageId" ],
"enum": [ "entity.name.enum.yourLanguageId" ],
"function": [ "entity.name.function.yourLanguageId" ],
"variable": [ "variable.other.yourLanguageId" ]
}
}
]
},
"dependencies": {
"@types/vscode": "^1.63.0",
"vscode-languageclient": "^8.0.0",
"vscode-languageserver": "^8.0.0",
"vscode-languageserver-protocol": "^3.17.0"
},
"devDependencies": {
"@types/glob": "^7.1.4",
"@types/mocha": "^9.0.0",
"@types/node": "16.x",
"eslint": "^8.4.1",
"glob": "^7.1.7",
"mocha": "^9.1.3",
"typescript": "^4.5.4",
"@vscode/test-electron": "^2.0.3"
}
}import * as path from 'path';
import { workspace, ExtensionContext } from 'vscode';
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind
} from 'vscode-languageclient/node';
let client: LanguageClient;
export function activate(context: ExtensionContext) {
const serverModule = context.asAbsolutePath(
path.join('server', 'out', 'server.js')
);
const debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] };
const serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: {
module: serverModule,
transport: TransportKind.ipc,
options: debugOptions
}
};
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'yourLanguageId' }],
synchronize: {
configurationSection: 'languageServerExample',
fileEvents: workspace.createFileSystemWatcher('**/.clientrc')
}
};
client = new LanguageClient(
'languageServerExample',
'Language Server Example',
serverOptions,
clientOptions
);
client.start();
}
export function deactivate(): Thenable<void> | undefined {
if (!client) {
return undefined;
}
return client.stop();
}配置语言:
language-configuration.json 文件,定义语言的语法和符号。{
"comments": {
"lineComment": "//",
"blockComment": [ "/*", "*/" ]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
{"open": "{", "close": "}"},
{"open": "[", "close": "]"},
{"open": "(", "close": ")"},
{"open": "\"", "close": "\"", "notIn": ["string"]},
{"open": "'", "close": "'", "notIn": ["string", "comment"]}
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
]
}调试和测试:
在package.json文件的contributes.semanticTokenTypes部分,你可以定义自己的token类型。 例如,你可以添加一个名为myCustomType的token类型:
"contributes": {
"semanticTokenTypes": [
"class",
"interface",
"enum",
"function",
"variable",
"myCustomType"
]
}然后在你的语言服务器代码中,你需要确保你使用了这个新的token类型。 在server.ts的connection.onSemanticTokens处理程序中,你需要修改builder.push调用,以使用正确的token类型索引。 例如,如果myCustomType是列表中的第6个类型(索引为5),那么你需要使用5作为tokenType参数。
处理大型文件进行语义标记可能会变得非常慢。 以下是一些提高性能的策略:
增量更新: 只在文件更改的部分重新计算语义标记,而不是每次都重新处理整个文件。 使用TextDocument.onDidChangeContent事件来检测更改,并仅对更改的区域进行标记。
ShopWind网店系统是国内最专业的网店程序之一,采用ASP语言设计开发,速度快、性能好、安全性高。ShopWind网店购物系统提供性化的后台管理界面,标准的网上商店管理模式和强大的网店软件后台管理功能。ShopWind网店系统提供了灵活强大的模板机制,内置多套免费精美模板,同时可在后台任意更换,让您即刻快速建立不同的网店外观。同时您可以对网模板自定义设计,建立个性化网店形象。ShopWind网
0
分块处理: 将文件分成更小的块,并并行处理这些块。 这可以通过使用Web Workers或Node.js的cluster模块来实现。
缓存: 缓存语义标记的结果,以便在下次需要时可以重用它们。 确保在文件更改时使缓存失效。
优化正则表达式: 确保你使用的正则表达式是高效的。 避免使用过于复杂的正则表达式,并尽可能使用预编译的正则表达式。
限制标记范围: 只标记当前可见区域内的代码。 当用户滚动到新的区域时,再标记新的代码。
不同的编程语言有不同的LSP库和工具。 以下是一些常用语言的例子:
Python: 使用pygls库。 pygls提供了一个简单的API来创建语言服务器。 你可以使用pygls.lsp.methods.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL方法来注册语义标记提供程序。
Java: 使用lsp4j库。 lsp4j是一个用于LSP的Java绑定。 你需要创建一个实现TextDocumentService接口的类,并实现semanticTokensFull方法。
C#: 使用OmniSharp或MonoDevelop。 这些工具提供对C#语言的LSP支持。
无论你选择哪种语言,都需要遵循LSP协议,并使用相应的库来处理与VS Code的通信。 关键在于理解LSP协议,并能够将你的语言的语法和语义映射到LSP的token类型和修饰符。
以上就是如何为VSCode设置一个自定义的语义标记提供程序?的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号