基于lsp 增强代码编辑器
最近因为我们公司的低代码开发平台中的代码编辑器的一些诟病,饱受开发同学吐槽,我们决定基于LSP 方式增强已有的代码编辑器功能,来提升开发同学们的用户体验。学习了一下LSP,整理总结一下如下
1. LSP 基本概念
LSP(Language Server Protocol)是一种语言服务器协议,它定义了一种通用的协议,使得不同的编辑器或IDE可以使用相同的语言服务器进行代码编辑和分析。LSP的主要目的是提高编辑器和IDE的代码智能化和跨平台性。
LSP 的基本工作原理是:编辑器或IDE与语言服务器建立连接后,通过 LSP 协议进行通信。当用户在编辑器中输入代码时,编辑器会将相关信息发送给语言服务器,语言服务器会进行代码分析和补全等操作,并将结果返回给编辑器,编辑器再将结果展示给用户。
使用 LSP 可以实现多种功能,包括代码补全、代码格式化、语法检查、代码重构等。LSP 的优势在于其跨平台性和灵活性,可以让不同的编辑器或IDE使用相同的语言服务器,从而提高开发效率和代码质量。
先基于vscode 我们实现一个简单的TypeScript 语言服务器,我们需要安装 TypeScript 语言服务器和 VS Code 编辑器。
新建 TypeScript 项目 在项目中创建一个名为server.ts的文件
我们实现代码补全
// 导入了一些 LSP 相关的模块,然后创建了一个 `connection` 对象和一个 `documents` 对象。
import {
createConnection,
TextDocuments,
ProposedFeatures,
InitializeParams,
CompletionItem,
CompletionItemKind,
} from 'vscode-languageserver';
// connection对象用于与客户端建立连接,并注册了一些事件处理函数。`documents` 对象用于管理文档,并监听文档变化事件。
const connection = createConnection(ProposedFeatures.all);
const documents = new TextDocuments();
documents.listen(connection);
// 在 `onInitialize` 事件处理函数中,我们返回了一些 LSP 支持的能力,包括文档同步和代码补全。
connection.onInitialize((params: InitializeParams) => {
return {
capabilities: {
textDocumentSync: documents.syncKind,
completionProvider: {
resolveProvider: true,
triggerCharacters: ['.'],
},
},
};
});
// 在 onCompletion 事件处理函数中,我们创建了两个 CompletionItem 对象,并将它们作为代码补全的结果返回。
connection.onCompletion((textDocumentPosition) => {
const { line, character } = textDocumentPosition.position;
const document = documents.get(textDocumentPosition.textDocument.uri);
const completions: CompletionItem[] = [
{
label: 'Hello',
kind: CompletionItemKind.Text,
data: 1,
},
{
label: 'World',
kind: CompletionItemKind.Text,
data: 2,
},
];
return completions;
});
// 我们调用 `connection.listen()` 方法来启动 LSP 服务器。
connection.listen();
在 VS Code 编辑器中打开 client.ts
文件,并输入以下代码:
console.
当输入 console.
时,编辑器会自动调用 LSP 服务器,展示代码补全结果。
这个示例只是 LSP 的一个简单演示,实际上 LSP 还支持更多的功能和事件,例如代码格式化、语法检查、代码重构等。
除了代码补全功能之外,LSP 还支持以下功能和事件:
- 文档同步:当文档发生变化时,LSP 可以自动同步文档内容,以便进行语法检查、代码重构等操作。
- 代码格式化:LSP 可以根据编码规范自动格式化代码,以提高代码可读性和一致性。
- 语法检查:LSP 可以检查代码中的语法错误和潜在问题,并提供相应的修复建议。
- 代码重构:LSP 可以根据代码结构和语义,自动重构代码,以提高代码质量和可维护性。
- 调试支持:LSP 可以与调试器集成,提供调试支持,以便更方便地调试代码。
- 代码导航:LSP 可以提供代码导航功能,以便快速定位和浏览代码。
- 代码分析:LSP 可以进行代码分析,以提供更准确的代码补全和代码重构建议。
- 代码片段:LSP 可以提供代码片段功能,以便快速插入常用代码模板。
LSP 可以大大提高编辑器和 IDE 的代码智能化和跨平台性,让开发者更加高效地编写和维护代码。
2. LSP 功能的代码示例:
1. 文档同步
import { createConnection, TextDocuments } from 'vscode-languageserver';
const connection = createConnection();
const documents = new TextDocuments();
documents.listen(connection);
documents.onDidChangeContent((change) => {
console.log(change.document.uri, change.document.getText());
});
connection.listen();
上述代码中,我们创建了一个 documents
对象,并监听了 onDidChangeContent
事件。当文档发生变化时,事件处理函数会将文档的 URI 和文本内容打印出来。
2. 代码格式化
import { createConnection, TextDocuments } from 'vscode-languageserver';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { format } from 'prettier';
const connection = createConnection();
const documents = new TextDocuments(TextDocument);
documents.listen(connection);
connection.onRequest('textDocument/formatting', (params) => {
const document = documents.get(params.textDocument.uri);
const formatted = format(document.getText(), { parser: 'typescript' });
return [{ range: { start: { line: 0, character: 0 }, end: document.positionAt(document.getText().length) }, newText: formatted }];
});
connection.listen();
上述代码中,我们使用了 prettier
模块来进行代码格式化。在 onRequest
事件处理函数中,我们监听了 textDocument/formatting
请求,并将请求的文本进行格式化,然后将格式化后的文本返回给客户端。
3. 语法检查
import { createConnection, TextDocuments, Diagnostic, DiagnosticSeverity } from 'vscode-languageserver';
import * as ts from 'typescript';
const connection = createConnection();
const documents = new TextDocuments();
documents.listen(connection);
connection.onDidChangeWatchedFiles((change) => {
console.log('File change event received:', change);
});
connection.onDidChangeConfiguration((change) => {
console.log('Configuration change event received:', change);
});
documents.onDidChangeContent((change) => {
const diagnostics: Diagnostic[] = [];
const program = ts.createProgram({
rootNames: [change.document.uri],
options: {},
});
const diagnosticsHost: ts.FormatDiagnosticsHost = {
getCanonicalFileName: (path) => path,
getCurrentDirectory: ts.sys.getCurrentDirectory,
getNewLine: () => ts.sys.newLine,
};
const emitResult = program.emit();
const allDiagnostics = ts
.getPreEmitDiagnostics(program)
.concat(emitResult.diagnostics)
.filter((diagnostic) => diagnostic.file !== undefined);
allDiagnostics.forEach((diagnostic) => {
const { file } = diagnostic;
if (file) {
const { line, character } = file.getLineAndCharacterOfPosition(diagnostic.start!);
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
const severity = diagnostic.category === ts.DiagnosticCategory.Error ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning;
diagnostics.push({
severity,
range: {
start: { line, character },
end: { line, character: character + 1 },
},
message,
source: 'typescript',
});
}
});
connection.sendDiagnostics({ uri: change.document.uri, diagnostics });
});
connection.listen();
上述代码中,我们使用了 TypeScript 编译器来进行语法检查。在 onDidChangeContent
事件处理函数中,我们使用 ts.createProgram
方法来创建一个 TypeScript 编译器实例,并使用 ts.getPreEmitDiagnostics
方法来获取所有的诊断信息。然后,我们将诊断信息转换成 LSP 的 Diagnostic
对象,并通过 connection.sendDiagnostics
方法将诊断信息发送给客户端。
4. 代码重构
import { createConnection, TextDocuments, Range, Position } from 'vscode-languageserver';
import * as ts from 'typescript';
const connection = createConnection();
const documents = new TextDocuments();
documents.listen(connection);
connection.onDidChangeWatchedFiles((change) => {
console.log('File change event received:', change);
});
connection.onDidChangeConfiguration((change) => {
console.log('Configuration change event received:', change);
});
documents.onDidChangeContent(async (change) => {
const refactorInfo = await connection.sendRequest('textDocument/codeAction', {
textDocument: change.document,
range: Range.create(Position.create(0, 0), Position.create(change.document.lineCount, 0)),
context: {
diagnostics: [],
only: ['refactor'],
},
});
if (refactorInfo) {
console.log(refactorInfo);
}
});
connection.listen();
上述代码中,我们使用了 textDocument/codeAction
请求来进行代码重构。在 onDidChangeContent
事件处理函数中,我们向客户端发送了一条 textDocument/codeAction
请求,并将请求的范围设置为整个文档。然后,我们将请求的结果打印出来。
LSP 可以提供丰富的功能和事件,可以根据具体需求进行使用。
转载自:https://juejin.cn/post/7218778345903456314