Skip to content

Commit b9c5af8

Browse files
committed
src/goCodeLens: show codelens for implementations of symbols
This change adds `implementations` as a codelens to abstract and concrete symbols Fixes #56695
1 parent c107653 commit b9c5af8

File tree

5 files changed

+202
-1
lines changed

5 files changed

+202
-1
lines changed

docs/settings.md

+2
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,13 @@ Feature level setting to enable/disable code lens for references and run/debug t
211211
| Properties | Description |
212212
| --- | --- |
213213
| `runtest` | If true, enables code lens for running and debugging tests <br/> Default: `true` |
214+
| `implementation` | If true, enables code lens for showing implementations <br/> Default: `true` |
214215

215216
Default:
216217
```
217218
{
218219
"runtest" : true,
220+
"implementation" : true,
219221
}
220222
```
221223
### `go.formatFlags`

extension/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -1551,6 +1551,11 @@
15511551
"type": "boolean",
15521552
"default": true,
15531553
"description": "If true, enables code lens for running and debugging tests"
1554+
},
1555+
"implementation": {
1556+
"type": "boolean",
1557+
"default": true,
1558+
"description": "If true, enables code lens for showing implementations"
15541559
}
15551560
},
15561561
"additionalProperties": false,

extension/src/goCodeLens.ts

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
/* eslint-disable @typescript-eslint/no-explicit-any */
3+
/*---------------------------------------------------------
4+
* Copyright (C) Microsoft Corporation. All rights reserved.
5+
* Licensed under the MIT License. See LICENSE in the project root for license information.
6+
*--------------------------------------------------------*/
7+
8+
'use strict';
9+
10+
import vscode = require('vscode');
11+
import { CancellationToken, CodeLens, TextDocument } from 'vscode';
12+
import { getGoConfig } from './config';
13+
import { GoBaseCodeLensProvider } from './goBaseCodelens';
14+
import { GoDocumentSymbolProvider } from './goDocumentSymbols';
15+
import { GoExtensionContext } from './context';
16+
import { GO_MODE } from './goMode';
17+
import { getSymbolImplementations } from './language/goLanguageServer';
18+
19+
export class GoCodeLensProvider extends GoBaseCodeLensProvider {
20+
static activate(ctx: vscode.ExtensionContext, goCtx: GoExtensionContext) {
21+
const codeLensProvider = new this(goCtx);
22+
ctx.subscriptions.push(vscode.languages.registerCodeLensProvider(GO_MODE, codeLensProvider));
23+
ctx.subscriptions.push(
24+
vscode.workspace.onDidChangeConfiguration(async (e: vscode.ConfigurationChangeEvent) => {
25+
if (!e.affectsConfiguration('go')) {
26+
return;
27+
}
28+
const updatedGoConfig = getGoConfig();
29+
if (updatedGoConfig['enableCodeLens']) {
30+
codeLensProvider.setEnabled(updatedGoConfig['enableCodeLens']['implementation']);
31+
}
32+
})
33+
);
34+
}
35+
36+
constructor(private readonly goCtx: GoExtensionContext) {
37+
super();
38+
}
39+
40+
public async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
41+
if (!this.enabled) {
42+
return [];
43+
}
44+
const config = getGoConfig(document.uri);
45+
const codeLensConfig = config.get<{ [key: string]: any }>('enableCodeLens');
46+
const codelensEnabled = codeLensConfig ? codeLensConfig['implementation'] : false;
47+
if (!codelensEnabled || !document.fileName.endsWith('.go')) {
48+
return [];
49+
}
50+
51+
const abstractCodelenses = this.getCodeLensForAbstractSymbols(document, token);
52+
const concreteCodelenses = this.getCodeLensForConcreteSymbols(document, token);
53+
54+
const codeLenses = await Promise.all([abstractCodelenses, concreteCodelenses]);
55+
56+
return codeLenses.flat();
57+
}
58+
59+
private async getCodeLensForConcreteSymbols(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
60+
const concreteTypes = await this.getConcreteTypes(document);
61+
if (concreteTypes && concreteTypes.length) {
62+
const concreteTypesCodeLens = await this.mapSymbolsToCodeLenses(document, concreteTypes);
63+
return concreteTypesCodeLens;
64+
}
65+
66+
return [];
67+
}
68+
69+
private async getCodeLensForAbstractSymbols(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
70+
const interfaces = await this.getInterfaces(document);
71+
if (interfaces && interfaces.length) {
72+
const interfacesCodeLens = this.mapSymbolsToCodeLenses(document, interfaces);
73+
74+
const methodsCodeLens = this.mapSymbolsToCodeLenses(
75+
document,
76+
interfaces.flatMap((i) => i.children)
77+
);
78+
79+
const codeLenses = await Promise.all([interfacesCodeLens, methodsCodeLens]);
80+
81+
return codeLenses.flat();
82+
}
83+
return [];
84+
}
85+
86+
private async getInterfaces(document: TextDocument): Promise<vscode.DocumentSymbol[]> {
87+
const documentSymbolProvider = GoDocumentSymbolProvider(this.goCtx);
88+
const symbols = await documentSymbolProvider.provideDocumentSymbols(document);
89+
if (!symbols || symbols.length === 0) {
90+
return [];
91+
}
92+
const pkg = symbols[0];
93+
if (!pkg) {
94+
return [];
95+
}
96+
const children = pkg.children;
97+
const interfaces = children.filter((s) => s.kind === vscode.SymbolKind.Interface);
98+
if (!interfaces) {
99+
return [];
100+
}
101+
102+
return interfaces;
103+
}
104+
105+
private async getConcreteTypes(document: TextDocument): Promise<vscode.DocumentSymbol[]> {
106+
const documentSymbolProvider = GoDocumentSymbolProvider(this.goCtx);
107+
const symbols = await documentSymbolProvider.provideDocumentSymbols(document);
108+
if (!symbols || symbols.length === 0) {
109+
return [];
110+
}
111+
const pkg = symbols[0];
112+
if (!pkg) {
113+
return [];
114+
}
115+
const children = pkg.children;
116+
const concreteTypes = children.filter((s) =>
117+
[vscode.SymbolKind.Struct, vscode.SymbolKind.Method].includes(s.kind)
118+
);
119+
if (!concreteTypes) {
120+
return [];
121+
}
122+
123+
return concreteTypes;
124+
}
125+
126+
private async mapSymbolsToCodeLenses(
127+
document: vscode.TextDocument,
128+
symbols: vscode.DocumentSymbol[]
129+
): Promise<vscode.CodeLens[]> {
130+
return Promise.all(
131+
symbols.map(async (s) => {
132+
const implementations = await this.getImplementations(document, s);
133+
if (implementations.length) {
134+
return new CodeLens(s.range, {
135+
title: `${implementations.length} implementation${implementations.length > 1 ? 's' : ''}`,
136+
command: 'editor.action.goToLocations',
137+
arguments: [document.uri, s.range.start, implementations, 'peek']
138+
});
139+
}
140+
141+
return new CodeLens(s.range, {
142+
title: 'no implementation found',
143+
command: ''
144+
});
145+
})
146+
);
147+
}
148+
149+
private async getImplementations(
150+
document: vscode.TextDocument,
151+
symbol: vscode.DocumentSymbol
152+
): Promise<vscode.Location[]> {
153+
return getSymbolImplementations(this.goCtx, document, symbol);
154+
}
155+
}

extension/src/goMain.ts

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import * as commands from './commands';
7373
import { toggleVulncheckCommandFactory } from './goVulncheck';
7474
import { GoTaskProvider } from './goTaskProvider';
7575
import { setTelemetryEnvVars, telemetryReporter } from './goTelemetry';
76+
import { GoCodeLensProvider } from './goCodeLens';
7677

7778
const goCtx: GoExtensionContext = {};
7879

@@ -144,6 +145,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<ExtensionA
144145
registerCommand('go.builds.run', commands.runBuilds);
145146
registerCommand('go.environment.status', expandGoStatusBar);
146147

148+
GoCodeLensProvider.activate(ctx, goCtx);
147149
GoRunTestCodeLensProvider.activate(ctx, goCtx);
148150
GoDebugConfigurationProvider.activate(ctx, goCtx);
149151
GoDebugFactory.activate(ctx, goCtx);

extension/src/language/goLanguageServer.ts

+38-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ import {
3333
ProvideCompletionItemsSignature,
3434
ProvideDocumentFormattingEditsSignature,
3535
ResponseError,
36-
RevealOutputChannelOn
36+
RevealOutputChannelOn,
37+
Location
3738
} from 'vscode-languageclient';
3839
import { LanguageClient, ServerOptions } from 'vscode-languageclient/node';
3940
import { getGoConfig, getGoplsConfig, extensionInfo } from '../config';
@@ -1665,3 +1666,39 @@ async function getGoplsStats(binpath?: string) {
16651666
return `gopls stats -anon failed after ${duration} ms. Please check if gopls is killed by OS.`;
16661667
}
16671668
}
1669+
1670+
export async function getSymbolImplementations(
1671+
goCtx: GoExtensionContext,
1672+
document: vscode.TextDocument,
1673+
symbol: vscode.DocumentSymbol
1674+
): Promise<vscode.Location[]> {
1675+
const languageClient = goCtx.languageClient;
1676+
if (languageClient) {
1677+
const params = {
1678+
textDocument: { uri: document.uri.toString() },
1679+
position: {
1680+
line: symbol.selectionRange.start.line,
1681+
character: symbol.selectionRange.start.character
1682+
}
1683+
};
1684+
try {
1685+
const implementations = await languageClient.sendRequest<Location[]>('textDocument/implementation', params);
1686+
return (
1687+
implementations.map(
1688+
(i) =>
1689+
new vscode.Location(
1690+
vscode.Uri.parse(i.uri),
1691+
new vscode.Range(
1692+
new vscode.Position(i.range.start.line, i.range.start.character),
1693+
new vscode.Position(i.range.end.line, i.range.end.character)
1694+
)
1695+
)
1696+
) || []
1697+
);
1698+
} catch (error) {
1699+
console.error(`unable to get implementations for ${symbol.name}:`, error);
1700+
return [];
1701+
}
1702+
}
1703+
return [];
1704+
}

0 commit comments

Comments
 (0)