From 0509883f56a9dd0f5915d15d4e32cae29c6001b4 Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Wed, 21 Sep 2016 16:49:38 -0700 Subject: [PATCH 1/6] Add completion items for unimported packages --- package.json | 2 +- src/goSuggest.ts | 237 ++++++++++++++++++++++++++++++++++------------- test/go.test.ts | 34 +++++++ 3 files changed, 208 insertions(+), 65 deletions(-) diff --git a/package.json b/package.json index 3ab613762..1baf31c76 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "vscode": "^0.11.17" }, "engines": { - "vscode": "0.10.x" + "vscode": "^1.5.0" }, "activationEvents": [ "onLanguage:go", diff --git a/src/goSuggest.ts b/src/goSuggest.ts index 186259d80..4cf23a74e 100644 --- a/src/goSuggest.ts +++ b/src/goSuggest.ts @@ -9,8 +9,9 @@ import vscode = require('vscode'); import cp = require('child_process'); import { dirname, basename } from 'path'; import { getBinPath } from './goPath'; -import { parameters } from './util'; +import { parameters, parseFilePrelude } from './util'; import { promptForMissingTool } from './goInstallTools'; +import { listPackages } from './goImport'; function vscodeKindFromGoCodeClass(kind: string): vscode.CompletionItemKind { switch (kind) { @@ -34,15 +35,22 @@ interface GoCodeSuggestion { type: string; } +interface PackageInfo { + name: string; + path: string; +} + export class GoCompletionItemProvider implements vscode.CompletionItemProvider { private gocodeConfigurationComplete = false; + private pkgsList: PackageInfo[] = []; public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable { return this.ensureGoCodeConfigured().then(() => { return new Promise((resolve, reject) => { let filename = document.fileName; let lineText = document.lineAt(position.line).text; + let lineTillCurrentPosition = lineText.substr(0, position.character); if (lineText.match(/^\s*\/\//)) { return resolve([]); @@ -66,80 +74,130 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { } let offset = document.offsetAt(position); - let gocode = getBinPath('gocode'); - - // Unset GOOS and GOARCH for the `gocode` process to ensure that GOHOSTOS and GOHOSTARCH - // are used as the target operating system and architecture. `gocode` is unable to provide - // autocompletion when the Go environment is configured for cross compilation. - let env = Object.assign({}, process.env, { GOOS: '', GOARCH: '' }); - - // Spawn `gocode` process - let p = cp.execFile(gocode, ['-f=json', 'autocomplete', filename, 'c' + offset], { env }, (err, stdout, stderr) => { - try { - if (err && (err).code === 'ENOENT') { - promptForMissingTool('gocode'); - } - if (err) return reject(err); - let results = <[number, GoCodeSuggestion[]]>JSON.parse(stdout.toString()); - let suggestions = []; - // 'Smart Snippet' for package clause - // TODO: Factor this out into a general mechanism - if (!document.getText().match(/package\s+(\w+)/)) { - let defaultPackageName = - basename(document.fileName) === 'main.go' - ? 'main' - : basename(dirname(document.fileName)); - let packageItem = new vscode.CompletionItem('package ' + defaultPackageName); - packageItem.kind = vscode.CompletionItemKind.Snippet; - packageItem.insertText = 'package ' + defaultPackageName + '\r\n\r\n'; - suggestions.push(packageItem); + let inputText = document.getText(); + + return this.runGoCode(filename, inputText, offset, inString, position, lineText).then(suggestions => { + // Add importable packages matching currentword to suggestions + suggestions = suggestions.concat(this.getMatchingPackages(currentWord)); + + // If no suggestions and cursor is at a dot, then check if preceeding word is a package name + // If yes, then import the package in the inputText and run gocode again to get suggestions + if (suggestions.length === 0 && lineTillCurrentPosition.endsWith('.')){ + + let pkgPath = this.getPackagePathFromLine(lineTillCurrentPosition); + if (pkgPath) { + // Now that we have the package path, import it right after the "package" statement + let {imports, pkg} = parseFilePrelude(vscode.window.activeTextEditor.document.getText()); + let posToAddImport = document.offsetAt(new vscode.Position(pkg.start +1 , 0)); + let textToAdd = `import "${pkgPath}"\n` + inputText = inputText.substr(0, posToAddImport) + textToAdd + inputText.substr(posToAddImport); + offset += textToAdd.length; - } - if (results[1]) { - for (let suggest of results[1]) { - if (inString && suggest.class !== 'import') continue; - let item = new vscode.CompletionItem(suggest.name); - item.kind = vscodeKindFromGoCodeClass(suggest.class); - item.detail = suggest.type; - if (inString && suggest.class === 'import') { - item.textEdit = new vscode.TextEdit( - new vscode.Range( - position.line, - lineText.substring(0, position.character).lastIndexOf('"') + 1, - position.line, - position.character), - suggest.name - ); - } - let conf = vscode.workspace.getConfiguration('go'); - if (conf.get('useCodeSnippetsOnFunctionSuggest') && suggest.class === 'func') { - let params = parameters(suggest.type.substring(4)); - let paramSnippets = []; - for (let i in params) { - let param = params[i].trim(); - if (param) { - param = param.replace('{', '\\{').replace('}', '\\}'); - paramSnippets.push('{{' + param + '}}'); - } + // Now that we have the package imported in the inputText, run gocode again + return this.runGoCode(filename, inputText, offset, inString, position, lineText).then(newsuggestions => { + // Since the new suggestions are due to the package that we imported, + // add a command to do the same in the actual document in the editor + newsuggestions.forEach(item => { + item.command = { + title: 'Import Package', + command: 'go.import.add', + arguments: [pkgPath] } - item.insertText = suggest.name + '(' + paramSnippets.join(', ') + '){{}}'; - } - suggestions.push(item); - }; + }); + resolve(newsuggestions); + }); } - resolve(suggestions); - } catch (e) { - reject(e); } + resolve(suggestions); }); - p.stdin.end(document.getText()); + }); }); } + private runGoCode(filename: string, inputText: string, offset: number, inString:boolean, position: vscode.Position, lineText: string): Thenable{ + return new Promise((resolve, reject) => { + let gocode = getBinPath('gocode'); + + // Unset GOOS and GOARCH for the `gocode` process to ensure that GOHOSTOS and GOHOSTARCH + // are used as the target operating system and architecture. `gocode` is unable to provide + // autocompletion when the Go environment is configured for cross compilation. + let env = Object.assign({}, process.env, { GOOS: '', GOARCH: '' }); + + // Spawn `gocode` process + let p = cp.execFile(gocode, ['-f=json', 'autocomplete', filename, 'c' + offset], { env }, (err, stdout, stderr) => { + try { + if (err && (err).code === 'ENOENT') { + promptForMissingTool('gocode'); + } + if (err) return reject(err); + let results = <[number, GoCodeSuggestion[]]>JSON.parse(stdout.toString()); + let suggestions = []; + // 'Smart Snippet' for package clause + // TODO: Factor this out into a general mechanism + if (!inputText.match(/package\s+(\w+)/)) { + let defaultPackageName = + basename(filename) === 'main.go' + ? 'main' + : basename(dirname(filename)); + let packageItem = new vscode.CompletionItem('package ' + defaultPackageName); + packageItem.kind = vscode.CompletionItemKind.Snippet; + packageItem.insertText = 'package ' + defaultPackageName + '\r\n\r\n'; + suggestions.push(packageItem); + + } + if (results[1]) { + for (let suggest of results[1]) { + if (inString && suggest.class !== 'import') continue; + let item = new vscode.CompletionItem(suggest.name); + item.kind = vscodeKindFromGoCodeClass(suggest.class); + item.detail = suggest.type; + if (inString && suggest.class === 'import') { + item.textEdit = new vscode.TextEdit( + new vscode.Range( + position.line, + lineText.substring(0, position.character).lastIndexOf('"') + 1, + position.line, + position.character), + suggest.name + ); + } + let conf = vscode.workspace.getConfiguration('go'); + if (conf.get('useCodeSnippetsOnFunctionSuggest') && suggest.class === 'func') { + let params = parameters(suggest.type.substring(4)); + let paramSnippets = []; + for (let i in params) { + let param = params[i].trim(); + if (param) { + param = param.replace('{', '\\{').replace('}', '\\}'); + paramSnippets.push('{{' + param + '}}'); + } + } + item.insertText = suggest.name + '(' + paramSnippets.join(', ') + '){{}}'; + } + suggestions.push(item); + }; + } + resolve(suggestions); + } catch (e) { + reject(e); + } + }); + p.stdin.end(inputText); + }); + } // TODO: Shouldn't lib-path also be set? private ensureGoCodeConfigured(): Thenable { - return new Promise((resolve, reject) => { + let pkgPromise = listPackages().then((pkgs: string[]) => { + this.pkgsList = pkgs.map(pkg => { + let index = pkg.lastIndexOf('/'); + return { + name: index === -1 ? pkg: pkg.substr(index + 1), + path: pkg + } + }) + }); + let configPromise = new Promise((resolve, reject) => { // TODO: Since the gocode daemon is shared amongst clients, shouldn't settings be // adjusted per-invocation to avoid conflicts from other gocode-using programs? if (this.gocodeConfigurationComplete) { @@ -153,5 +211,56 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { }); }); }); + return Promise.all([pkgPromise, configPromise]).then(()=> {return Promise.resolve()});; + } + + // Return importable packages that match given word as Completion Items + private getMatchingPackages(word: string): vscode.CompletionItem[] { + if (!word) return []; + let completionItems = this.pkgsList.filter((pkgInfo: PackageInfo) => { + return pkgInfo.name.startsWith(word); + }).map((pkgInfo: PackageInfo) => { + let item = new vscode.CompletionItem(pkgInfo.name, vscode.CompletionItemKind.Keyword); + item.detail = 'Add import'; + item.insertText = pkgInfo.name; + item.command = { + title: 'Import Package', + command: 'go.import.add', + arguments: [pkgInfo.path] + } + return item; + }); + return completionItems; + } + + // Given a line ending with dot, return the word preceeding the dot if it is a package name that can be imported + private getPackagePathFromLine(line: string): string { + let pkgName = null; + + // There could be multiple dots in the line + // we are interested in the word preceeding the last one + let splits = line.split('.'); + line = splits[splits.length - 2]; + + // There could be multiple words in the line + // we are interested in the last one + let wordmatches = null; + let pattern = /(\w+)/g; + while (wordmatches = pattern.exec(line)) { + pkgName = wordmatches[1]; + } + + if (!pkgName){ + return; + } + + // Word is isolated. Now check pkgsList for a match + let matchingPackages = this.pkgsList.filter(pkgInfo => { + return pkgInfo.name === pkgName; + }); + + if (matchingPackages && matchingPackages.length === 1){ + return matchingPackages[0].path; + } } } diff --git a/test/go.test.ts b/test/go.test.ts index e6ff06297..8edaa4b15 100644 --- a/test/go.test.ts +++ b/test/go.test.ts @@ -98,6 +98,40 @@ encountered. }).then(() => done(), done); }); + test('Test Completion on unimported packages', (done) => { + let provider = new GoCompletionItemProvider(); + let testCases: [vscode.Position, string[]][] = [ + [new vscode.Position(12, 2), ['bytes']], + [new vscode.Position(13, 5), ['Abs', 'Acos', 'Asin']] + ]; + let uri = vscode.Uri.file(path.join(fixturePath, 'test.go')); + + vscode.workspace.openTextDocument(uri).then((textDocument) => { + return vscode.window.showTextDocument(textDocument).then((editor => { + return editor.edit(editbuilder => { + editbuilder.insert(new vscode.Position(12,0), 'by\n'); + editbuilder.insert(new vscode.Position(13,0), 'math.\n'); + }).then(() => { + let promises = testCases.map(([position, expected]) => + provider.provideCompletionItems(textDocument, position, null).then(items => { + let labels = items.map(x => x.label); + for (let entry of expected) { + assert.equal(labels.indexOf(entry) > -1, true, `missing expected item in competion list: ${entry} Actual: ${labels}`); + } + }) + ); + return Promise.all(promises); + }) + })).then(()=>{ + vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + return Promise.resolve(); + }); + + }, (err) => { + assert.ok(false, `error in OpenTextDocument ${err}`); + }).then(() => done(), done); + }); + test('Test Signature Help', (done) => { let provider = new GoSignatureHelpProvider(); let testCases: [vscode.Position, string][] = [ From 5ae0d1047d6250d8609980f3a0bf5c974c64bd3f Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Wed, 21 Sep 2016 20:47:58 -0700 Subject: [PATCH 2/6] Fix lint errors --- src/goSuggest.ts | 43 ++++++++++++++++++++++--------------------- test/go.test.ts | 8 ++++---- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/goSuggest.ts b/src/goSuggest.ts index 4cf23a74e..0ca11a757 100644 --- a/src/goSuggest.ts +++ b/src/goSuggest.ts @@ -75,34 +75,34 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { let offset = document.offsetAt(position); let inputText = document.getText(); - + return this.runGoCode(filename, inputText, offset, inString, position, lineText).then(suggestions => { // Add importable packages matching currentword to suggestions suggestions = suggestions.concat(this.getMatchingPackages(currentWord)); - + // If no suggestions and cursor is at a dot, then check if preceeding word is a package name // If yes, then import the package in the inputText and run gocode again to get suggestions - if (suggestions.length === 0 && lineTillCurrentPosition.endsWith('.')){ - + if (suggestions.length === 0 && lineTillCurrentPosition.endsWith('.')) { + let pkgPath = this.getPackagePathFromLine(lineTillCurrentPosition); if (pkgPath) { // Now that we have the package path, import it right after the "package" statement let {imports, pkg} = parseFilePrelude(vscode.window.activeTextEditor.document.getText()); - let posToAddImport = document.offsetAt(new vscode.Position(pkg.start +1 , 0)); - let textToAdd = `import "${pkgPath}"\n` + let posToAddImport = document.offsetAt(new vscode.Position(pkg.start + 1 , 0)); + let textToAdd = `import "${pkgPath}"\n`; inputText = inputText.substr(0, posToAddImport) + textToAdd + inputText.substr(posToAddImport); offset += textToAdd.length; // Now that we have the package imported in the inputText, run gocode again return this.runGoCode(filename, inputText, offset, inString, position, lineText).then(newsuggestions => { - // Since the new suggestions are due to the package that we imported, - // add a command to do the same in the actual document in the editor + // Since the new suggestions are due to the package that we imported, + // add a command to do the same in the actual document in the editor newsuggestions.forEach(item => { item.command = { title: 'Import Package', command: 'go.import.add', arguments: [pkgPath] - } + }; }); resolve(newsuggestions); }); @@ -110,12 +110,11 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { } resolve(suggestions); }); - }); }); } - private runGoCode(filename: string, inputText: string, offset: number, inString:boolean, position: vscode.Position, lineText: string): Thenable{ + private runGoCode(filename: string, inputText: string, offset: number, inString: boolean, position: vscode.Position, lineText: string): Thenable { return new Promise((resolve, reject) => { let gocode = getBinPath('gocode'); @@ -173,7 +172,7 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { paramSnippets.push('{{' + param + '}}'); } } - item.insertText = suggest.name + '(' + paramSnippets.join(', ') + '){{}}'; + item.insertText = suggest.name + '(' + paramSnippets.join(', ') + ') {{}}'; } suggestions.push(item); }; @@ -189,13 +188,13 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { // TODO: Shouldn't lib-path also be set? private ensureGoCodeConfigured(): Thenable { let pkgPromise = listPackages().then((pkgs: string[]) => { - this.pkgsList = pkgs.map(pkg => { +this.pkgsList = pkgs.map(pkg => { let index = pkg.lastIndexOf('/'); return { - name: index === -1 ? pkg: pkg.substr(index + 1), + name: index === -1 ? pkg : pkg.substr(index + 1), path: pkg - } - }) + }; + }); }); let configPromise = new Promise((resolve, reject) => { // TODO: Since the gocode daemon is shared amongst clients, shouldn't settings be @@ -211,7 +210,9 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { }); }); }); - return Promise.all([pkgPromise, configPromise]).then(()=> {return Promise.resolve()});; + return Promise.all([pkgPromise, configPromise]).then(() => { + return Promise.resolve(); + }); ; } // Return importable packages that match given word as Completion Items @@ -227,7 +228,7 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { title: 'Import Package', command: 'go.import.add', arguments: [pkgInfo.path] - } + }; return item; }); return completionItems; @@ -250,7 +251,7 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { pkgName = wordmatches[1]; } - if (!pkgName){ + if (!pkgName) { return; } @@ -259,8 +260,8 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { return pkgInfo.name === pkgName; }); - if (matchingPackages && matchingPackages.length === 1){ + if (matchingPackages && matchingPackages.length === 1) { return matchingPackages[0].path; - } + } } } diff --git a/test/go.test.ts b/test/go.test.ts index 8edaa4b15..c58baaccd 100644 --- a/test/go.test.ts +++ b/test/go.test.ts @@ -109,8 +109,8 @@ encountered. vscode.workspace.openTextDocument(uri).then((textDocument) => { return vscode.window.showTextDocument(textDocument).then((editor => { return editor.edit(editbuilder => { - editbuilder.insert(new vscode.Position(12,0), 'by\n'); - editbuilder.insert(new vscode.Position(13,0), 'math.\n'); + editbuilder.insert(new vscode.Position(12, 0), 'by\n'); + editbuilder.insert(new vscode.Position(13, 0), 'math.\n'); }).then(() => { let promises = testCases.map(([position, expected]) => provider.provideCompletionItems(textDocument, position, null).then(items => { @@ -121,8 +121,8 @@ encountered. }) ); return Promise.all(promises); - }) - })).then(()=>{ + }); + })).then(() => { vscode.commands.executeCommand('workbench.action.closeActiveEditor'); return Promise.resolve(); }); From 1e10347dd311bc299ff93894068041af5c8af7d4 Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Wed, 28 Sep 2016 19:20:08 -0700 Subject: [PATCH 3/6] Use better regex, add pkg path to suggestion doc --- src/goSuggest.ts | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/goSuggest.ts b/src/goSuggest.ts index 0ca11a757..f1f21d3cb 100644 --- a/src/goSuggest.ts +++ b/src/goSuggest.ts @@ -222,7 +222,8 @@ this.pkgsList = pkgs.map(pkg => { return pkgInfo.name.startsWith(word); }).map((pkgInfo: PackageInfo) => { let item = new vscode.CompletionItem(pkgInfo.name, vscode.CompletionItemKind.Keyword); - item.detail = 'Add import'; + item.detail = pkgInfo.path; + item.documentation = 'Imports the package'; item.insertText = pkgInfo.name; item.command = { title: 'Import Package', @@ -236,25 +237,13 @@ this.pkgsList = pkgs.map(pkg => { // Given a line ending with dot, return the word preceeding the dot if it is a package name that can be imported private getPackagePathFromLine(line: string): string { - let pkgName = null; - - // There could be multiple dots in the line - // we are interested in the word preceeding the last one - let splits = line.split('.'); - line = splits[splits.length - 2]; - - // There could be multiple words in the line - // we are interested in the last one - let wordmatches = null; - let pattern = /(\w+)/g; - while (wordmatches = pattern.exec(line)) { - pkgName = wordmatches[1]; - } - - if (!pkgName) { + let pattern = /(\w+)\.$/g; + let wordmatches = pattern.exec(line); + if (!wordmatches) { return; } + let [_, pkgName] = wordmatches; // Word is isolated. Now check pkgsList for a match let matchingPackages = this.pkgsList.filter(pkgInfo => { return pkgInfo.name === pkgName; From 5d6e3d67961e689cfe74c5b6810b5644ae43e36e Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Wed, 28 Sep 2016 19:53:41 -0700 Subject: [PATCH 4/6] Use additionalTextEdits so that 'useCodeSnippetsOnFunctionSuggest' feature continues to work --- src/goImport.ts | 59 ++++++++++++++++++++++++------------------------ src/goSuggest.ts | 11 ++++----- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/goImport.ts b/src/goImport.ts index ecbeba1b9..400d4b4dc 100644 --- a/src/goImport.ts +++ b/src/goImport.ts @@ -61,36 +61,37 @@ function askUserForImport(): Thenable { }); } +export function getTextEditForAddImport(arg: string): vscode.TextEdit { + // Import name wasn't provided + if (arg === undefined) { + return null; + } + + let {imports, pkg} = parseFilePrelude(vscode.window.activeTextEditor.document.getText()); + let multis = imports.filter(x => x.kind === 'multi'); + if (multis.length > 0) { + // There is a multiple import declaration, add to the last one + let closeParenLine = multis[multis.length - 1].end; + return vscode.TextEdit.insert(new vscode.Position(closeParenLine, 0), '\t"' + arg + '"\n'); + } else if (imports.length > 0) { + // There are only single import declarations, add after the last one + let lastSingleImport = imports[imports.length - 1].end; + return vscode.TextEdit.insert(new vscode.Position(lastSingleImport + 1, 0), 'import "' + arg + '"\n'); + } else if (pkg && pkg.start >= 0) { + // There are no import declarations, but there is a package declaration + return vscode.TextEdit.insert(new vscode.Position(pkg.start + 1, 0), '\nimport (\n\t"' + arg + '"\n)\n'); + } else { + // There are no imports and no package declaration - give up + return null; + } +} + export function addImport(arg: string) { let p = arg ? Promise.resolve(arg) : askUserForImport(); p.then(imp => { - // Import name wasn't provided - if (imp === undefined) { - return null; - } - - let {imports, pkg} = parseFilePrelude(vscode.window.activeTextEditor.document.getText()); - let multis = imports.filter(x => x.kind === 'multi'); - if (multis.length > 0) { - // There is a multiple import declaration, add to the last one - let closeParenLine = multis[multis.length - 1].end; - return vscode.window.activeTextEditor.edit(editBuilder => { - editBuilder.insert(new vscode.Position(closeParenLine, 0), '\t"' + imp + '"\n'); - }); - } else if (imports.length > 0) { - // There are only single import declarations, add after the last one - let lastSingleImport = imports[imports.length - 1].end; - return vscode.window.activeTextEditor.edit(editBuilder => { - editBuilder.insert(new vscode.Position(lastSingleImport + 1, 0), 'import "' + imp + '"\n'); - }); - } else if (pkg && pkg.start >= 0) { - // There are no import declarations, but there is a package declaration - return vscode.window.activeTextEditor.edit(editBuilder => { - editBuilder.insert(new vscode.Position(pkg.start + 1, 0), '\nimport (\n\t"' + imp + '"\n)\n'); - }); - } else { - // There are no imports and no package declaration - give up - return null; - } + let edit = getTextEditForAddImport(imp); + vscode.window.activeTextEditor.edit(editBuilder => { + editBuilder.insert(edit.range.start, edit.newText); + }); }); -} +} \ No newline at end of file diff --git a/src/goSuggest.ts b/src/goSuggest.ts index f1f21d3cb..3c39c86e1 100644 --- a/src/goSuggest.ts +++ b/src/goSuggest.ts @@ -11,7 +11,7 @@ import { dirname, basename } from 'path'; import { getBinPath } from './goPath'; import { parameters, parseFilePrelude } from './util'; import { promptForMissingTool } from './goInstallTools'; -import { listPackages } from './goImport'; +import { listPackages, getTextEditForAddImport } from './goImport'; function vscodeKindFromGoCodeClass(kind: string): vscode.CompletionItemKind { switch (kind) { @@ -96,13 +96,10 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { // Now that we have the package imported in the inputText, run gocode again return this.runGoCode(filename, inputText, offset, inString, position, lineText).then(newsuggestions => { // Since the new suggestions are due to the package that we imported, - // add a command to do the same in the actual document in the editor + // add additionalTextEdits to do the same in the actual document in the editor + // We use additionalTextEdits instead of command so that 'useCodeSnippetsOnFunctionSuggest' feature continues to work newsuggestions.forEach(item => { - item.command = { - title: 'Import Package', - command: 'go.import.add', - arguments: [pkgPath] - }; + item.additionalTextEdits = [getTextEditForAddImport(pkgPath)]; }); resolve(newsuggestions); }); From 7b7ac12419cac189b455d2b88916cf9d33b68c64 Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Wed, 28 Sep 2016 19:58:33 -0700 Subject: [PATCH 5/6] Apply edit only if edit exists --- src/goImport.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/goImport.ts b/src/goImport.ts index 400d4b4dc..2568dad38 100644 --- a/src/goImport.ts +++ b/src/goImport.ts @@ -90,8 +90,10 @@ export function addImport(arg: string) { let p = arg ? Promise.resolve(arg) : askUserForImport(); p.then(imp => { let edit = getTextEditForAddImport(imp); - vscode.window.activeTextEditor.edit(editBuilder => { - editBuilder.insert(edit.range.start, edit.newText); - }); + if (edit) { + vscode.window.activeTextEditor.edit(editBuilder => { + editBuilder.insert(edit.range.start, edit.newText); + }); + } }); } \ No newline at end of file From e9d86d603d52e526e7dbf25b42171aa456d4bcee Mon Sep 17 00:00:00 2001 From: Ramya Achutha Rao Date: Thu, 6 Oct 2016 15:22:11 -0700 Subject: [PATCH 6/6] Add setting autocomplteUnimportedPackages, dont show imported pkgs --- package.json | 5 +++++ src/goSuggest.ts | 25 +++++++++++++++++-------- test/go.test.ts | 5 ++++- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 1baf31c76..9fe4cbba7 100644 --- a/package.json +++ b/package.json @@ -331,6 +331,11 @@ "type": "object", "default": {}, "description": "Environment variables that will passed to the process that runs the Go tests" + }, + "go.autocomplteUnimportedPackages": { + "type": "boolean", + "default": false, + "description": "Autocomplete members from unimported packages." } } } diff --git a/src/goSuggest.ts b/src/goSuggest.ts index 3c39c86e1..3805ccd1e 100644 --- a/src/goSuggest.ts +++ b/src/goSuggest.ts @@ -46,11 +46,16 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { private pkgsList: PackageInfo[] = []; public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable { + return this.provideCompletionItemsInternal(document, position, token, vscode.workspace.getConfiguration('go')); + } + + public provideCompletionItemsInternal(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, config: vscode.WorkspaceConfiguration): Thenable { return this.ensureGoCodeConfigured().then(() => { return new Promise((resolve, reject) => { let filename = document.fileName; let lineText = document.lineAt(position.line).text; let lineTillCurrentPosition = lineText.substr(0, position.character); + let autocompleteUnimportedPackages = config['autocomplteUnimportedPackages'] === true; if (lineText.match(/^\s*\/\//)) { return resolve([]); @@ -77,6 +82,10 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { let inputText = document.getText(); return this.runGoCode(filename, inputText, offset, inString, position, lineText).then(suggestions => { + if (!autocompleteUnimportedPackages) { + return resolve(suggestions); + } + // Add importable packages matching currentword to suggestions suggestions = suggestions.concat(this.getMatchingPackages(currentWord)); @@ -184,15 +193,15 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { } // TODO: Shouldn't lib-path also be set? private ensureGoCodeConfigured(): Thenable { - let pkgPromise = listPackages().then((pkgs: string[]) => { -this.pkgsList = pkgs.map(pkg => { - let index = pkg.lastIndexOf('/'); - return { - name: index === -1 ? pkg : pkg.substr(index + 1), - path: pkg - }; + let pkgPromise = listPackages(true).then((pkgs: string[]) => { + this.pkgsList = pkgs.map(pkg => { + let index = pkg.lastIndexOf('/'); + return { + name: index === -1 ? pkg : pkg.substr(index + 1), + path: pkg + }; + }); }); - }); let configPromise = new Promise((resolve, reject) => { // TODO: Since the gocode daemon is shared amongst clients, shouldn't settings be // adjusted per-invocation to avoid conflicts from other gocode-using programs? diff --git a/test/go.test.ts b/test/go.test.ts index c58baaccd..028781eb4 100644 --- a/test/go.test.ts +++ b/test/go.test.ts @@ -99,6 +99,9 @@ encountered. }); test('Test Completion on unimported packages', (done) => { + let config = Object.create(vscode.workspace.getConfiguration('go'), { + 'autocomplteUnimportedPackages': { value: true } + }); let provider = new GoCompletionItemProvider(); let testCases: [vscode.Position, string[]][] = [ [new vscode.Position(12, 2), ['bytes']], @@ -113,7 +116,7 @@ encountered. editbuilder.insert(new vscode.Position(13, 0), 'math.\n'); }).then(() => { let promises = testCases.map(([position, expected]) => - provider.provideCompletionItems(textDocument, position, null).then(items => { + provider.provideCompletionItemsInternal(textDocument, position, null, config).then(items => { let labels = items.map(x => x.label); for (let entry of expected) { assert.equal(labels.indexOf(entry) > -1, true, `missing expected item in competion list: ${entry} Actual: ${labels}`);