@microsoft/compose-language-service
Version:
Language service for Docker Compose documents
208 lines • 10.2 kB
JavaScript
"use strict";
/*!--------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ComposeLanguageService = void 0;
const vscode_languageserver_1 = require("vscode-languageserver");
const DocumentSettings_1 = require("../client/DocumentSettings");
const TelemetryEvent_1 = require("../client/TelemetryEvent");
const ComposeDocument_1 = require("./ComposeDocument");
const MultiCompletionProvider_1 = require("./providers/completion/MultiCompletionProvider");
const DiagnosticProvider_1 = require("./providers/DiagnosticProvider");
const DocumentFormattingProvider_1 = require("./providers/DocumentFormattingProvider");
const ImageLinkProvider_1 = require("./providers/ImageLinkProvider");
const KeyHoverProvider_1 = require("./providers/KeyHoverProvider");
const ServiceStartupCodeLensProvider_1 = require("./providers/ServiceStartupCodeLensProvider");
const ActionContext_1 = require("./utils/ActionContext");
const TelemetryAggregator_1 = require("./utils/telemetry/TelemetryAggregator");
const DefaultCapabilities = {
// Text document synchronization
textDocumentSync: {
openClose: true,
change: vscode_languageserver_1.TextDocumentSyncKind.Incremental,
willSave: false,
willSaveWaitUntil: false,
save: false,
},
// Both basic and advanced completions
completionProvider: {
triggerCharacters: ['-', ':', ' ', '"'],
resolveProvider: false,
},
// Code lenses for starting services
codeLensProvider: {
resolveProvider: false,
},
// Hover over YAML keys
hoverProvider: true,
// Links to Docker Hub on image names
documentLinkProvider: {
resolveProvider: false,
},
// YAML formatting
documentFormattingProvider: true,
// Workspace features
workspace: {
workspaceFolders: {
supported: true,
},
},
};
// Default settings for a client with no alternate YAML language service
const DefaultAlternateYamlLanguageServiceClientCapabilities = {
syntaxValidation: false,
schemaValidation: false,
basicCompletions: false,
advancedCompletions: false,
serviceStartupCodeLens: false,
hover: false,
imageLinks: false,
formatting: false,
};
class ComposeLanguageService {
constructor(connection, clientParams) {
var _a, _b, _c;
this.connection = connection;
this.clientParams = clientParams;
this.documentManager = new vscode_languageserver_1.TextDocuments(ComposeDocument_1.ComposeDocument.DocumentManagerConfig);
this.subscriptions = [];
this._capabilities = DefaultCapabilities;
let altYamlCapabilities = (_a = clientParams.capabilities.experimental) === null || _a === void 0 ? void 0 : _a.alternateYamlLanguageService;
if (altYamlCapabilities) {
connection.console.info('An alternate YAML language service is present. The Compose language service will not enable features already provided by the alternate.');
}
else {
altYamlCapabilities = DefaultAlternateYamlLanguageServiceClientCapabilities;
}
// Hook up the document listeners, which create a Disposable which will be added to this.subscriptions
if (altYamlCapabilities.syntaxValidation && altYamlCapabilities.schemaValidation) {
// Noop. No server-side capability needs to be set for diagnostics because it is based on pushing from server to client.
}
else {
this.createDocumentManagerHandler(this.documentManager.onDidChangeContent, new DiagnosticProvider_1.DiagnosticProvider((_b = clientParams.initializationOptions) === null || _b === void 0 ? void 0 : _b.diagnosticDelay, !altYamlCapabilities.syntaxValidation, !altYamlCapabilities.schemaValidation));
}
// End of document listeners
// Hook up all the applicable LSP listeners, which do not create Disposables for some reason
if (altYamlCapabilities.basicCompletions && altYamlCapabilities.advancedCompletions) {
this._capabilities.completionProvider = undefined;
}
else {
this.createLspHandler(this.connection.onCompletion, new MultiCompletionProvider_1.MultiCompletionProvider(!altYamlCapabilities.basicCompletions, !altYamlCapabilities.advancedCompletions));
}
if (altYamlCapabilities.serviceStartupCodeLens) {
this._capabilities.codeLensProvider = undefined;
}
else {
this.createLspHandler(this.connection.onCodeLens, new ServiceStartupCodeLensProvider_1.ServiceStartupCodeLensProvider());
}
if (altYamlCapabilities.hover) {
this._capabilities.hoverProvider = undefined;
}
else {
this.createLspHandler(this.connection.onHover, new KeyHoverProvider_1.KeyHoverProvider());
}
if (altYamlCapabilities.imageLinks) {
this._capabilities.documentLinkProvider = undefined;
}
else {
this.createLspHandler(this.connection.onDocumentLinks, new ImageLinkProvider_1.ImageLinkProvider());
}
if (altYamlCapabilities.formatting) {
this._capabilities.documentFormattingProvider = undefined;
}
else {
this.createLspHandler(this.connection.onDocumentFormatting, new DocumentFormattingProvider_1.DocumentFormattingProvider());
}
// End of LSP listeners
// Hook up one additional notification handler
this.connection.onNotification(DocumentSettings_1.DocumentSettingsNotification.method, (params) => this.onDidChangeDocumentSettings(params));
// Start the document listener
this.documentManager.listen(this.connection);
// Start the telemetry aggregator
this.subscriptions.push(this.telemetryAggregator = new TelemetryAggregator_1.TelemetryAggregator(this.connection, (_c = clientParams.initializationOptions) === null || _c === void 0 ? void 0 : _c.telemetryAggregationInterval));
}
dispose() {
for (const subscription of this.subscriptions) {
subscription.dispose();
}
}
get capabilities() {
return this._capabilities;
}
onDidChangeDocumentSettings(params) {
// TODO: Telemetrize this?
const composeDoc = this.documentManager.get(params.textDocument.uri);
if (composeDoc) {
composeDoc.updateSettings(params);
}
}
createLspHandler(event, handler) {
event(async (params, token, workDoneProgress, resultProgress) => {
return await this.callWithTelemetryAndErrorHandling(handler.constructor.name, async () => {
const doc = this.documentManager.get(params.textDocument.uri);
if (!doc) {
throw new vscode_languageserver_1.ResponseError(vscode_languageserver_1.ErrorCodes.InvalidParams, 'Document not found in cache.');
}
const extendedParams = {
...params,
document: doc,
};
return await Promise.resolve(handler.on(extendedParams, token, workDoneProgress, resultProgress));
});
});
}
createDocumentManagerHandler(event, handler) {
event(async (params) => {
return await this.callWithTelemetryAndErrorHandling(handler.constructor.name, async () => {
const extendedParams = {
...params,
textDocument: params.document.id,
};
return await Promise.resolve(handler.on(extendedParams, vscode_languageserver_1.CancellationToken.None));
});
}, this, this.subscriptions);
}
async callWithTelemetryAndErrorHandling(callbackId, callback) {
const actionContext = {
clientCapabilities: this.clientParams.capabilities,
connection: this.connection,
telemetry: (0, TelemetryEvent_1.initEvent)(callbackId),
};
const startTime = process.hrtime.bigint();
try {
return await (0, ActionContext_1.runWithContext)(actionContext, callback);
}
catch (error) {
let responseError;
let stack;
if (error instanceof vscode_languageserver_1.ResponseError) {
responseError = error;
stack = error.stack;
}
else if (error instanceof Error) {
responseError = new vscode_languageserver_1.ResponseError(vscode_languageserver_1.ErrorCodes.UnknownErrorCode, error.message, error);
stack = error.stack;
}
else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
responseError = new vscode_languageserver_1.ResponseError(vscode_languageserver_1.ErrorCodes.InternalError, error.toString ? error.toString() : 'Unknown error');
}
actionContext.telemetry.properties.result = 'Failed';
actionContext.telemetry.properties.error = responseError.code.toString();
actionContext.telemetry.properties.errorMessage = responseError.message;
actionContext.telemetry.properties.stack = stack;
return responseError;
}
finally {
const endTime = process.hrtime.bigint();
const elapsedMicroseconds = Number((endTime - startTime) / BigInt(1000));
actionContext.telemetry.measurements.duration = elapsedMicroseconds;
// The aggregator will internally handle suppressing / etc.
this.telemetryAggregator.logEvent(actionContext.telemetry);
}
}
}
exports.ComposeLanguageService = ComposeLanguageService;
//# sourceMappingURL=ComposeLanguageService.js.map