Skip to content

Commit 84d8e2e

Browse files
committed
extension/src: add package symbols view
Registers an additional explorer view "Package Outline" that displays all the symbols in the currently open file's package by calling the gopls.package_symbols command. Refreshes package symbols data when the user saves or switches the currently open file. Requires gopls built with: https://go-review.git.corp.google.com/c/tools/+/645755 Change-Id: Ic4949b8160b81b8e3fd9120d6231e2e2706106fd Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/645596 kokoro-CI: kokoro <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Hongxiang Jiang <[email protected]>
1 parent af102aa commit 84d8e2e

File tree

3 files changed

+280
-0
lines changed

3 files changed

+280
-0
lines changed

extension/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2783,6 +2783,12 @@
27832783
"name": "go",
27842784
"icon": "media/go-logo-white.svg",
27852785
"when": "go.showExplorer"
2786+
},
2787+
{
2788+
"id": "go.package.outline",
2789+
"name": "Package Outline",
2790+
"icon": "media/go-logo-white.svg",
2791+
"when": "go.showPackageOutline"
27862792
}
27872793
],
27882794
"test": [

extension/src/goMain.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import extensionAPI from './extensionAPI';
6868
import { GoTestExplorer } from './goTest/explore';
6969
import { killRunningPprof } from './goTest/profile';
7070
import { GoExplorerProvider } from './goExplorer';
71+
import { GoPackageOutlineProvider } from './goPackageOutline';
7172
import { GoExtensionContext } from './context';
7273
import * as commands from './commands';
7374
import { toggleVulncheckCommandFactory } from './goVulncheck';
@@ -151,6 +152,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<ExtensionA
151152
experiments.activate(ctx);
152153
GoTestExplorer.setup(ctx, goCtx);
153154
GoExplorerProvider.setup(ctx);
155+
GoPackageOutlineProvider.setup(ctx);
154156

155157
goCtx.buildDiagnosticCollection = vscode.languages.createDiagnosticCollection('go');
156158
ctx.subscriptions.push(goCtx.buildDiagnosticCollection);

extension/src/goPackageOutline.ts

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
/*---------------------------------------------------------
2+
* Copyright 2025 The Go Authors. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE in the project root for license information.
4+
*--------------------------------------------------------*/
5+
6+
import vscode = require('vscode');
7+
import { SymbolKind } from 'vscode-languageserver-protocol';
8+
9+
interface PackageSymbolsCommandResult {
10+
PackageName: string;
11+
Files: string[];
12+
Symbols: PackageSymbolData[];
13+
}
14+
15+
export class GoPackageOutlineProvider implements vscode.TreeDataProvider<PackageSymbol> {
16+
private _onDidChangeTreeData: vscode.EventEmitter<PackageSymbol | undefined> = new vscode.EventEmitter<
17+
PackageSymbol | undefined
18+
>();
19+
20+
readonly onDidChangeTreeData: vscode.Event<PackageSymbol | undefined> = this._onDidChangeTreeData.event;
21+
22+
public result?: PackageSymbolsCommandResult;
23+
public activeDocument?: vscode.TextDocument;
24+
25+
static setup(ctx: vscode.ExtensionContext) {
26+
const provider = new this(ctx);
27+
const {
28+
window: { registerTreeDataProvider }
29+
} = vscode;
30+
ctx.subscriptions.push(registerTreeDataProvider('go.package.outline', provider));
31+
return provider;
32+
}
33+
34+
constructor(ctx: vscode.ExtensionContext) {
35+
this.reload(vscode.window.activeTextEditor?.document);
36+
let previousVersion: number | undefined;
37+
// Reload package symbol data on saving document with changes
38+
ctx.subscriptions.push(
39+
vscode.workspace.onDidSaveTextDocument((d) => {
40+
if (d.uri === vscode.window.activeTextEditor?.document.uri) {
41+
if (d.version !== previousVersion) {
42+
this.reload(d);
43+
previousVersion = d.version;
44+
}
45+
}
46+
})
47+
);
48+
// Reload package symbol data when switching active file
49+
ctx.subscriptions.push(
50+
vscode.window.onDidChangeActiveTextEditor((e) => {
51+
if (!e?.document?.fileName.endsWith('.go')) {
52+
return;
53+
}
54+
this.reload(e?.document);
55+
})
56+
);
57+
}
58+
59+
getTreeItem(element: PackageSymbol) {
60+
return element;
61+
}
62+
63+
rootItems(): Promise<PackageSymbol[]> {
64+
const list = Array<PackageSymbol>();
65+
// Add a tree item to display the current package name. Its "command" value will be undefined and thus
66+
// will not link anywhere when clicked
67+
list.push(
68+
new PackageSymbol(
69+
{
70+
name: this.result?.PackageName ? 'Current Package: ' + this.result.PackageName : '',
71+
detail: '',
72+
kind: 0,
73+
range: new vscode.Range(0, 0, 0, 0),
74+
selectionRange: new vscode.Range(0, 0, 0, 0),
75+
children: [],
76+
file: 0
77+
},
78+
[],
79+
vscode.TreeItemCollapsibleState.None
80+
)
81+
);
82+
const res = this.result;
83+
if (res) {
84+
res.Symbols?.forEach((d) =>
85+
list.push(
86+
new PackageSymbol(
87+
d,
88+
res.Files ?? [],
89+
d.children?.length > 0
90+
? vscode.TreeItemCollapsibleState.Collapsed
91+
: vscode.TreeItemCollapsibleState.None
92+
)
93+
)
94+
);
95+
}
96+
return new Promise((resolve) => resolve(list));
97+
}
98+
99+
getChildren(element?: PackageSymbol): Thenable<PackageSymbol[] | undefined> {
100+
// getChildren is called with null element when TreeDataProvider first loads
101+
if (!element) {
102+
return this.rootItems();
103+
}
104+
return Promise.resolve(element.children);
105+
}
106+
107+
async reload(e?: vscode.TextDocument) {
108+
if (!e) {
109+
this.result = undefined;
110+
this.activeDocument = undefined;
111+
return;
112+
}
113+
this.activeDocument = e;
114+
try {
115+
const res = (await vscode.commands.executeCommand('gopls.package_symbols', {
116+
URI: e.uri.toString()
117+
})) as PackageSymbolsCommandResult;
118+
this.result = res;
119+
// Show the Package Outline explorer if the request returned symbols for the current package
120+
vscode.commands.executeCommand('setContext', 'go.showPackageOutline', res?.Symbols?.length > 0);
121+
this._onDidChangeTreeData.fire(undefined);
122+
} catch (e) {
123+
// Hide the Package Outline explorer
124+
vscode.commands.executeCommand('setContext', 'go.showPackageOutline', false);
125+
console.log('ERROR', e);
126+
}
127+
}
128+
}
129+
130+
interface PackageSymbolData {
131+
/**
132+
* The name of this symbol.
133+
*/
134+
name: string;
135+
136+
/**
137+
* More detail for this symbol, e.g. the signature of a function.
138+
*/
139+
detail: string;
140+
141+
/**
142+
* The kind of this symbol.
143+
*/
144+
kind: number;
145+
146+
/**
147+
* Tags for this symbol.
148+
*/
149+
tags?: ReadonlyArray<vscode.SymbolTag>;
150+
151+
/**
152+
* The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code.
153+
*/
154+
range: vscode.Range;
155+
156+
/**
157+
* The range that should be selected and reveal when this symbol is being picked, e.g. the name of a function.
158+
* Must be contained by the [`range`](#DocumentSymbol.range).
159+
*/
160+
selectionRange: vscode.Range;
161+
162+
/**
163+
* Children of this symbol, e.g. properties of a class.
164+
*/
165+
children: PackageSymbolData[];
166+
167+
/**
168+
* Index of this symbol's file in PackageSymbolsCommandResult.Files
169+
*/
170+
file: number;
171+
}
172+
173+
class PackageSymbol extends vscode.TreeItem {
174+
constructor(
175+
private readonly data: PackageSymbolData,
176+
private readonly files: string[],
177+
public readonly collapsibleState: vscode.TreeItemCollapsibleState
178+
) {
179+
super(data.name, collapsibleState);
180+
const file = files[data.file ?? 0];
181+
this.resourceUri = files && files.length > 0 ? vscode.Uri.parse(file) : undefined;
182+
const [icon, kind] = this.getSymbolInfo();
183+
this.iconPath = icon;
184+
this.description = data.detail;
185+
this.tooltip = data.name + ' (' + kind + ')';
186+
this.command = this.resourceUri
187+
? {
188+
command: 'vscode.openWith',
189+
title: '',
190+
arguments: [
191+
this.resourceUri,
192+
'default',
193+
{
194+
selection: new vscode.Range(data.range.start, data.range.start)
195+
}
196+
]
197+
}
198+
: undefined;
199+
}
200+
201+
get children(): PackageSymbol[] | undefined {
202+
return this.data.children?.map(
203+
(c) =>
204+
new PackageSymbol(
205+
c,
206+
this.files,
207+
c.children?.length > 0
208+
? vscode.TreeItemCollapsibleState.Collapsed
209+
: vscode.TreeItemCollapsibleState.None
210+
)
211+
);
212+
}
213+
214+
private getSymbolInfo(): [vscode.ThemeIcon | undefined, string] {
215+
switch (this.data.kind) {
216+
case SymbolKind.File:
217+
return [new vscode.ThemeIcon('symbol-file'), 'file'];
218+
case SymbolKind.Module:
219+
return [new vscode.ThemeIcon('symbol-module'), 'module'];
220+
case SymbolKind.Namespace:
221+
return [new vscode.ThemeIcon('symbol-namespace'), 'namespace'];
222+
case SymbolKind.Package:
223+
return [new vscode.ThemeIcon('symbol-package'), 'package'];
224+
case SymbolKind.Class:
225+
return [new vscode.ThemeIcon('symbol-class'), 'class'];
226+
case SymbolKind.Method:
227+
return [new vscode.ThemeIcon('symbol-method'), 'method'];
228+
case SymbolKind.Property:
229+
return [new vscode.ThemeIcon('symbol-property'), 'property'];
230+
case SymbolKind.Field:
231+
return [new vscode.ThemeIcon('symbol-field'), 'field'];
232+
case SymbolKind.Constructor:
233+
return [new vscode.ThemeIcon('symbol-constructor'), 'constructor'];
234+
case SymbolKind.Enum:
235+
return [new vscode.ThemeIcon('symbol-enum'), 'enum'];
236+
case SymbolKind.Interface:
237+
return [new vscode.ThemeIcon('symbol-interface'), 'interface'];
238+
case SymbolKind.Function:
239+
return [new vscode.ThemeIcon('symbol-function'), 'function'];
240+
case SymbolKind.Variable:
241+
return [new vscode.ThemeIcon('symbol-variable'), 'variable'];
242+
case SymbolKind.Constant:
243+
return [new vscode.ThemeIcon('symbol-constant'), 'constant'];
244+
case SymbolKind.String:
245+
return [new vscode.ThemeIcon('symbol-string'), 'string'];
246+
case SymbolKind.Number:
247+
return [new vscode.ThemeIcon('symbol-number'), 'number'];
248+
case SymbolKind.Boolean:
249+
return [new vscode.ThemeIcon('symbol-boolean'), 'boolean'];
250+
case SymbolKind.Array:
251+
return [new vscode.ThemeIcon('symbol-array'), 'array'];
252+
case SymbolKind.Object:
253+
return [new vscode.ThemeIcon('symbol-object'), 'object'];
254+
case SymbolKind.Key:
255+
return [new vscode.ThemeIcon('symbol-key'), 'key'];
256+
case SymbolKind.Null:
257+
return [new vscode.ThemeIcon('symbol-null'), 'null'];
258+
case SymbolKind.EnumMember:
259+
return [new vscode.ThemeIcon('symbol-enum-member'), 'enum member'];
260+
case SymbolKind.Struct:
261+
return [new vscode.ThemeIcon('symbol-struct'), 'struct'];
262+
case SymbolKind.Event:
263+
return [new vscode.ThemeIcon('symbol-event'), 'event'];
264+
case SymbolKind.Operator:
265+
return [new vscode.ThemeIcon('symbol-operator'), 'operator'];
266+
case SymbolKind.TypeParameter:
267+
return [new vscode.ThemeIcon('symbol-type-parameter'), 'type parameter'];
268+
default:
269+
return [undefined, 'unknown'];
270+
}
271+
}
272+
}

0 commit comments

Comments
 (0)