diff --git a/src/goSuggest.ts b/src/goSuggest.ts index bcc7eb330..b8aa4f7c6 100644 --- a/src/goSuggest.ts +++ b/src/goSuggest.ts @@ -36,6 +36,9 @@ interface GoCodeSuggestion { type: string; } +const lineCommentRegex = /^\s*\/\/\s+/; +const exportedMemberRegex = /(const|func|type|var)\s+([A-Z]\w*)/; + export class GoCompletionItemProvider implements vscode.CompletionItemProvider { private pkgsList = new Map(); @@ -52,7 +55,17 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { let lineTillCurrentPosition = lineText.substr(0, position.character); let autocompleteUnimportedPackages = config['autocompleteUnimportedPackages'] === true && !lineText.match(/^(\s)*(import|package)(\s)+/); - // prevent completion when typing in a line comment + // triggering completions in comments on exported members + if (lineCommentRegex.test(lineTillCurrentPosition) && position.line + 1 < document.lineCount) { + let nextLine = document.lineAt(position.line + 1).text.trim(); + let memberType = nextLine.match(exportedMemberRegex); + let suggestionItem: vscode.CompletionItem; + if (memberType && memberType.length === 3) { + suggestionItem = new vscode.CompletionItem(memberType[2], vscodeKindFromGoCodeClass(memberType[1])); + } + return resolve(suggestionItem ? [suggestionItem] : []); + } + // prevent completion when typing in a line comment that doesnt start from the beginning of the line const commentIndex = lineText.indexOf('//'); if (commentIndex >= 0 && position.character > commentIndex) { return resolve([]); @@ -345,4 +358,4 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider { return matchingPackages[0]; } } -} +} \ No newline at end of file diff --git a/test/fixtures/completions/exportedMemberDocs.go b/test/fixtures/completions/exportedMemberDocs.go new file mode 100644 index 000000000..3f8560dd4 --- /dev/null +++ b/test/fixtures/completions/exportedMemberDocs.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" +) + +// L +var Language = "english" // should not invoke completion since this is line comment + +// G +const GreetingText = "Hello" + +// S +func SayHello() { + fmt.Println("Says hello!") +} + +// HelloParams +type HelloParams struct { + language string +} diff --git a/test/go.test.ts b/test/go.test.ts index 824d3a8da..a3b21194d 100644 --- a/test/go.test.ts +++ b/test/go.test.ts @@ -66,6 +66,7 @@ suite('Go Extension Tests', () => { fs.copySync(path.join(fixtureSourcePath, 'buildTags', 'hello.go'), path.join(fixturePath, 'buildTags', 'hello.go')); fs.copySync(path.join(fixtureSourcePath, 'completions', 'unimportedPkgs.go'), path.join(fixturePath, 'completions', 'unimportedPkgs.go')); fs.copySync(path.join(fixtureSourcePath, 'completions', 'snippets.go'), path.join(fixturePath, 'completions', 'snippets.go')); + fs.copySync(path.join(fixtureSourcePath, 'completions', 'exportedMemberDocs.go'), path.join(fixturePath, 'completions', 'exportedMemberDocs.go')); fs.copySync(path.join(fixtureSourcePath, 'importTest', 'noimports.go'), path.join(fixturePath, 'importTest', 'noimports.go')); fs.copySync(path.join(fixtureSourcePath, 'importTest', 'groupImports.go'), path.join(fixturePath, 'importTest', 'groupImports.go')); fs.copySync(path.join(fixtureSourcePath, 'importTest', 'singleImports.go'), path.join(fixturePath, 'importTest', 'singleImports.go')); @@ -278,9 +279,9 @@ It returns the number of bytes written and any write error encountered. for (let i in expected) { for (let j in sortedDiagnostics) { if (expected[i].line - && (expected[i].line === sortedDiagnostics[j].line) - && (expected[i].severity === sortedDiagnostics[j].severity) - && (expected[i].msg === sortedDiagnostics[j].msg)) { + && (expected[i].line === sortedDiagnostics[j].line) + && (expected[i].severity === sortedDiagnostics[j].severity) + && (expected[i].msg === sortedDiagnostics[j].msg)) { matchCount++; } } @@ -404,8 +405,8 @@ It returns the number of bytes written and any write error encountered. for (let i in expected) { for (let j in sortedDiagnostics) { if ((expected[i].line === sortedDiagnostics[j].line) - && (expected[i].severity === sortedDiagnostics[j].severity) - && (expected[i].msg === sortedDiagnostics[j].msg)) { + && (expected[i].severity === sortedDiagnostics[j].severity) + && (expected[i].msg === sortedDiagnostics[j].msg)) { matchCount++; } } @@ -776,36 +777,36 @@ It returns the number of bytes written and any write error encountered. vscode.workspace.openTextDocument(uri).then((textDocument) => { return vscode.window.showTextDocument(textDocument).then(editor => { - let noFunctionSnippet = provider.provideCompletionItemsInternal(editor.document, new vscode.Position(9, 6), null, Object.create(baseConfig, {'useCodeSnippetsOnFunctionSuggest': { value: false }})).then(items => { - let item = items.find(x => x.label === 'Print'); - assert.equal(!item.insertText, true); - }); + let noFunctionSnippet = provider.provideCompletionItemsInternal(editor.document, new vscode.Position(9, 6), null, Object.create(baseConfig, { 'useCodeSnippetsOnFunctionSuggest': { value: false } })).then(items => { + let item = items.find(x => x.label === 'Print'); + assert.equal(!item.insertText, true); + }); - let withFunctionSnippet = provider.provideCompletionItemsInternal(editor.document, new vscode.Position(9, 6), null, Object.create(baseConfig, {'useCodeSnippetsOnFunctionSuggest': { value: true }})).then(items => { - let item = items.find(x => x.label === 'Print'); - assert.equal((item.insertText).value, 'Print(${1:a ...interface{\\}})'); + let withFunctionSnippet = provider.provideCompletionItemsInternal(editor.document, new vscode.Position(9, 6), null, Object.create(baseConfig, { 'useCodeSnippetsOnFunctionSuggest': { value: true } })).then(items => { + let item = items.find(x => x.label === 'Print'); + assert.equal((item.insertText).value, 'Print(${1:a ...interface{\\}})'); - }); + }); - let withFunctionSnippetNotype = provider.provideCompletionItemsInternal(editor.document, new vscode.Position(9, 6), null, Object.create(baseConfig, {'useCodeSnippetsOnFunctionSuggestWithoutType': { value: true }})).then(items => { - let item = items.find(x => x.label === 'Print'); - assert.equal((item.insertText).value, 'Print(${1:a})'); - }); + let withFunctionSnippetNotype = provider.provideCompletionItemsInternal(editor.document, new vscode.Position(9, 6), null, Object.create(baseConfig, { 'useCodeSnippetsOnFunctionSuggestWithoutType': { value: true } })).then(items => { + let item = items.find(x => x.label === 'Print'); + assert.equal((item.insertText).value, 'Print(${1:a})'); + }); - let noFunctionAsVarSnippet = provider.provideCompletionItemsInternal(editor.document, new vscode.Position(11, 3), null, Object.create(baseConfig, {'useCodeSnippetsOnFunctionSuggest': { value: false }})).then(items => { - let item = items.find(x => x.label === 'funcAsVariable'); - assert.equal(!item.insertText, true); - }); + let noFunctionAsVarSnippet = provider.provideCompletionItemsInternal(editor.document, new vscode.Position(11, 3), null, Object.create(baseConfig, { 'useCodeSnippetsOnFunctionSuggest': { value: false } })).then(items => { + let item = items.find(x => x.label === 'funcAsVariable'); + assert.equal(!item.insertText, true); + }); - let withFunctionAsVarSnippet = provider.provideCompletionItemsInternal(editor.document, new vscode.Position(11, 3), null, Object.create(baseConfig, {'useCodeSnippetsOnFunctionSuggest': { value: true }})).then(items => { - let item = items.find(x => x.label === 'funcAsVariable'); - assert.equal((item.insertText).value, 'funcAsVariable(${1:k string})'); - }); + let withFunctionAsVarSnippet = provider.provideCompletionItemsInternal(editor.document, new vscode.Position(11, 3), null, Object.create(baseConfig, { 'useCodeSnippetsOnFunctionSuggest': { value: true } })).then(items => { + let item = items.find(x => x.label === 'funcAsVariable'); + assert.equal((item.insertText).value, 'funcAsVariable(${1:k string})'); + }); - let withFunctionAsVarSnippetNoType = provider.provideCompletionItemsInternal(editor.document, new vscode.Position(11, 3), null, Object.create(baseConfig, {'useCodeSnippetsOnFunctionSuggestWithoutType': { value: true }})).then(items => { - let item = items.find(x => x.label === 'funcAsVariable'); - assert.equal((item.insertText).value, 'funcAsVariable(${1:k})'); - }); + let withFunctionAsVarSnippetNoType = provider.provideCompletionItemsInternal(editor.document, new vscode.Position(11, 3), null, Object.create(baseConfig, { 'useCodeSnippetsOnFunctionSuggestWithoutType': { value: true } })).then(items => { + let item = items.find(x => x.label === 'funcAsVariable'); + assert.equal((item.insertText).value, 'funcAsVariable(${1:k})'); + }); let noFunctionAsTypeSnippet = provider.provideCompletionItemsInternal(editor.document, new vscode.Position(14, 0), null, Object.create(baseConfig, {'useCodeSnippetsOnFunctionSuggest': { value: false }})).then(items => { let item1 = items.find(x => x.label === 'HandlerFunc'); @@ -863,6 +864,39 @@ It returns the number of bytes written and any write error encountered. }).then(() => done(), done); }); + test('Test Completion on Comments for Exported Members', (done) => { + let provider = new GoCompletionItemProvider(); + let testCases: [vscode.Position, string[]][] = [ + [new vscode.Position(6, 4), ['Language']], + [new vscode.Position(9, 4), ['GreetingText']], + // checking for comment completions with begining of comment without space + [new vscode.Position(12, 2), []], + // cursor between /$/ this should not trigger any completion + [new vscode.Position(12, 1), []], + [new vscode.Position(12, 4), ['SayHello']], + [new vscode.Position(17, 5), ['HelloParams']], + ]; + let uri = vscode.Uri.file(path.join(fixturePath, 'completions', 'exportedMemberDocs.go')); + + vscode.workspace.openTextDocument(uri).then((textDocument) => { + return vscode.window.showTextDocument(textDocument).then(editor => { + let promises = testCases.map(([position, expected]) => + provider.provideCompletionItems(editor.document, position, null).then(items => { + let labels = items.map(x => x.label); + assert.equal(expected.length, labels.length, `expected number of completions: ${expected.length} Actual: ${labels.length} at position(${position.line},${position.character}) ${labels}`); + expected.forEach((entry, index) => { + assert.equal(entry, labels[index], `mismatch in comment completion list Expected: ${entry} Actual: ${labels[index]}`); + }); + + }) + ); + return Promise.all(promises).then(() => vscode.commands.executeCommand('workbench.action.closeActiveEditor')); + }); + }, (err) => { + assert.ok(false, `error in OpenTextDocument ${err}`); + }).then(() => done(), done); + }); + test('getImportPath()', () => { let testCases: [string, string][] = [ ['import "github.com/sirupsen/logrus"', 'github.com/sirupsen/logrus'],