in src/goLanguageServer.ts [486:753]
export async function buildLanguageClient(cfg: BuildLanguageClientOption): Promise<LanguageClient> {
const goplsWorkspaceConfig = await adjustGoplsWorkspaceConfiguration(cfg, getGoplsConfig(), 'gopls', undefined);
const documentSelector = [
// gopls handles only file URIs.
{ language: 'go', scheme: 'file' },
{ language: 'go.mod', scheme: 'file' },
{ language: 'go.sum', scheme: 'file' },
{ language: 'go.work', scheme: 'file' },
{ language: 'gotmpl', scheme: 'file' }
];
const c = new LanguageClient(
'go', // id
cfg.serverName, // name e.g. gopls
{
command: cfg.path,
args: ['-mode=stdio', ...cfg.flags],
options: { env: cfg.env }
},
{
initializationOptions: goplsWorkspaceConfig,
documentSelector,
uriConverters: {
// Apply file:/// scheme to all file paths.
code2Protocol: (uri: vscode.Uri): string =>
(uri.scheme ? uri : uri.with({ scheme: 'file' })).toString(),
protocol2Code: (uri: string) => vscode.Uri.parse(uri)
},
outputChannel: cfg.outputChannel,
traceOutputChannel: cfg.traceOutputChannel,
revealOutputChannelOn: RevealOutputChannelOn.Never,
initializationFailedHandler: (error: WebRequest.ResponseError<InitializeError>): boolean => {
vscode.window.showErrorMessage(
`The language server is not able to serve any features. Initialization failed: ${error}. `
);
suggestGoplsIssueReport(
'The gopls server failed to initialize',
errorKind.initializationFailure,
error
);
return false;
},
errorHandler: {
error: (error: Error, message: Message, count: number): ErrorAction => {
// Allow 5 crashes before shutdown.
if (count < 5) {
return ErrorAction.Continue;
}
vscode.window.showErrorMessage(
`Error communicating with the language server: ${error}: ${message}.`
);
return ErrorAction.Shutdown;
},
closed: (): CloseAction => {
// Allow 5 crashes before shutdown.
crashCount++;
if (crashCount < 5) {
return CloseAction.Restart;
}
suggestGoplsIssueReport(
'The connection to gopls has been closed. The gopls server may have crashed.',
errorKind.crash
);
return CloseAction.DoNotRestart;
}
},
middleware: {
executeCommand: async (command: string, args: any[], next: ExecuteCommandSignature) => {
try {
return await next(command, args);
} catch (e) {
const answer = await vscode.window.showErrorMessage(
`Command '${command}' failed: ${e}.`,
'Show Trace'
);
if (answer === 'Show Trace') {
serverOutputChannel.show();
}
return null;
}
},
provideFoldingRanges: async (
doc: vscode.TextDocument,
context: FoldingContext,
token: CancellationToken,
next: ProvideFoldingRangeSignature
) => {
const ranges = await next(doc, context, token);
if ((!ranges || ranges.length === 0) && doc.lineCount > 0) {
return undefined;
}
return ranges;
},
provideCodeLenses: async (
doc: vscode.TextDocument,
token: vscode.CancellationToken,
next: ProvideCodeLensesSignature
): Promise<vscode.CodeLens[]> => {
const codeLens = await next(doc, token);
if (!codeLens || codeLens.length === 0) {
return codeLens;
}
return codeLens.reduce((lenses: vscode.CodeLens[], lens: vscode.CodeLens) => {
switch (lens.command.title) {
case 'run test': {
return [...lenses, ...createTestCodeLens(lens)];
}
case 'run benchmark': {
return [...lenses, ...createBenchmarkCodeLens(lens)];
}
default: {
return [...lenses, lens];
}
}
}, []);
},
provideDocumentFormattingEdits: async (
document: vscode.TextDocument,
options: vscode.FormattingOptions,
token: vscode.CancellationToken,
next: ProvideDocumentFormattingEditsSignature
) => {
if (cfg.features.formatter) {
return cfg.features.formatter.provideDocumentFormattingEdits(document, options, token);
}
return next(document, options, token);
},
handleDiagnostics: (
uri: vscode.Uri,
diagnostics: vscode.Diagnostic[],
next: HandleDiagnosticsSignature
) => {
if (!cfg.features.diagnostics) {
return null;
}
// Deduplicate diagnostics with those found by the other tools.
removeDuplicateDiagnostics(vetDiagnosticCollection, uri, diagnostics);
removeDuplicateDiagnostics(buildDiagnosticCollection, uri, diagnostics);
removeDuplicateDiagnostics(lintDiagnosticCollection, uri, diagnostics);
return next(uri, diagnostics);
},
provideCompletionItem: async (
document: vscode.TextDocument,
position: vscode.Position,
context: vscode.CompletionContext,
token: vscode.CancellationToken,
next: ProvideCompletionItemsSignature
) => {
const list = await next(document, position, context, token);
if (!list) {
return list;
}
const items = Array.isArray(list) ? list : list.items;
// Give all the candidates the same filterText to trick VSCode
// into not reordering our candidates. All the candidates will
// appear to be equally good matches, so VSCode's fuzzy
// matching/ranking just maintains the natural "sortText"
// ordering. We can only do this in tandem with
// "incompleteResults" since otherwise client side filtering is
// important.
if (!Array.isArray(list) && list.isIncomplete && list.items.length > 1) {
let hardcodedFilterText = items[0].filterText;
if (!hardcodedFilterText) {
// tslint:disable:max-line-length
// According to LSP spec,
// https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
// if filterText is falsy, the `label` should be used.
// But we observed that's not the case.
// Even if vscode picked the label value, that would
// cause to reorder candiates, which is not ideal.
// Force to use non-empty `label`.
// https://github.com/golang/vscode-go/issues/441
let { label } = items[0];
if (typeof label !== 'string') label = label.label;
hardcodedFilterText = label;
}
for (const item of items) {
item.filterText = hardcodedFilterText;
}
}
// TODO(hyangah): when v1.42+ api is available, we can simplify
// language-specific configuration lookup using the new
// ConfigurationScope.
// const paramHintsEnabled = vscode.workspace.getConfiguration(
// 'editor.parameterHints',
// { languageId: 'go', uri: document.uri });
const editorParamHintsEnabled = vscode.workspace.getConfiguration(
'editor.parameterHints',
document.uri
)['enabled'];
const goParamHintsEnabled = vscode.workspace.getConfiguration('[go]', document.uri)[
'editor.parameterHints.enabled'
];
let paramHintsEnabled = false;
if (typeof goParamHintsEnabled === 'undefined') {
paramHintsEnabled = editorParamHintsEnabled;
} else {
paramHintsEnabled = goParamHintsEnabled;
}
// If the user has parameterHints (signature help) enabled,
// trigger it for function or method completion items.
if (paramHintsEnabled) {
for (const item of items) {
if (item.kind === CompletionItemKind.Method || item.kind === CompletionItemKind.Function) {
item.command = {
title: 'triggerParameterHints',
command: 'editor.action.triggerParameterHints'
};
}
}
}
return list;
},
// Keep track of the last file change in order to not prompt
// user if they are actively working.
didOpen: (e, next) => {
lastUserAction = new Date();
next(e);
},
didChange: (e, next) => {
lastUserAction = new Date();
next(e);
},
didClose: (e, next) => {
lastUserAction = new Date();
next(e);
},
didSave: (e, next) => {
lastUserAction = new Date();
next(e);
},
workspace: {
configuration: async (
params: ConfigurationParams,
token: CancellationToken,
next: ConfigurationRequest.HandlerSignature
): Promise<any[] | ResponseError<void>> => {
const configs = await next(params, token);
if (!configs || !Array.isArray(configs)) {
return configs;
}
const ret = [] as any[];
for (let i = 0; i < configs.length; i++) {
let workspaceConfig = configs[i];
if (!!workspaceConfig && typeof workspaceConfig === 'object') {
const scopeUri = params.items[i].scopeUri;
const resource = scopeUri ? vscode.Uri.parse(scopeUri) : undefined;
const section = params.items[i].section;
workspaceConfig = await adjustGoplsWorkspaceConfiguration(
cfg,
workspaceConfig,
section,
resource
);
}
ret.push(workspaceConfig);
}
return ret;
}
}
}
}
);
return c;
}