From f4c9d1937762dc5fb7158eabe8a0ebb24ea9863b Mon Sep 17 00:00:00 2001 From: Denis Shienkov Date: Thu, 17 Oct 2024 14:46:16 +0300 Subject: [PATCH] Add LSP server support Qbs implements the LSP server partially starts from Qbs v2.2.0, but a more usefull LSP support starts from Qbs v2.3.0. This suport belongs to Qbs API level greater than 4, so, right now, the VSCode extension will use the LSP features for Qbs language only for that mention Qbs versions, and more newest. --- CHANGELOG.md | 3 +- package.json | 1 + src/protocol/qbsprotocoldatakey.ts | 1 + src/protocol/qbsprotocolhelloresponse.ts | 2 ++ src/qbslanguageclient.ts | 41 ++++++++++++++++++++++++ src/qbssession.ts | 13 +++++++- 6 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 src/qbslanguageclient.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ed6375e..eae54b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ reads the available Qbs profiles and fills the profile selector. Also this command runs once instead of **Qbs: Scan Build Profiles** when the extension loading to avoid the Qbs profiles re-detection. -- Improve hihgliting for the Qbs language syntax in editor. +- Improved hihgliting for the Qbs language syntax in editor. +- Added support for LSP server provided by Qbs session. ## 2.1.6 diff --git a/package.json b/package.json index 83aa262..7feab9c 100644 --- a/package.json +++ b/package.json @@ -587,6 +587,7 @@ "jsonc-parser": "^3.0.0", "uuid": "^9.0.0", "vscode-cpptools": "^6.1.0", + "vscode-languageclient": "^9.0.1", "vscode-nls": "^5.0.0", "which": "~2.0.2" }, diff --git a/src/protocol/qbsprotocoldatakey.ts b/src/protocol/qbsprotocoldatakey.ts index 88c7036..79c4102 100644 --- a/src/protocol/qbsprotocoldatakey.ts +++ b/src/protocol/qbsprotocoldatakey.ts @@ -45,6 +45,7 @@ export enum QbsProtocolDataKey { Location = 'location', LogData = 'log-data', LogLevel = 'log-level', + LspSocket = 'lsp-socket', MaxJobCount = 'max-job-count', MaxProgress = 'max-progress', Message = 'message', diff --git a/src/protocol/qbsprotocolhelloresponse.ts b/src/protocol/qbsprotocolhelloresponse.ts index 2839796..61776e7 100644 --- a/src/protocol/qbsprotocolhelloresponse.ts +++ b/src/protocol/qbsprotocolhelloresponse.ts @@ -4,9 +4,11 @@ import { QbsProtocolDataKey } from './qbsprotocoldatakey'; export class QbsProtocolHelloResponse { public readonly apiLevel: number = 0; public readonly apiCompatibilityLevel: number = 0; + public readonly lspSocket?: string; public constructor(response: any) { this.apiLevel = parseInt(response[QbsProtocolDataKey.ApiLevel]); this.apiCompatibilityLevel = parseInt(response[QbsProtocolDataKey.ApiCompatLevel]); + this.lspSocket = response[QbsProtocolDataKey.LspSocket]; } } diff --git a/src/qbslanguageclient.ts b/src/qbslanguageclient.ts new file mode 100644 index 0000000..c20bdf2 --- /dev/null +++ b/src/qbslanguageclient.ts @@ -0,0 +1,41 @@ +import * as vscode from 'vscode'; + +import { + LanguageClient, + LanguageClientOptions, + MessageTransports, +} from 'vscode-languageclient/node'; + +import { createServerPipeTransport } from 'vscode-languageserver-protocol/node'; + +export class QbsLanguageClient implements vscode.Disposable { + private languageClient?: LanguageClient; + + public constructor(pipeName: string) { + const serverOptions = this.createMessageTransports(pipeName); + const clientOptions: LanguageClientOptions = { + documentSelector: [{ language: 'qbs' }], + }; + console.info('Starting Qbs language client on pipe: ' + pipeName); + this.languageClient = new LanguageClient('Qbs', (async () => serverOptions), clientOptions, true); + this.languageClient + .start() + .then(async () => { + console.info('Qbs language client started on pipe: ' + pipeName); + }) + .catch((reason) => { + void vscode.window.showErrorMessage('Cannot start Qbs language server'); + console.error('Unable to start Qbs language client on pipe: ' + pipeName + ', ' + reason); + }); + + } + + public dispose(): void { this.languageClient?.dispose(); } + + private async createMessageTransports(pipeName: string): Promise { + return new Promise(async (resolve) => { + const transport = createServerPipeTransport(pipeName); + resolve({ reader: transport[0], writer: transport[1] }); + }); + } +} diff --git a/src/qbssession.ts b/src/qbssession.ts index 6869a8c..1f4ac5b 100644 --- a/src/qbssession.ts +++ b/src/qbssession.ts @@ -21,6 +21,8 @@ import { QbsProtocolTaskMaxProgressResponse } from './protocol/qbsprotocoltaskma import { QbsProtocolTaskProgressResponse } from './protocol/qbsprotocoltaskprogressresponse'; import { QbsProtocolTaskStartedResponse } from './protocol/qbsprotocoltaskstartedresponse'; +import { QbsLanguageClient } from './qbslanguageclient'; + const localize = nls.config({ messageFormat: nls.MessageFormat.file })(); export enum QbsSessionState { @@ -40,6 +42,7 @@ export class QbsSessionProjectData { export class QbsSession implements vscode.Disposable { private engine: QbsProtocolEngine = new QbsProtocolEngine(); private state: QbsSessionState = QbsSessionState.Stopped; + private languageClient?: QbsLanguageClient; private readonly stateChanged: vscode.EventEmitter = new vscode.EventEmitter(); @@ -135,6 +138,8 @@ export class QbsSession implements vscode.Disposable { if (type === QbsProtocolDataKey.Hello) { const result = new QbsProtocolHelloResponse(response) this.helloReceived.fire(result); + if (result.lspSocket) + this.startupLanguageClient(result.lspSocket, result.apiLevel); } else if (type === QbsProtocolDataKey.ProjectResolved) { const data = new QbsProtocolProjectData(response[QbsProtocolDataKey.ProjectData]); const msg = new QbsProtocolMessageResponse(response[QbsProtocolDataKey.Error]); @@ -192,6 +197,12 @@ export class QbsSession implements vscode.Disposable { this.stateChanged.fire(this.state); } + private startupLanguageClient(pipeName: string, apiLevel: number) { + if (apiLevel <= 4) + return; // LSP supports by Qbs from the Qbs API Level version 4 and greather! + this.languageClient = new QbsLanguageClient(pipeName); + } + private static convert(status: QbsProtocolEngineState): QbsSessionState { switch (status) { case QbsProtocolEngineState.Started: @@ -217,7 +228,7 @@ export class QbsSession implements vscode.Disposable { return false; } else if (!fs.existsSync(qbsPath)) { await vscode.window.showErrorMessage(localize('qbs.executable.not-found.error.message', - `Qbs executable not found.`)); + 'Qbs executable not found.')); return false; } return true;