diff --git a/package.json b/package.json index 6a58d4a29..a48fe047c 100644 --- a/package.json +++ b/package.json @@ -689,6 +689,12 @@ "description": "If false, the import statements will be excluded while using the Go to Symbol in File feature", "scope": "resource" }, + "go.gotoSymbol.includeGoroot": { + "type": "boolean", + "default": false, + "description": "If false, the standard library located at $GOROOT will be excluded while using the Go to Symbol in File feature", + "scope": "resource" + }, "go.enableCodeLens": { "type": "object", "properties": { diff --git a/src/goSymbol.ts b/src/goSymbol.ts index dd0698a64..8f71d59c7 100644 --- a/src/goSymbol.ts +++ b/src/goSymbol.ts @@ -7,6 +7,7 @@ import vscode = require('vscode'); import cp = require('child_process'); import { getBinPath, getToolsEnvVars, killProcess } from './util'; +import { getGoRuntimePath } from './goPath'; import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools'; // Keep in sync with github.com/acroca/go-symbols' @@ -51,12 +52,15 @@ export class GoWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider if (vscode.window.activeTextEditor && vscode.workspace.getWorkspaceFolder(vscode.window.activeTextEditor.document.uri)) { root = vscode.workspace.getWorkspaceFolder(vscode.window.activeTextEditor.document.uri).uri.fsPath; } - if (!root) { + + let goConfig = vscode.workspace.getConfiguration('go', vscode.window.activeTextEditor ? vscode.window.activeTextEditor.document.uri : null); + + if (!root && !goConfig.gotoSymbol.includeGoroot) { vscode.window.showInformationMessage('No workspace is open to find symbols.'); return; } - return getWorkspaceSymbols(root, query, token).then(results => { + return getWorkspaceSymbols(root, query, token, goConfig).then(results => { let symbols: vscode.SymbolInformation[] = []; convertToCodeSymbols(results, symbols); return symbols; @@ -69,37 +73,69 @@ export function getWorkspaceSymbols(workspacePath: string, query: string, token: goConfig = vscode.workspace.getConfiguration('go', vscode.window.activeTextEditor ? vscode.window.activeTextEditor.document.uri : null); } let gotoSymbolConfig = goConfig['gotoSymbol']; + let calls: Promise[] = []; + let ignoreFolders: string[] = gotoSymbolConfig ? gotoSymbolConfig['ignoreFolders'] : []; - let args = (ignoreFolderFeatureOn && ignoreFolders && ignoreFolders.length > 0) ? ['-ignore', ignoreFolders.join(',')] : []; - args.push(workspacePath); - args.push(query); + let baseArgs = (ignoreFolderFeatureOn && ignoreFolders && ignoreFolders.length > 0) ? ['-ignore', ignoreFolders.join(',')] : []; + + calls.push(callGoSymbols([...baseArgs, workspacePath, query], token)); + + if (gotoSymbolConfig.includeGoroot) { + let gorootCall = getGoroot() + .then(goRoot => callGoSymbols([...baseArgs, goRoot, query], token)); + calls.push(gorootCall); + } + + return Promise.all(calls) + .then(([...results]) => [].concat(...results)) + .catch((err: Error) => { + if (err && (err).code === 'ENOENT') { + promptForMissingTool('go-symbols'); + } + if (err.message.startsWith('flag provided but not defined: -ignore')) { + promptForUpdatingTool('go-symbols'); + return getWorkspaceSymbols(workspacePath, query, token, goConfig, false); + } + }); +} + +function callGoSymbols(args: string[], token: vscode.CancellationToken): Promise { let gosyms = getBinPath('go-symbols'); let env = getToolsEnvVars(); let p: cp.ChildProcess; + if (token) { token.onCancellationRequested(() => killProcess(p)); } return new Promise((resolve, reject) => { p = cp.execFile(gosyms, args, { maxBuffer: 1024 * 1024, env }, (err, stdout, stderr) => { - try { - if (err && (err).code === 'ENOENT') { - promptForMissingTool('go-symbols'); - } - if (err && stderr && stderr.startsWith('flag provided but not defined: -ignore')) { - promptForUpdatingTool('go-symbols'); - p = null; - return getWorkspaceSymbols(workspacePath, query, token, goConfig, false).then(results => { - return resolve(results); - }); - } - if (err) return resolve(null); - let result = stdout.toString(); - let decls = JSON.parse(result); - return resolve(decls); - } catch (e) { - reject(e); + if (err && stderr && stderr.startsWith('flag provided but not defined: -ignore')) { + return reject(new Error(stderr)); + } else if (err) { + return reject(err); } + let result = stdout.toString(); + let decls = JSON.parse(result); + return resolve(decls); }); }); } + +function getGoroot(): Promise { + let goExecutable = getGoRuntimePath(); + if (!goExecutable) { + return Promise.reject(new Error('Cannot find "go" binary. Update PATH or GOROOT appropriately')); + } + return new Promise((resolve, reject) => { + cp.execFile(goExecutable, ['env', 'GOROOT'], (err, stdout) => { + if (err) { + reject(err); + return; + } + let [goRoot] = stdout.split('\n'); + resolve(goRoot.trim()); + }); + }); +} + diff --git a/test/go.test.ts b/test/go.test.ts index 291b7b872..78740e460 100644 --- a/test/go.test.ts +++ b/test/go.test.ts @@ -681,7 +681,7 @@ It returns the number of bytes written and any write error encountered. }).then(() => done(), done); }); - test('Workspace Symbols', (done) => { + test('Workspace Symbols', () => { // This test needs a go project that has vendor folder and vendor packages // Since the Go extension takes a dependency on the godef tool at github.com/rogpeppe/godef // which has vendor packages, we are using it here to test the "replace vendor packages with relative path" feature. @@ -703,6 +703,21 @@ It returns the number of bytes written and any write error encountered. } } }); + let configWithIncludeGoroot = Object.create(vscode.workspace.getConfiguration('go'), { + 'gotoSymbol': { + value: { + 'includeGoroot': true + } + } + }); + let configWithoutIncludeGoroot = Object.create(vscode.workspace.getConfiguration('go'), { + 'gotoSymbol': { + value: { + 'includeGoroot': false + } + } + }); + let withoutIgnoringFolders = getWorkspaceSymbols(workspacePath, 'WinInfo', null, configWithoutIgnoringFolders).then(results => { assert.equal(results[0].name, 'WinInfo'); assert.equal(results[0].path, path.join(workspacePath, 'vendor/9fans.net/go/acme/acme.go')); @@ -710,7 +725,14 @@ It returns the number of bytes written and any write error encountered. let withIgnoringFolders = getWorkspaceSymbols(workspacePath, 'WinInfo', null, configWithIgnoringFolders).then(results => { assert.equal(results.length, 0); }); - Promise.all([withIgnoringFolders, withoutIgnoringFolders]).then(() => done(), done); + let withoutIncludingGoroot = getWorkspaceSymbols(workspacePath, 'Mutex', null, configWithoutIncludeGoroot).then(results => { + assert.equal(results.length, 0); + }); + let withIncludingGoroot = getWorkspaceSymbols(workspacePath, 'Mutex', null, configWithIncludeGoroot).then(results => { + assert(results.some(result => result.name === 'Mutex')); + }); + + return Promise.all([withIgnoringFolders, withoutIgnoringFolders, withIncludingGoroot, withoutIncludingGoroot]); }); test('Test Completion', (done) => {