From c7e83f71a4d8322709b5cd8ef216fc77cd6b5f80 Mon Sep 17 00:00:00 2001 From: buravc Date: Wed, 9 Oct 2024 21:34:43 +0200 Subject: [PATCH 1/2] src/goCodeLens: show codelens for implementations of symbols This change adds implementations as a codelens to abstract and concrete symbols. Fixes #56695 --- docs/settings.md | 2 + extension/package.json | 5 + extension/src/goCodeLens.ts | 155 +++++++++++++++++++++ extension/src/goMain.ts | 2 + extension/src/language/goLanguageServer.ts | 39 +++++- 5 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 extension/src/goCodeLens.ts diff --git a/docs/settings.md b/docs/settings.md index aa55b8298e..bded92b5b4 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -211,11 +211,13 @@ Feature level setting to enable/disable code lens for references and run/debug t | Properties | Description | | --- | --- | | `runtest` | If true, enables code lens for running and debugging tests
Default: `true` | +| `implementation` | If true, enables code lens for showing implementations
Default: `true` | Default: ``` { "runtest" : true, + "implementation" : true, } ``` ### `go.formatFlags` diff --git a/extension/package.json b/extension/package.json index 6306554f46..f466ae8d28 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1551,6 +1551,11 @@ "type": "boolean", "default": true, "description": "If true, enables code lens for running and debugging tests" + }, + "implementation": { + "type": "boolean", + "default": true, + "description": "If true, enables code lens for showing implementations" } }, "additionalProperties": false, diff --git a/extension/src/goCodeLens.ts b/extension/src/goCodeLens.ts new file mode 100644 index 0000000000..b2533ad491 --- /dev/null +++ b/extension/src/goCodeLens.ts @@ -0,0 +1,155 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------*/ + +'use strict'; + +import vscode = require('vscode'); +import { CancellationToken, CodeLens, TextDocument } from 'vscode'; +import { getGoConfig } from './config'; +import { GoBaseCodeLensProvider } from './goBaseCodelens'; +import { GoDocumentSymbolProvider } from './goDocumentSymbols'; +import { GoExtensionContext } from './context'; +import { GO_MODE } from './goMode'; +import { getSymbolImplementations } from './language/goLanguageServer'; + +export class GoCodeLensProvider extends GoBaseCodeLensProvider { + static activate(ctx: vscode.ExtensionContext, goCtx: GoExtensionContext) { + const codeLensProvider = new this(goCtx); + ctx.subscriptions.push(vscode.languages.registerCodeLensProvider(GO_MODE, codeLensProvider)); + ctx.subscriptions.push( + vscode.workspace.onDidChangeConfiguration(async (e: vscode.ConfigurationChangeEvent) => { + if (!e.affectsConfiguration('go')) { + return; + } + const updatedGoConfig = getGoConfig(); + if (updatedGoConfig['enableCodeLens']) { + codeLensProvider.setEnabled(updatedGoConfig['enableCodeLens']['implementation']); + } + }) + ); + } + + constructor(private readonly goCtx: GoExtensionContext) { + super(); + } + + public async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise { + if (!this.enabled) { + return []; + } + const config = getGoConfig(document.uri); + const codeLensConfig = config.get<{ [key: string]: any }>('enableCodeLens'); + const codelensEnabled = codeLensConfig ? codeLensConfig['implementation'] : false; + if (!codelensEnabled || !document.fileName.endsWith('.go')) { + return []; + } + + const abstractCodelenses = this.getCodeLensForAbstractSymbols(document, token); + const concreteCodelenses = this.getCodeLensForConcreteSymbols(document, token); + + const codeLenses = await Promise.all([abstractCodelenses, concreteCodelenses]); + + return codeLenses.flat(); + } + + private async getCodeLensForConcreteSymbols(document: TextDocument, token: CancellationToken): Promise { + const concreteTypes = await this.getConcreteTypes(document); + if (concreteTypes && concreteTypes.length) { + const concreteTypesCodeLens = await this.mapSymbolsToCodeLenses(document, concreteTypes); + return concreteTypesCodeLens; + } + + return []; + } + + private async getCodeLensForAbstractSymbols(document: TextDocument, token: CancellationToken): Promise { + const interfaces = await this.getInterfaces(document); + if (interfaces && interfaces.length) { + const interfacesCodeLens = this.mapSymbolsToCodeLenses(document, interfaces); + + const methodsCodeLens = this.mapSymbolsToCodeLenses( + document, + interfaces.flatMap((i) => i.children) + ); + + const codeLenses = await Promise.all([interfacesCodeLens, methodsCodeLens]); + + return codeLenses.flat(); + } + return []; + } + + private async getInterfaces(document: TextDocument): Promise { + const documentSymbolProvider = GoDocumentSymbolProvider(this.goCtx); + const symbols = await documentSymbolProvider.provideDocumentSymbols(document); + if (!symbols || symbols.length === 0) { + return []; + } + const pkg = symbols[0]; + if (!pkg) { + return []; + } + const children = pkg.children; + const interfaces = children.filter((s) => s.kind === vscode.SymbolKind.Interface); + if (!interfaces) { + return []; + } + + return interfaces; + } + + private async getConcreteTypes(document: TextDocument): Promise { + const documentSymbolProvider = GoDocumentSymbolProvider(this.goCtx); + const symbols = await documentSymbolProvider.provideDocumentSymbols(document); + if (!symbols || symbols.length === 0) { + return []; + } + const pkg = symbols[0]; + if (!pkg) { + return []; + } + const children = pkg.children; + const concreteTypes = children.filter((s) => + [vscode.SymbolKind.Struct, vscode.SymbolKind.Method].includes(s.kind) + ); + if (!concreteTypes) { + return []; + } + + return concreteTypes; + } + + private async mapSymbolsToCodeLenses( + document: vscode.TextDocument, + symbols: vscode.DocumentSymbol[] + ): Promise { + return Promise.all( + symbols.map(async (s) => { + const implementations = await this.getImplementations(document, s); + if (implementations.length) { + return new CodeLens(s.range, { + title: `${implementations.length} implementation${implementations.length > 1 ? 's' : ''}`, + command: 'editor.action.goToLocations', + arguments: [document.uri, s.range.start, implementations, 'peek'] + }); + } + + return new CodeLens(s.range, { + title: 'no implementation found', + command: '' + }); + }) + ); + } + + private async getImplementations( + document: vscode.TextDocument, + symbol: vscode.DocumentSymbol + ): Promise { + return getSymbolImplementations(this.goCtx, document, symbol); + } +} diff --git a/extension/src/goMain.ts b/extension/src/goMain.ts index 015b1eb13a..58a1d7cc79 100644 --- a/extension/src/goMain.ts +++ b/extension/src/goMain.ts @@ -73,6 +73,7 @@ import * as commands from './commands'; import { toggleVulncheckCommandFactory } from './goVulncheck'; import { GoTaskProvider } from './goTaskProvider'; import { setTelemetryEnvVars, telemetryReporter } from './goTelemetry'; +import { GoCodeLensProvider } from './goCodeLens'; const goCtx: GoExtensionContext = {}; @@ -144,6 +145,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { + const languageClient = goCtx.languageClient; + if (languageClient) { + const params = { + textDocument: { uri: document.uri.toString() }, + position: { + line: symbol.selectionRange.start.line, + character: symbol.selectionRange.start.character + } + }; + try { + const implementations = await languageClient.sendRequest('textDocument/implementation', params); + return ( + implementations.map( + (i) => + new vscode.Location( + vscode.Uri.parse(i.uri), + new vscode.Range( + new vscode.Position(i.range.start.line, i.range.start.character), + new vscode.Position(i.range.end.line, i.range.end.character) + ) + ) + ) || [] + ); + } catch (error) { + console.error(`unable to get implementations for ${symbol.name}:`, error); + return []; + } + } + return []; +} From 7df4c91358027e3f10e2fd1ca45111d46800670a Mon Sep 17 00:00:00 2001 From: buravc Date: Sun, 13 Oct 2024 22:49:09 +0200 Subject: [PATCH 2/2] src/goCodeLens: add prefetchImpl config to split heavy code lens impl logic --- docs/settings.md | 2 + extension/package.json | 9 ++- extension/src/goCodeLens.ts | 81 ++++++++++++++++------ extension/src/language/goLanguageServer.ts | 22 +++--- 4 files changed, 80 insertions(+), 34 deletions(-) diff --git a/docs/settings.md b/docs/settings.md index bded92b5b4..c6987fe500 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -212,12 +212,14 @@ Feature level setting to enable/disable code lens for references and run/debug t | --- | --- | | `runtest` | If true, enables code lens for running and debugging tests
Default: `true` | | `implementation` | If true, enables code lens for showing implementations
Default: `true` | +| `prefetchImpls` | If true, enables code lens for showing implementations
Default: `false` | Default: ``` { "runtest" : true, "implementation" : true, + "prefetchImpls" : false, } ``` ### `go.formatFlags` diff --git a/extension/package.json b/extension/package.json index f466ae8d28..6475b471e9 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1556,11 +1556,18 @@ "type": "boolean", "default": true, "description": "If true, enables code lens for showing implementations" + }, + "prefetchImpls": { + "type": "boolean", + "default": false, + "description": "If true, shows implementation count in the code lens text" } }, "additionalProperties": false, "default": { - "runtest": true + "runtest": true, + "implementation": true, + "prefetchImpls": false }, "description": "Feature level setting to enable/disable code lens for references and run/debug tests", "scope": "resource" diff --git a/extension/src/goCodeLens.ts b/extension/src/goCodeLens.ts index b2533ad491..80154d443b 100644 --- a/extension/src/goCodeLens.ts +++ b/extension/src/goCodeLens.ts @@ -31,6 +31,10 @@ export class GoCodeLensProvider extends GoBaseCodeLensProvider { } }) ); + + codeLensProvider.goToImplementations = codeLensProvider.goToImplementations.bind(codeLensProvider); + + vscode.commands.registerCommand('go.codeLens.goToImplementations', codeLensProvider.goToImplementations); } constructor(private readonly goCtx: GoExtensionContext) { @@ -48,32 +52,42 @@ export class GoCodeLensProvider extends GoBaseCodeLensProvider { return []; } - const abstractCodelenses = this.getCodeLensForAbstractSymbols(document, token); - const concreteCodelenses = this.getCodeLensForConcreteSymbols(document, token); + const prefetchImpls = codeLensConfig ? codeLensConfig['prefetchImpls'] : false; - const codeLenses = await Promise.all([abstractCodelenses, concreteCodelenses]); + const abstractCodelenses = this.getCodeLensForAbstractSymbols(document, token, prefetchImpls); + const concreteCodelenses = this.getCodeLensForConcreteSymbols(document, token, prefetchImpls); + const codeLenses = await Promise.all([abstractCodelenses, concreteCodelenses]); return codeLenses.flat(); } - private async getCodeLensForConcreteSymbols(document: TextDocument, token: CancellationToken): Promise { + private async getCodeLensForConcreteSymbols( + document: TextDocument, + token: CancellationToken, + prefetchImpls: boolean + ): Promise { const concreteTypes = await this.getConcreteTypes(document); if (concreteTypes && concreteTypes.length) { - const concreteTypesCodeLens = await this.mapSymbolsToCodeLenses(document, concreteTypes); + const concreteTypesCodeLens = await this.mapSymbolsToCodeLenses(document, concreteTypes, prefetchImpls); return concreteTypesCodeLens; } return []; } - private async getCodeLensForAbstractSymbols(document: TextDocument, token: CancellationToken): Promise { + private async getCodeLensForAbstractSymbols( + document: TextDocument, + token: CancellationToken, + prefetchImpls: boolean + ): Promise { const interfaces = await this.getInterfaces(document); if (interfaces && interfaces.length) { - const interfacesCodeLens = this.mapSymbolsToCodeLenses(document, interfaces); + const interfacesCodeLens = this.mapSymbolsToCodeLenses(document, interfaces, prefetchImpls); const methodsCodeLens = this.mapSymbolsToCodeLenses( document, - interfaces.flatMap((i) => i.children) + interfaces.flatMap((i) => i.children), + prefetchImpls ); const codeLenses = await Promise.all([interfacesCodeLens, methodsCodeLens]); @@ -125,24 +139,47 @@ export class GoCodeLensProvider extends GoBaseCodeLensProvider { private async mapSymbolsToCodeLenses( document: vscode.TextDocument, - symbols: vscode.DocumentSymbol[] + symbols: vscode.DocumentSymbol[], + prefetchImpls: boolean ): Promise { - return Promise.all( - symbols.map(async (s) => { - const implementations = await this.getImplementations(document, s); - if (implementations.length) { + if (prefetchImpls) { + return Promise.all( + symbols.map(async (s) => { + const implementations = await this.getImplementations(document, s); + if (implementations.length) { + return new CodeLens(s.range, { + title: `${implementations.length} implementation${implementations.length > 1 ? 's' : ''}`, + command: 'editor.action.goToLocations', + arguments: [document.uri, s.range.start, implementations, 'peek'] + }); + } + return new CodeLens(s.range, { - title: `${implementations.length} implementation${implementations.length > 1 ? 's' : ''}`, - command: 'editor.action.goToLocations', - arguments: [document.uri, s.range.start, implementations, 'peek'] + title: 'no implementation found', + command: '' }); - } + }) + ); + } - return new CodeLens(s.range, { - title: 'no implementation found', - command: '' - }); - }) + return symbols.map((s) => { + return new CodeLens(s.range, { + title: 'implementations', + command: 'go.codeLens.goToImplementations', + arguments: [document, s] + }); + }); + } + + private async goToImplementations(document: vscode.TextDocument, symbol: vscode.DocumentSymbol) { + const implementations = await this.getImplementations(document, symbol); + await vscode.commands.executeCommand( + 'editor.action.goToLocations', + document.uri, + symbol.range.start, + implementations, + 'peek', + 'No implementation found' ); } diff --git a/extension/src/language/goLanguageServer.ts b/extension/src/language/goLanguageServer.ts index 57ce16b2ec..b31467a051 100644 --- a/extension/src/language/goLanguageServer.ts +++ b/extension/src/language/goLanguageServer.ts @@ -1683,18 +1683,18 @@ export async function getSymbolImplementations( }; try { const implementations = await languageClient.sendRequest('textDocument/implementation', params); - return ( - implementations.map( - (i) => - new vscode.Location( - vscode.Uri.parse(i.uri), - new vscode.Range( - new vscode.Position(i.range.start.line, i.range.start.character), - new vscode.Position(i.range.end.line, i.range.end.character) + return implementations + ? implementations.map( + (i) => + new vscode.Location( + vscode.Uri.parse(i.uri), + new vscode.Range( + new vscode.Position(i.range.start.line, i.range.start.character), + new vscode.Position(i.range.end.line, i.range.end.character) + ) ) - ) - ) || [] - ); + ) + : []; } catch (error) { console.error(`unable to get implementations for ${symbol.name}:`, error); return [];