From 0020119fece6c9e21f80dc6543f7e28c541ac3d6 Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Wed, 16 Nov 2016 17:38:15 -0800 Subject: [PATCH] Support both godef and gogetdoc --- .travis.yml | 2 +- package.json | 23 ++++++++- src/goDeclaration.ts | 119 +++++++++++++++++++++++++++++++++++++------ src/goExtraInfo.ts | 12 ++--- src/goSignature.ts | 23 +++++++-- test/go.test.ts | 50 ++++++++++-------- 6 files changed, 178 insertions(+), 51 deletions(-) diff --git a/.travis.yml b/.travis.yml index f8f413177..e6921341c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ install: - npm install - npm run vscode:prepublish - go get -u -v github.com/nsf/gocode - - go get -u -v github.com/zmb3/gogetdoc + - if [[ "$(go version)" =~ "go version go1.5" ]]; then go get -u -v github.com/rogpeppe/godef; else go get -u -v github.com/zmb3/gogetdoc; fi - if [[ "$(go version)" =~ "go version go1.5" ]]; then echo cannot get golint; else go get -u -v github.com/golang/lint/golint; fi - go get -u -v github.com/lukehoban/go-outline - go get -u -v sourcegraph.com/sqs/goreturns diff --git a/package.json b/package.json index f99c8c780..100f0a28f 100644 --- a/package.json +++ b/package.json @@ -178,7 +178,12 @@ "default": "." }, "mode": { - "enum": ["debug", "remote", "test", "exec"], + "enum": [ + "debug", + "remote", + "test", + "exec" + ], "description": "One of 'debug', 'remote', 'test', 'exec'.", "default": "debug" }, @@ -295,7 +300,12 @@ "go.formatTool": { "type": "string", "default": "goreturns", - "description": "Pick 'gofmt', 'goimports' or 'goreturns' to run on format." + "description": "Pick 'gofmt', 'goimports' or 'goreturns' to run on format.", + "enum": [ + "gofmt", + "goimports", + "goreturns" + ] }, "go.formatFlags": { "type": "array", @@ -365,6 +375,15 @@ "type": "boolean", "default": true, "description": "Autocomplete members from unimported packages." + }, + "go.docsTool": { + "type": "string", + "default": "godoc", + "description": "Pick 'godoc' or 'gogetdoc' to get documentation. In Go 1.5, godoc is used regardless of the choice here.", + "enum": [ + "godoc", + "gogetdoc" + ] } } } diff --git a/src/goDeclaration.ts b/src/goDeclaration.ts index 898668fae..32cac0af6 100644 --- a/src/goDeclaration.ts +++ b/src/goDeclaration.ts @@ -7,49 +7,136 @@ import vscode = require('vscode'); import cp = require('child_process'); +import path = require('path'); import { getBinPath } from './goPath'; import { byteOffsetAt } from './util'; import { promptForMissingTool } from './goInstallTools'; +import { getGoVersion, SemVersion } from './util'; export interface GoDefinitionInformtation { file: string; line: number; column: number; - docInfo: GoDocInfomation; + doc: string; + declarationlines: string[]; + toolUsed: string; } export function definitionLocation(document: vscode.TextDocument, position: vscode.Position, includeDocs = true): Promise { + let toolToUse = vscode.workspace.getConfiguration('go')['docsTool']; + return getGoVersion().then((ver: SemVersion) => { + if (!ver) { + return Promise.resolve(null); + } + if (toolToUse === 'godoc' || ver.major < 1 || (ver.major === 1 && ver.minor < 6)) { + return definitionLocation_godef(document, position, includeDocs); + } + return definitionLocation_gogetdoc(document, position); + }); +} + +function definitionLocation_godef(document: vscode.TextDocument, position: vscode.Position, includeDocs = true): Promise { return new Promise((resolve, reject) => { + let wordAtPosition = document.getWordRangeAtPosition(position); let offset = byteOffsetAt(document, position); - let gogetdoc = getBinPath('gogetdoc'); - let p = cp.execFile(gogetdoc, ['-u', '-json', '-modified', '-pos', document.fileName + ':#' + offset.toString()], {}, (err, stdout, stderr) => { + + let godef = getBinPath('godef'); + + // Spawn `godef` process + let p = cp.execFile(godef, ['-t', '-i', '-f', document.fileName, '-o', offset.toString()], {}, (err, stdout, stderr) => { try { if (err && (err).code === 'ENOENT') { - promptForMissingTool('gogetdoc'); + promptForMissingTool('godef'); } if (err) return resolve(null); - let goDocInfomation = JSON.parse(stdout.toString()); - let match = /(.*):(\d+):(\d+)/.exec(goDocInfomation.pos); + let result = stdout.toString(); + let lines = result.split('\n'); + let match = /(.*):(\d+):(\d+)/.exec(lines[0]); if (!match) { - return resolve({ - file: null, - line: 0, - column: 0, - docInfo: goDocInfomation - }); + // TODO: Gotodef on pkg name: + // /usr/local/go/src/html/template\n + return resolve(null); } let [_, file, line, col] = match; - return resolve({ + let signature = lines[1]; + let godoc = getBinPath('godoc'); + let pkgPath = path.dirname(file); + let definitionInformation: GoDefinitionInformtation = { file: file, line: +line - 1, - column: +col - 1, - docInfo: goDocInfomation + column: + col - 1, + declarationlines: lines.splice(1), + toolUsed: 'godef', + doc: null + }; + if (!includeDocs) { + return resolve(definitionInformation); + } + cp.execFile(godoc, [pkgPath], {}, (err, stdout, stderr) => { + if (err && (err).code === 'ENOENT') { + vscode.window.showInformationMessage('The "godoc" command is not available.'); + } + let godocLines = stdout.toString().split('\n'); + let doc = ''; + let sigName = signature.substring(0, signature.indexOf(' ')); + let sigParams = signature.substring(signature.indexOf(' func') + 5); + let searchSignature = 'func ' + sigName + sigParams; + for (let i = 0; i < godocLines.length; i++) { + if (godocLines[i] === searchSignature) { + while (godocLines[++i].startsWith(' ')) { + doc += godocLines[i].substring(4) + '\n'; + } + break; + } + } + if (doc !== '') { + definitionInformation.doc = doc; + } + return resolve(definitionInformation); }); } catch (e) { reject(e); } }); + p.stdin.end(document.getText()); + }); +} + +function definitionLocation_gogetdoc(document: vscode.TextDocument, position: vscode.Position): Promise { + return new Promise((resolve, reject) => { + let wordAtPosition = document.getWordRangeAtPosition(position); + let offset = byteOffsetAt(document, position); + let gogetdoc = getBinPath('gogetdoc'); + let p = cp.execFile(gogetdoc, ['-u', '-json', '-modified', '-pos', document.fileName + ':#' + offset.toString()], {}, (err, stdout, stderr) => { + try { + if (err && (err).code === 'ENOENT') { + promptForMissingTool('gogetdoc'); + } + if (err) return resolve(null); + let goGetDocOutput = JSON.parse(stdout.toString()); + let match = /(.*):(\d+):(\d+)/.exec(goGetDocOutput.pos); + let definitionInfo = { + file: null, + line: 0, + column: 0, + toolUsed: 'gogetdoc', + declarationlines: goGetDocOutput.decl.split('\n'), + doc: goGetDocOutput.doc + }; + if (!match) { + return resolve(definitionInfo); + } + let [_, file, line, col] = match; + definitionInfo.file = match[1]; + definitionInfo.line = +match[2] - 1; + definitionInfo.column = +match[3] - 1; + return resolve(definitionInfo); + + } catch (e) { + reject(e); + } + }); let documentText = document.getText(); let documentArchive = document.fileName + '\n'; documentArchive = documentArchive + Buffer.byteLength(documentText) + '\n'; @@ -69,7 +156,7 @@ export class GoDefinitionProvider implements vscode.DefinitionProvider { } } -interface GoDocInfomation { +interface GoGetDocOuput { name: string; import: string; decl: string; diff --git a/src/goExtraInfo.ts b/src/goExtraInfo.ts index d4a164133..99d760ad4 100644 --- a/src/goExtraInfo.ts +++ b/src/goExtraInfo.ts @@ -12,19 +12,15 @@ export class GoHoverProvider implements HoverProvider { public provideHover(document: TextDocument, position: Position, token: CancellationToken): Thenable { return definitionLocation(document, position, true).then(definitionInfo => { if (definitionInfo == null) return null; - let lines = definitionInfo.docInfo.decl.split('\n') + let lines = definitionInfo.declarationlines .filter(line => !line.startsWith('\t//') && line !== '') .map(line => line.replace(/\t/g, ' ')); let text; - if (lines.length > 1) { - text = lines.join('\n').replace(/\n+$/, ''); - } else { - text = lines[0]; - } + text = lines.join('\n').replace(/\n+$/, ''); let hoverTexts: MarkedString[] = []; hoverTexts.push({ language: 'go', value: text }); - if (definitionInfo.docInfo.doc != null) { - hoverTexts.push(definitionInfo.docInfo.doc); + if (definitionInfo.doc != null) { + hoverTexts.push(definitionInfo.doc); } let hover = new Hover(hoverTexts); return hover; diff --git a/src/goSignature.ts b/src/goSignature.ts index 81064c467..237d705d8 100755 --- a/src/goSignature.ts +++ b/src/goSignature.ts @@ -28,10 +28,25 @@ export class GoSignatureHelpProvider implements SignatureHelpProvider { return null; } let result = new SignatureHelp(); - let text = res.docInfo.decl.substring(5); - let si = new SignatureInformation(text, res.docInfo.doc); - let braceStart = text.indexOf('('); - si.parameters = parameters(text.substring(braceStart)).map(paramText => + let text, sig: string; + let si: SignatureInformation; + if (res.toolUsed === 'godef') { + // declaration is of the form "Add func(a int, b int) int" + text = res.declarationlines[0]; + let nameEnd = text.indexOf(' '); + let sigStart = nameEnd + 5; // ' func' + let funcName = text.substring(0, nameEnd); + sig = text.substring(sigStart); + si = new SignatureInformation(funcName + sig, res.doc); + } else { + // declaration is of the form "func Add(a int, b int) int" + text = res.declarationlines[0].substring(5); + si = new SignatureInformation(text, res.doc); + let braceStart = text.indexOf('('); + sig = text.substring(braceStart); + } + + si.parameters = parameters(sig).map(paramText => new ParameterInformation(paramText) ); result.signatures = [si]; diff --git a/test/go.test.ts b/test/go.test.ts index 60be2dbb4..067953228 100644 --- a/test/go.test.ts +++ b/test/go.test.ts @@ -53,28 +53,38 @@ encountered. `; let testCases: [vscode.Position, string, string][] = [ // [new vscode.Position(3,3), '/usr/local/go/src/fmt'], - [new vscode.Position(9, 6), 'func main()', null], - [new vscode.Position(7, 2), 'package fmt', null], - [new vscode.Position(7, 6), 'func Println(a ...interface{}) (n int, err error)', printlnDoc], - [new vscode.Position(10, 3), 'func print(txt string)', null] + [new vscode.Position(9, 6), 'main func()', null], + [new vscode.Position(7, 2), 'import (fmt "fmt")', null], + [new vscode.Position(7, 6), 'Println func(a ...interface{}) (n int, err error)', printlnDoc], + [new vscode.Position(10, 3), 'print func(txt string)', null] ]; let uri = vscode.Uri.file(path.join(fixturePath, 'test.go')); - vscode.workspace.openTextDocument(uri).then((textDocument) => { - let promises = testCases.map(([position, expectedSignature, expectedDocumentation]) => - provider.provideHover(textDocument, position, null).then(res => { - // TODO: Documentation appears to currently be broken on Go 1.7, so disabling these tests for now - // if (expectedDocumentation === null) { - // assert.equal(res.contents.length, 1); - // } else { - // assert.equal(res.contents.length, 2); - // assert.equal(expectedDocumentation, (res.contents[0])); - // } - assert.equal(expectedSignature, (<{ language: string; value: string }>res.contents[0]).value); - }) - ); - return Promise.all(promises); - }, (err) => { - assert.ok(false, `error in OpenTextDocument ${err}`); + + getGoVersion().then(version => { + if (version.major > 1 || (version.major === 1 && version.minor > 5)) { + testCases[0][1] = 'func main()'; + testCases[1][1] = 'package fmt'; + testCases[2][1] = 'func Println(a ...interface{}) (n int, err error)'; + testCases[3][1] = 'func print(txt string)'; + } + return vscode.workspace.openTextDocument(uri).then((textDocument) => { + let promises = testCases.map(([position, expectedSignature, expectedDocumentation]) => + provider.provideHover(textDocument, position, null).then(res => { + // TODO: Documentation appears to currently be broken on Go 1.7, so disabling these tests for now + // if (expectedDocumentation === null) { + // assert.equal(res.contents.length, 1); + // } else { + // assert.equal(res.contents.length, 2); + // assert.equal(expectedDocumentation, (res.contents[0])); + // } + assert.equal(expectedSignature, (<{ language: string; value: string }>res.contents[0]).value); + }) + ); + return Promise.all(promises); + }, (err) => { + assert.ok(false, `error in OpenTextDocument ${err}`); + return Promise.reject(err); + }); }).then(() => done(), done); });