Краткое изложение протокола языкового сервера VSCode LSP

Зачем использовать протокол языкового сервера?

LSP( Протокол языкового сервера ) Языковой сервер — это специальное расширение кода Visual Studio, которое обеспечивает возможности редактирования для многих языков программирования. Используя языковой сервер, вы можете реализовать автозаполнение, проверку ошибок (диагностику), переход к определению и многие другие функции языка, поддерживаемые VS Code.

Однако при реализации поддержки языковых возможностей в VS Code мы обнаружили три общие проблемы:

Во-первых, языковые серверы часто реализуются на своих родных языках программирования, что затрудняет их интеграцию с VS Code со средой выполнения Node.js.

Кроме того, языковые функции могут требовать больших ресурсов. Например, для правильной проверки файлов языковые серверы должны анализировать большое количество файлов, строить для них абстрактные синтаксические деревья и выполнять статический анализ программы. Эти операции могут вызвать большую загрузку ЦП и памяти, нам необходимо убедиться, что производительность VS Code не пострадает.

Наконец, интеграция нескольких языковых инструментов с несколькими редакторами кода может потребовать огромных усилий. С точки зрения языковых инструментов, они должны приспосабливаться к редакторам кода с различными API. С точки зрения редактора кода, он не может ожидать какого-либо единого API от языковых инструментов. В конечном итоге это работа и усилия , необходимые для реализации языка для Nредактора .MN*M

вставьте сюда описание изображения

Для решения этих проблем Microsoft предоставляет протокол языкового сервера (Language Server Protocol) , предназначенный для предоставления спецификаций сообщества для языковых подключаемых модулей и редакторов. Таким образом, языковой сервер может быть реализован на любом языке, а связь по протоколу также позволяет избежать высоких накладных расходов, связанных с запуском плагина в основном процессе. Кроме того, любой LSP-совместимый языковой плагин можно интегрировать с LSP-совместимым редактором кода.LSP — беспроигрышное решение для разработчиков языковых плагинов и сторонних редакторов.

В VS Code языковой сервер состоит из двух частей:

  • Языковые клиенты: простые расширения VS Code, написанные на JavaScript/TypeScript. Это расширение имеет доступ ко всем API пространств имен VS Code.
  • Языковой сервер: инструмент языкового анализа, работающий в отдельном процессе.

Как упоминалось выше, запуск языкового сервера в отдельном процессе имеет два преимущества:

  • Инструмент анализа может быть реализован на любом языке, если он может взаимодействовать с языковым клиентом в соответствии с протоколом языкового сервера.
  • Поскольку инструменты языкового анализа обычно интенсивно используют ЦП и память, их запуск в отдельных процессах позволяет избежать снижения производительности.

Во-первых, разберитесь с расширением языка программирования

Какие расширения, связанные с языком программирования, можно сделать

Давайте сначала посмотрим на картинку, чтобы увидеть, какие расширения языка программирования поддерживает vscode.
вставьте сюда описание изображения
Во-первых, мы добавляем поддержку языковой конфигурации в разделе, посвященном package.json:

"languages": [{
    
    
     "id": "basic",
     "extensions": [
         ".bas" // 自定义语言扩展名
     ],
     "configuration": "./language-configuration.json"
 }

примечание

Используйте // для однострочных комментариев и /**/ для многострочных комментариев. Давайте напишем language-configuation.json так:

"comments": {
    
    
     "lineComment": "//",
     "blockComment": [
         "/*",
         "*/"
     ]
 }

После определения мы можем использовать Ctrl+K (Windows) или Cmd-K (Mac), чтобы вызвать открытие или закрытие комментариев.

соответствие скобки

Соединяем скобки и квадратные скобки:

"brackets": [
     [
         "[",
         "]"
     ],
     [
         "(",
         ")"
     ],
 ],

Автодополнение скобок

Вы можете использовать функцию автозаполнения скобок, чтобы предотвратить запись половины скобок:

"autoClosingPairs": [
     {
    
    
         "open": "\"",
         "close": "\""
     },
     {
    
    
         "open": "[",
         "close": "]"
     },
     {
    
    
         "open": "(",
         "close": ")"
     },
     {
    
    
         "open": "Sub",
         "close": "End Sub"
     }
 ]

В приведенном выше примере ввод «заполнит другую половину». То же самое справедливо и для других скобок.

Заключить выбранную область в скобки

После выбора области введите полукруглые скобки, чтобы автоматически окружить ее парой полных скобок, что называется функцией автоматического окружения.

пример:

"surroundingPairs": [
     [
         "[",
         "]"
     ],
     [
         "(",
         ")"
     ],
     [
         "\"",
         "\""
     ],
     [
         "'",
         "'",
     ]
 ],

свертывание кода

Когда будет больше функций и блоков кода, это вызовет определенные трудности при чтении кода. Мы можем свернуть блок кода. Это также старая функция времен Vim и emacs.

Давайте возьмем свертывание Sub/End Sub в качестве примера, чтобы увидеть, как записывается свертывание кода:

    "folding": {
    
    
        "markers": {
    
    
            "start": "^\\s*Sub.*",
            "end": "^\\s*End\\s*Sub.*"
        }
    }

Давайте посмотрим на эффект подскладывания:

вставьте сюда описание изображения

Диагностическая диагностическая информация (реализована подключаемым модулем расширения vscode)

Важной функцией языковых расширений является диагностика сканирования кода. Эта диагностическая информация представлена ​​vscode.Diagnostic в качестве носителя.
Давайте взглянем на членов класса vscode.Diagnostic и их взаимосвязь со связанными классами,
вставьте сюда описание изображения
от мала до велика, эти классы:

  • Позиция: Координаты символа, расположенного на линии.
  • Диапазон: определяется двумя позициями начальной и конечной точек.
  • Расположение: диапазон с URI
  • DiagnosticRelatedInformation: расположение с сообщением
  • Диагностика: тело представляет собой строку сообщения, диапазон и связанную с диагностикой информацию.

Составление диагностического сообщения
Давайте составим диагностическое сообщение.

for(var i = 0; i < 10; i ++) {
    
    
  for(var i = 0; i < 10; i ++) {
    
    
    console.log('*')
  }
}

В этом примере переменная управления циклом повторно используется во внешнем и внутреннем циклах, что приводит к сбою внешнего цикла.
Проблемный диапазон — это символы с 9-го по 10-й в строке 2. Позиция начинается с 0, поэтому мы строим диапазон с двумя позициями в качестве начала и конца, например, от (2,8) до (2,9).

new vscode.Range(
   new vscode.Position(2, 8), new vscode.Position(2, 9),
)

Имея Range, а также строку описания проблемы и серьезную программу проблемы, можно построить диагностику.

 let diag1: vscode.Diagnostic = new vscode.Diagnostic(
      new vscode.Range(
          new vscode.Position(2, 8), new vscode.Position(2, 9),
      ),
      '循环变量重复赋值',
      vscode.DiagnosticSeverity.Hint,
  )

Информация, связанная с диагностикой
Как упоминалось в предыдущем разделе, существует три элемента: диапазон, сообщение и серьезность, и можно создать диагностическую информацию.

Кроме того, можно установить некоторую дополнительную информацию.
Первый — это источник, например, из определенной версии eslint, с использованием определенных правил и тому подобное. Это можно записать в исходное свойство диагностики.

diag1.source = '某某规则';

Второй — это код ошибки, который полезен для классификации и запросов. Это представлено атрибутом кода, который может быть либо числом, либо строкой.

diag1.code = 401;

В-третьих, связанная информация. Для примера в предыдущем разделе мы сказали, что i был назначен, поэтому мы можем дополнительно сообщить разработчику, где он был назначен. Так что должен быть uri, адрес, по которому можно найти код. Существует также диапазон, чтобы указать конкретное местоположение в uri. Как упоминалось ранее, это структура vscode.Location.

    diag1.relatedInformation = [new vscode.DiagnosticRelatedInformation(
        new vscode.Location(document.uri,
            new vscode.Range(new vscode.Position(2, 4), new vscode.Position(2, 5))),
        '第一次赋值')];

Давайте соберем их вместе и выдадим сообщение об ошибке для приведенного выше test.js. Главное — записать указанную выше подсказку в переданную коллекцию DiagnosticCollection.

import * as vscode from 'vscode';
import * as path from 'path';

export function updateDiags(document: vscode.TextDocument,
    collection: vscode.DiagnosticCollection): void {
    
    
    let diag1: vscode.Diagnostic = new vscode.Diagnostic(
        new vscode.Range(
            new vscode.Position(2, 8), new vscode.Position(2, 9),
        ),
        '循环变量重复赋值',
        vscode.DiagnosticSeverity.Hint,
    );
    diag1.source = 'basic-lint';
    diag1.relatedInformation = [new vscode.DiagnosticRelatedInformation(
        new vscode.Location(document.uri,
            new vscode.Range(new vscode.Position(2, 4), new vscode.Position(2, 5))),
        '第一次赋值')];
    diag1.code = 102;

    if (document && path.basename(document.uri.fsPath) === 'test.js') {
    
    
        collection.set(document.uri, [diag1]);
    } else {
    
    
        collection.clear();
    }
}

События, запускающие диагностическую информацию
Затем мы добавляем вызов функции updateDiags, которую мы только что написали в функции активации плагина.

	const diag_coll = vscode.languages.createDiagnosticCollection('basic-lint-1');

	if (vscode.window.activeTextEditor) {
    
    
		diag.updateDiags(vscode.window.activeTextEditor.document, diag_coll);
	}

	context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(
		(e: vscode.TextEditor | undefined) => {
    
    
			if (e !== undefined) {
    
    
				diag.updateDiags(e.document, diag_coll);
			}
		}));

Запустите его, откройте test.bas во вновь запущенном vscode, а затем произвольно отредактируйте код в конце и активируйте триггеры. Рабочий интерфейс выглядит следующим образом:
вставьте сюда описание изображения

Диагностическая информация (реализована в режиме LSP)

код сервера

documents.onDidChangeContent(change => {
    
    
	validateTextDocument(change.document);
});

async function validateTextDocument(textDocument: TextDocument): Promise<void> {
    
    
	// In this simple example we get the settings for every validate run.
	const settings = await getDocumentSettings(textDocument.uri);

	// The validator creates diagnostics for all uppercase words length 2 and more
	const text = textDocument.getText();
	const pattern = /\b[A-Z]{2,}\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 all uppercase.`,
			source: 'ex'
		};
		if (hasDiagnosticRelatedInformationCapability) {
    
    
			diagnostic.relatedInformation = [
				{
    
    
					location: {
    
    
						uri: textDocument.uri,
						range: Object.assign({
    
    }, diagnostic.range)
					},
					message: 'Spelling matters'
				},
				{
    
    
					location: {
    
    
						uri: textDocument.uri,
						range: Object.assign({
    
    }, diagnostic.range)
					},
					message: 'Particularly for names'
				}
			];
		}
		diagnostics.push(diagnostic);
	}

	// Send the computed diagnostics to VSCode.
	connection.sendDiagnostics({
    
     uri: textDocument.uri, diagnostics });
}

основной методconnection.sendDiagnostics

клиентский код

/* --------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 * ------------------------------------------------------------------------------------------ */

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) {
    
    
	// The server is implemented in node
	const serverModule = context.asAbsolutePath(
		path.join('server', 'out', 'server.js')
	);
	// The debug options for the server
	// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
	const debugOptions = {
    
     execArgv: ['--nolazy', '--inspect=6009'] };

	// If the extension is launched in debug mode then the debug server options are used
	// Otherwise the run options are used
	const serverOptions: ServerOptions = {
    
    
		run: {
    
     module: serverModule, transport: TransportKind.ipc },
		debug: {
    
    
			module: serverModule,
			transport: TransportKind.ipc,
			options: debugOptions
		}
	};

	// Options to control the language client
	const clientOptions: LanguageClientOptions = {
    
    
		// Register the server for plain text documents
		documentSelector: [{
    
     scheme: 'file', language: 'plaintext' }],
		synchronize: {
    
    
			// Notify the server about file changes to '.clientrc files contained in the workspace
			fileEvents: workspace.createFileSystemWatcher('**/.clientrc')
		}
	};

	// Create the language client and start the client.
	client = new LanguageClient(
		'languageServerExample',
		'Language Server Example',
		serverOptions,
		clientOptions
	);

	// Start the client. This will also launch the server
	client.start();
}

export function deactivate(): Thenable<void> | undefined {
    
    
	if (!client) {
    
    
		return undefined;
	}
	return client.stop();
}

Два, подсветка синтаксиса

подсветка расширения vscode

Механизм токенизации VS Code использует синтаксис TextMate . Синтаксис TextMate представляет собой структурированный набор регулярных выражений и записывается в виде plist (XML) или файла JSON. Расширения VS Code могут добавлять грамматики через точку добавления грамматик.

Механизм токенизации TextMate работает в том же процессе, что и средство визуализации, и токены обновляются в соответствии с пользовательским вводом. Теги используются для подсветки синтаксиса, а также для классификации исходного кода на области комментариев, строк и регулярных выражений.

вставьте сюда описание изображения
вставьте сюда описание изображения
Например, мы определяем имя файла как ( произвольное fwhf)
вставьте сюда описание изображения
. /qq_42231248/статья/детали/129683141?spm=1001.2014.3001.5502
вставьте сюда описание изображения
вставьте сюда описание изображения

вставьте сюда описание изображения

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

Введение
«Поставщик семантических токенов» — это встроенный объектный протокол vscode, который должен самостоятельно сканировать содержимое файла кода, а затем возвращать последовательность семантических токенов в виде целочисленного массива, сообщая vscode, какая строка, столбец, а длина файла — тип токена.

Обратите внимание, что сканирование в TextMate управляется движком, а правила сопоставления выполняются построчно, в то время как правила сканирования и правила сопоставления в сценарии «Поставщик сематических токенов» реализуются самим разработчиком подключаемого модуля, что повышает гибкость, но также снижает относительную стоимость разработки выше.

С точки зрения реализации «Поставщик сематических токенов» определяется vscode.DocumentSemanticTokensProviderинтерфейсом, и разработчики могут реализовать два метода по мере необходимости:

provideDocumentSemanticTokens : Полный анализ семантики файла кода
provideDocumentSemanticTokensEdits : Инкрементальный анализ семантики редактируемого модуля
Рассмотрим полный пример:

import * as vscode from 'vscode';

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

vscode.languages.registerDocumentSemanticTokensProvider(selector, provider, legend);

Я полагаю, что большинство читателей почувствуют себя незнакомыми с этим кодом.После долгого размышления над ним я думаю, что его легче понять с точки зрения вывода функции, то есть tokensBuilder.build() в строке 17 приведенного выше примера. код.

Цвет причастия мы можем определить сами

"semanticTokenColors": {
    
    
"userName": "#2E8B57",
	"companyName": "#2E8B57",
	"court": "#6495ED",
	"lawFirm": "#4876FF",
	"law": "#FF8247",
	"time": "#EEB422",
	// "address:lawdawn": "#54a15a"

},

вставьте сюда описание изображения

3. Создайте языковой сервер с нуля

Структура каталогов
вставьте сюда описание изображения
делится на clientконец и serverконец

Реализация основной функции: извлечение цвета

пакет.json

 "activationEvents": [
    "onLanguage:plaintext"
  ],
  // "main": "./client/dist/browserClientMain", //桌面端
  "browser": "./client/dist/browserClientMain", // 浏览器端
  "contributes": {
    
    
    "configuration": [
      {
    
    
        "order": 22,
        "id": "lsp-web-extension-sample",
        "title": "lsp-web-extension-sample",
        "properties": {
    
    
          "lsp-web-extension-sample.trace.server": {
    
    
            "type": "string",
            "scope": "window",
            "enum": [
              "off",
              "messages",
              "verbose"
            ],
            "default": "verbose",
            "description": "Traces the communication between VS Code and the lsp-web-extension-sample language server."
          }
        }
      }
    ]
  },
  • main: запись на рабочем столе
  • browser: вход на стороне браузера

сервер/src/browserServerMain.ts

/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
import {
    
     createConnection, BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageserver/browser';

import {
    
     Color, ColorInformation, Range, InitializeParams, InitializeResult, ServerCapabilities, TextDocuments, ColorPresentation, TextEdit, TextDocumentIdentifier } from 'vscode-languageserver';
import {
    
     TextDocument } from 'vscode-languageserver-textdocument';


console.log('running server lsp-web-extension-sample');

/* browser specific setup code */

const messageReader = new BrowserMessageReader(self);
const messageWriter = new BrowserMessageWriter(self);

const connection = createConnection(messageReader, messageWriter);

/* from here on, all code is non-browser specific and could be shared with a regular extension */

connection.onInitialize((params: InitializeParams): InitializeResult => {
    
    
	const capabilities: ServerCapabilities = {
    
    
		colorProvider: {
    
    } // provide a color providr
	};
	return {
    
     capabilities };
});

// Track open, change and close text document events
const documents = new TextDocuments(TextDocument);
documents.listen(connection);

// Register providers
connection.onDocumentColor(params => getColorInformation(params.textDocument));
connection.onColorPresentation(params => getColorPresentation(params.color, params.range));

// Listen on the connection
connection.listen();


const colorRegExp = /#([0-9A-Fa-f]{6})/g;

function getColorInformation(textDocument: TextDocumentIdentifier) {
    
    
	console.log(111); 
	const colorInfos: ColorInformation[] = [];

	const document = documents.get(textDocument.uri);
	if (document) {
    
    
		const text = document.getText();

		colorRegExp.lastIndex = 0;
		let match;
		while ((match = colorRegExp.exec(text)) != null) {
    
    
			console.log('match->', match)
			const offset = match.index;
			const length = match[0].length;

			const range = Range.create(document.positionAt(offset), document.positionAt(offset + length));
			const color = parseColor(text, offset);
			console.log('color-->', color)
			colorInfos.push({
    
     color, range });
		}
	}

	return colorInfos;
}

function getColorPresentation(color: Color, range: Range) {
    
    
	console.log(22)
	const result: ColorPresentation[] = [];
	const red256 = Math.round(color.red * 255), green256 = Math.round(color.green * 255), blue256 = Math.round(color.blue * 255);

	function toTwoDigitHex(n: number): string {
    
    
		const r = n.toString(16);
		return r.length !== 2 ? '0' + r : r;
	}

	const label = `#${
      
      toTwoDigitHex(red256)}${
      
      toTwoDigitHex(green256)}${
      
      toTwoDigitHex(blue256)}`;
	result.push({
    
     label: label, textEdit: TextEdit.replace(range, label) });

	return result;
}


const enum CharCode {
    
    
	Digit0 = 48,
	Digit9 = 57,

	A = 65,
	F = 70,

	a = 97,
	f = 102,
}

function parseHexDigit(charCode: CharCode): number {
    
    
	if (charCode >= CharCode.Digit0 && charCode <= CharCode.Digit9) {
    
    
		return charCode - CharCode.Digit0;
	}
	if (charCode >= CharCode.A && charCode <= CharCode.F) {
    
    
		return charCode - CharCode.A + 10;
	}
	if (charCode >= CharCode.a && charCode <= CharCode.f) {
    
    
		return charCode - CharCode.a + 10;
	}
	return 0;
}

function parseColor(content: string, offset: number): Color {
    
    
	const r = (16 * parseHexDigit(content.charCodeAt(offset + 1)) + parseHexDigit(content.charCodeAt(offset + 2))) / 255;
	const g = (16 * parseHexDigit(content.charCodeAt(offset + 3)) + parseHexDigit(content.charCodeAt(offset + 4))) / 255;
	const b = (16 * parseHexDigit(content.charCodeAt(offset + 5)) + parseHexDigit(content.charCodeAt(offset + 6))) / 255;
	return Color.create(r, g, b, 1);
}

клиент/src/browserClientMain.ts

/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import {
    
     ExtensionContext, Uri } from 'vscode';
import {
    
     LanguageClientOptions } from 'vscode-languageclient';

import {
    
     LanguageClient } from 'vscode-languageclient/browser';

// this method is called when vs code is activated
export function activate(context: ExtensionContext) {
    
    

	console.log('lsp-web-extension-sample activated!');

	/* 
	 * all except the code to create the language client in not browser specific
	 * and couuld be shared with a regular (Node) extension
	 */
	const documentSelector = [{
    
     language: 'plaintext' }];

	// Options to control the language client
	const clientOptions: LanguageClientOptions = {
    
    
		documentSelector,
		synchronize: {
    
    },
		initializationOptions: {
    
    }
	};

	const client = createWorkerLanguageClient(context, clientOptions);

	const disposable = client.start();
	context.subscriptions.push(disposable);

	client.onReady().then(() => {
    
    
		console.log('lsp-web-extension-sample server is ready');
	});
}

function createWorkerLanguageClient(context: ExtensionContext, clientOptions: LanguageClientOptions) {
    
    
	// Create a worker. The worker main file implements the language server.
	const serverMain = Uri.joinPath(context.extensionUri, 'server/dist/browserServerMain.js');
	const worker = new Worker(serverMain.toString());

	// create the language server client to communicate with the server running in the worker
	return new LanguageClient('lsp-web-extension-sample', 'LSP Web Extension Sample', clientOptions, worker);
}

Отладка:
вставьте сюда описание изображения
вставьте сюда описание изображения
в официальном репозитории плагинов есть много примеров, вы можете скачать и попробовать.
Адрес: https://github.com/microsoft/vscode-extension-samples

В Интернете есть великие боги, которые резюмировали LSP API: https://vimsky.com/examples/detail/typescript-ex-vscode-languageserver-IConnection-onDocumentFormatting-method.html.

Ссылка: https://code.visualstudio.com/api/language-extensions/language-server-extension-guide

https://code.visualstudio.com/api/language-extensions/syntax-highlight-guide

https://cloud.tencent.com/developer/article/1841066

Supongo que te gusta

Origin blog.csdn.net/woyebuzhidao321/article/details/122004898
Recomendado
Clasificación