笔记软件在2023/4/6 14:01:06推送该笔记
Language Server
这个语言服务器的源码在/server中。比较重要的pacakge.json部分是
"dependencies": {
"vscode-languageserver": "^6.1.1",
"vscode-languageserver-textdocument": "^1.0.1"
}
下面是一个服务器的实现,提供了简单的纯文本管理——VS Code会向服务器发送一个文件的全部内容
import {
createConnection,
TextDocuments,
Diagnostic,
DiagnosticSeverity,
ProposedFeatures,
InitializeParams,
DidChangeConfigurationNotification,
CompletionItem,
CompletionItemKind,
TextDocumentPositionParams,
TextDocumentSyncKind,
InitializeResult
} from 'vscode-languageserver';
import { TextDocument } from 'vscode-languageserver-textdocument';
// 创建一个服务器连接。使用Node的IPC作为传输方式。
// 也包含所有的预览、建议等LSP特性
let connection = createConnection(ProposedFeatures.all);
// 创建一个简单的文本管理器。
// 文本管理器只支持全文本同步。
let documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
let hasConfigurationCapability: boolean = false;
let hasWorkspaceFolderCapability: boolean = false;
let hasDiagnosticRelatedInformationCapability: boolean = false;
connection.onInitialize((params: InitializeParams) => {
let capabilities = params.capabilities;
// 客户端是否支持`workspace/configuration`请求?
// 如果不是的话,降级到使用全局设置
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,
// Tell the client that this server supports code completion.
completionProvider: {
resolveProvider: true
}
}
};
if (hasWorkspaceFolderCapability) {
result.capabilities.workspace = {
workspaceFolders: {
supported: true
}
};
}
return result;
});
connection.onInitialized(() => {
if (hasConfigurationCapability) {
// 为所有配置Register for all configuration changes.
connection.client.register(
DidChangeConfigurationNotification.type,
undefined
);
}
if (hasWorkspaceFolderCapability) {
connection.workspace.onDidChangeWorkspaceFolders(_event => {
connection.console.log('Workspace folder change event received.');
});
}
});
// 配置示例
interface ExampleSettings {
maxNumberOfProblems: number;
}
// 当客户端不支持`workspace/configuration`请求时,使用global settings
// 请注意,在这个例子中服务器使用的客户端并不是问题所在,而是这种情况还可能发生在其他客户端身上。
const defaultSettings: ExampleSettings = { maxNumberOfProblems: 1000 };
let globalSettings: ExampleSettings = defaultSettings;
// 对所有打开的文档配置进行缓存
let 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;
}
// 只对打开的文档保留设置
documents.onDidClose(e => {
documentSettings.delete(e.document.uri);
});
// 文档的文本内容发生了改变。
// 这个事件在文档第一次打开或者内容变动时才会触发。
documents.onDidChangeContent(change => {
validateTextDocument(change.document);
});
async function validateTextDocument(textDocument: TextDocument): Promise<void> {
// 在这个简单的示例中,每次校验运行时我们都获取一次配置
let settings = await getDocumentSettings(textDocument.uri);
// 校验器如果检测到连续超过2个以上的大写字母则会报错
let text = textDocument.getText();
let pattern = /\b[A-Z]{2,}\b/g;
let m: RegExpExecArray | null;
let problems = 0;
let diagnostics: Diagnostic[] = [];
while ((m = pattern.exec(text)) && problems < settings.maxNumberOfProblems) {
problems++;
let diagnosic: 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: 'ex'
};
if (hasDiagnosticRelatedInformationCapability) {
diagnosic.relatedInformation = [
{
location: {
uri: textDocument.uri,
range: Object.assign({}, diagnosic.range)
},
message: 'Spelling matters'
},
{
location: {
uri: textDocument.uri,
range: Object.assign({}, diagnosic.range)
},
message: 'Particularly for names'
}
];
}
diagnostics.push(diagnosic);
}
// 将错误处理结果发送给VS Code
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
}
connection.onDidChangeWatchedFiles(_change => {
// 监测VS Code中的文件变动
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 = 'TypeScript documentation';
} else if (item.data === 2) {
item.detail = 'JavaScript details';
item.documentation = 'JavaScript documentation';
}
return item;
}
);
// 让文档管理器监听文档的打开,变动和关闭事件。
documents.listen(connection);
// 连接后启动监听
connection.listen();
标签:语言,实现,textDocument,capabilities,item,connection,let,workspace,服务端
From: https://www.cnblogs.com/myfriend/p/implement-the-language-service-side-z1vx5pf.html