in src/languageclient/startArmLanguageServer.ts [136:294]
await callWithTelemetryAndErrorHandling('startArmLanguageClient', async (actionContext: IActionContext) => {
actionContext.errorHandling.rethrow = true;
actionContext.errorHandling.suppressDisplay = true; // Let caller handle
// These trace levels are available in the server:
// Trace
// Debug
// Information
// Warning
// Error
// Critical
// None
let trace: string = workspace.getConfiguration(configPrefix).get<string>(configKeys.traceLevel)
// tslint:disable-next-line: strict-boolean-expressions
|| defaultTraceLevel;
let commonArgs = [
serverDllPath,
'--logLevel',
trace
];
const waitForDebugger = workspace.getConfiguration(configPrefix).get<boolean>(configKeys.waitForDebugger, false) === true;
if (waitForDebugger) {
commonArgs.push('--wait-for-debugger');
}
if (isRunningTests || ext.addCompletedDiagnostic) {
// Forces the server to add a completion message to its diagnostics
commonArgs.push('--verbose-diagnostics');
}
// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
let serverOptions: ServerOptions = {
run: { command: dotnetExePath, args: commonArgs, options: { shell: false } },
debug: { command: dotnetExePath, args: commonArgs, options: { shell: false } },
};
// Options to control the language client
let clientOptions: LanguageClientOptions = {
documentSelector: templateDocumentSelector,
diagnosticCollectionName: `${languageServerName} diagnostics`,
outputChannel: ext.outputChannel, // Use the same output channel as the extension does
revealOutputChannelOn: RevealOutputChannelOn.Error,
synchronize: {
configurationSection: configPrefix
},
middleware: {
handleDiagnostics: (uri: Uri, diagnostics: Diagnostic[], next: (uri: Uri, diagnostics: Diagnostic[]) => void): void => {
for (const d of diagnostics) {
if (d.source === backendValidationDiagnosticsSource) {
convertDiagnosticUrisToLinkedTemplateSchema(d);
}
}
next(uri, diagnostics);
},
provideCompletionItem: async (document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: (document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken) => undefined | null | CompletionItem[] | CompletionList | Thenable<undefined | null | CompletionItem[] | CompletionList>): Promise<undefined | null | CompletionItem[] | CompletionList> => {
let result: CompletionItem[] | CompletionList | undefined | null = await next(document, position, context, token);
if (result) {
let items: CompletionList | CompletionItem[] = result;
let isIncomplete: boolean = false;
if (items instanceof CompletionList) {
isIncomplete = items.isIncomplete ?? false;
items = items.items;
}
if (items.every(item => typeof item.label === "string" && isApiVersion(item.label))) {
// It's a list of apiVersion completions
// Show them in reverse order so the newest is at the top of the list
const countItems = items.length;
items = items.map((ci, index) => {
if (!ci.sortText) {
let sortText = (countItems - index).toString(10);
sortText = sortText.padStart(10 - sortText.length, '0');
ci.sortText = sortText;
}
return ci;
});
result = new CompletionList(items, isIncomplete);
}
}
return result;
},
}
};
// Create the language client and start the client.
// tslint:disable-next-line: strict-boolean-expressions
const langServerVersion = (await getLangServerVersion()) || "Unknown";
actionContext.telemetry.properties.langServerNugetVersion = langServerVersion;
ext.outputChannel.appendLine(`Starting ${languageServerName} at ${serverDllPath}`);
ext.outputChannel.appendLine(`Language server nuget version: ${langServerVersion}`);
ext.outputChannel.appendLine(`Client options:${os.EOL}${JSON.stringify(clientOptions, undefined, 2)}`);
ext.outputChannel.appendLine(`Server options:${os.EOL}${JSON.stringify(serverOptions, undefined, 2)}`);
let client: LanguageClient = new LanguageClient(
armTemplateLanguageId,
languageFriendlyName, // Used in the Output window combobox
serverOptions,
clientOptions,
);
// Use an error handler that sends telemetry
let defaultHandler = client.createDefaultErrorHandler();
client.clientOptions.errorHandler = new WrappedErrorHandler(defaultHandler);
if (waitForDebugger) {
window.showWarningMessage(`The ${configPrefix}.languageServer.waitForDebugger option is set. The language server will pause on startup until a debugger is attached.`);
}
client.onTelemetry((telemetryData: { eventName: string; properties: { [key: string]: string | undefined } }) => {
const eventName = telemetryData.eventName.replace(/^\/|\/$/g, ""); // Remove slashes at beginning or end
const fullEventName = `langserver/${eventName}`;
if (sanitizeTelemetryData(fullEventName, telemetryData.properties)
) {
callWithTelemetryAndErrorHandlingSync(fullEventName, telemetryActionContext => {
telemetryActionContext.errorHandling.suppressDisplay = true;
for (let prop of Object.getOwnPropertyNames(telemetryData.properties)) {
const value = telemetryData.properties[prop];
prop = prop.replace(/^\./g, ""); // Remove starting period
telemetryActionContext.telemetry.properties[prop] = String(value);
}
if (telemetryActionContext.telemetry.properties.error) {
telemetryActionContext.telemetry.properties.result = 'Failed';
}
});
}
});
try {
// client.trace = Trace.Messages;
let disposable = client.start();
ext.context.subscriptions.push(disposable);
await client.onReady();
ext.languageServerClient = client;
client.onRequest(notifications.requestOpenLinkedTemplate, async (args: IRequestOpenLinkedFileArgs) => {
return onRequestOpenLinkedFile(args);
});
client.onNotification(notifications.notifyTemplateGraph, async (args: INotifyTemplateGraphArgs) => {
onNotifyTemplateGraph(args);
});
client.onNotification(notifications.schemaValidationNotification, async (args: notifications.ISchemaValidationNotificationArgs) => {
onSchemaValidationNotication(args);
});
} catch (error) {
throw new Error(
`${languageServerName}: An error occurred starting the language server.${os.EOL}${os.EOL}${parseError(error).message}`
);
}
});