blob: 54a3783d8e82b930c3b1be857b8510ed739c87af [file] [log] [blame]
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------*/
'use strict';
import cp = require('child_process');
import path = require('path');
import vscode = require('vscode');
import { adjustWordPosition, definitionLocation, parseMissingError } from './goDeclaration';
import { promptForMissingTool } from './goInstallTools';
import { byteOffsetAt, canonicalizeGOPATHPrefix, getBinPath, getFileArchive, getGoConfig, getToolsEnvVars, goBuiltinTypes, killTree } from './util';
interface GuruDescribeOutput {
desc: string;
pos: string;
detail: string;
value: GuruDescribeValueOutput;
}
interface GuruDescribeValueOutput {
type: string;
value: string;
objpos: string;
typespos: GuruDefinitionOutput[];
}
interface GuruDefinitionOutput {
objpos: string;
desc: string;
}
export class GoTypeDefinitionProvider implements vscode.TypeDefinitionProvider {
provideTypeDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult<vscode.Definition> {
const adjustedPos = adjustWordPosition(document, position);
if (!adjustedPos[0]) {
return Promise.resolve(null);
}
position = adjustedPos[2];
return new Promise<vscode.Definition>((resolve, reject) => {
const goGuru = getBinPath('guru');
if (!path.isAbsolute(goGuru)) {
promptForMissingTool('guru');
return reject('Cannot find tool "guru" to find type definitions.');
}
const filename = canonicalizeGOPATHPrefix(document.fileName);
const offset = byteOffsetAt(document, position);
const env = getToolsEnvVars();
const buildTags = getGoConfig(document.uri)['buildTags'];
const args = buildTags ? ['-tags', buildTags] : [];
args.push('-json', '-modified', 'describe', `${filename}:#${offset.toString()}`);
const process = cp.execFile(goGuru, args, { env }, (err, stdout, stderr) => {
try {
if (err && (<any>err).code === 'ENOENT') {
promptForMissingTool('guru');
return resolve(null);
}
if (err) {
return reject(err);
}
const guruOutput = <GuruDescribeOutput>JSON.parse(stdout.toString());
if (!guruOutput.value || !guruOutput.value.typespos) {
if (guruOutput.value
&& guruOutput.value.type
&& !goBuiltinTypes.has(guruOutput.value.type)
&& guruOutput.value.type !== 'invalid type') {
console.log('no typespos from guru\'s output - try to update guru tool');
}
// Fall back to position of declaration
return definitionLocation(document, position, null, false, token).then(definitionInfo => {
if (definitionInfo == null || definitionInfo.file == null) {
return null;
}
const definitionResource = vscode.Uri.file(definitionInfo.file);
const pos = new vscode.Position(definitionInfo.line, definitionInfo.column);
resolve(new vscode.Location(definitionResource, pos));
}, err => {
const miss = parseMissingError(err);
if (miss[0]) {
promptForMissingTool(miss[1]);
} else if (err) {
return Promise.reject(err);
}
return Promise.resolve(null);
});
}
const results: vscode.Location[] = [];
guruOutput.value.typespos.forEach(ref => {
const match = /^(.*):(\d+):(\d+)/.exec(ref.objpos);
if (!match) {
return;
}
const [_, file, line, col] = match;
const referenceResource = vscode.Uri.file(file);
const pos = new vscode.Position(parseInt(line, 10) - 1, parseInt(col, 10) - 1);
results.push(new vscode.Location(referenceResource, pos));
});
resolve(results);
} catch (e) {
reject(e);
}
});
if (process.pid) {
process.stdin.end(getFileArchive(document));
}
token.onCancellationRequested(() =>
killTree(process.pid)
);
});
}
}