最近因为咱们公司的低代码开发渠道中的代码修改器的一些诟病,饱受开发同学吐槽,咱们决议基于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 还支撑以下功用和事情:

  1. 文档同步:当文档发生变化时,LSP 能够主动同步文档内容,以便进行语法查看、代码重构等操作。
  2. 代码格式化:LSP 能够依据编码标准主动格式化代码,以进步代码可读性和一致性。
  3. 语法查看:LSP 能够查看代码中的语法错误和潜在问题,并供给相应的修复主张。
  4. 代码重构:LSP 能够依据代码结构和语义,主动重构代码,以进步代码质量和可维护性。
  5. 调试支撑:LSP 能够与调试器集成,供给调试支撑,以便更方便地调试代码。
  6. 代码导航:LSP 能够供给代码导航功用,以便快速定位和浏览代码。
  7. 代码剖析:LSP 能够进行代码剖析,以供给更精确的代码补全和代码重构主张。
  8. 代码片段: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 能够供给丰富的功用和事情,能够依据详细需求进行运用。