diff --git a/pkg/analyzer/completion_item.go b/pkg/analyzer/completion_item.go index f7702006..18ae6b8e 100644 --- a/pkg/analyzer/completion_item.go +++ b/pkg/analyzer/completion_item.go @@ -35,6 +35,18 @@ const ( Snippet ) +// CompletionItemInsertTextRule is insert text insert rule. +type CompletionItemInsertTextRule int + +const ( + // KeepWhitespace is adjust whitespace/indentation of + // multiline insert texts to match the current line indentation. + KeepWhitespace CompletionItemInsertTextRule = 1 + + // InsertAsSnippet means that `insertText` is a snippet. + InsertAsSnippet CompletionItemInsertTextRule = 4 +) + // CompletionItem is monaco-editor binding type CompletionItem struct { // Label is item label @@ -47,6 +59,9 @@ type CompletionItem struct { Documentation interface{} `json:"documentation"` // InsertText is text to be inserted InsertText string `json:"insertText"` + // InsertTextRules is a string or snippet that should be inserted in a document + // when selecting this completion. When `falsy` the label in used. + InsertTextRules CompletionItemInsertTextRule `json:"insertTextRules,omitempty"` } // MarkdownString is monaco-editor string with markdown diff --git a/pkg/analyzer/decl.go b/pkg/analyzer/decl.go index d70d1cdd..63acec7d 100644 --- a/pkg/analyzer/decl.go +++ b/pkg/analyzer/decl.go @@ -2,6 +2,7 @@ package analyzer import ( "go/ast" + "strconv" "strings" ) @@ -77,14 +78,52 @@ func funcToString(fn *ast.FuncType) string { return str } +// formatFuncInsertText returns `insertText` snippet template for monaco-editor +// from function signature. +// +// Example: +// func sum(a, b int) int +// Will output: +// "sum(${1:a}, ${2:b})" +func formatFuncInsertText(fn *ast.FuncDecl) string { + sb := strings.Builder{} + sb.WriteString(fn.Name.String()) + sb.WriteByte('(') + + if fn.Type.Params != nil && fn.Type.Params.List != nil { + pos := 1 // in monaco, first input argument should start with 0 + params := make([]string, 0, fn.Type.Params.NumFields()) + + for _, param := range fn.Type.Params.List { + if len(param.Names) == 0 { + // $pos + params = append(params, "$"+strconv.Itoa(pos)) + pos++ + continue + } + + for _, p := range param.Names { + // ${pos:paramName} + params = append(params, "${"+strconv.Itoa(pos)+":"+p.Name+"}") + pos++ + } + } + sb.WriteString(strings.Join(params, ", ")) + } + + sb.WriteRune(')') + return sb.String() +} + func funcToItem(fn *ast.FuncDecl) *CompletionItem { ci := &CompletionItem{ - Label: fn.Name.String(), - Kind: Function, - Documentation: formatDoc(fn.Doc.Text()), + Label: fn.Name.String(), + Kind: Function, + Detail: funcToString(fn.Type), + Documentation: formatDoc(fn.Doc.Text()), + InsertText: formatFuncInsertText(fn), + InsertTextRules: InsertAsSnippet, } - ci.Detail = funcToString(fn.Type) - ci.InsertText = ci.Label + "()" return ci } diff --git a/pkg/analyzer/scanner.go b/pkg/analyzer/scanner.go index d25280db..81dab842 100644 --- a/pkg/analyzer/scanner.go +++ b/pkg/analyzer/scanner.go @@ -78,7 +78,7 @@ func (p *PackageScanner) appendFunc(fn *ast.FuncDecl, dest *SymbolIndex) { } item := funcToItem(fn) - log.Debugf("found function '%s.%s'", p.name, item.InsertText) + log.Debugf("found function '%s.%s'", p.name, item.Detail) dest.Append(item) } diff --git a/pkg/analyzer/scanner_test.go b/pkg/analyzer/scanner_test.go index e282b27b..6e8f48b9 100644 --- a/pkg/analyzer/scanner_test.go +++ b/pkg/analyzer/scanner_test.go @@ -12,42 +12,63 @@ import ( var examplePackageSummary = PackageSummary{ Functions: newSymbolIndex([]*CompletionItem{ { - Label: "SomeFunc", - Kind: Function, - Detail: "func(val string) string", - InsertText: "SomeFunc()", + Label: "SomeFunc", + Kind: Function, + Detail: "func(val string) string", + InsertText: "SomeFunc(${1:val})", + InsertTextRules: InsertAsSnippet, Documentation: NewMarkdownString( "SomeFunc is test function sample\nwith doc that contains code sample:" + "\n\n```\n\ta := \"foo\"\n\tfmt.PrintLn(a)\n\n```\nend\n\n", ), }, { - Label: "ChanArrFunc", - Kind: Function, - Detail: "func(items ...string) chan string", - InsertText: "ChanArrFunc()", - Documentation: NewMarkdownString("ChanArrFunc is stub\n\n"), + Label: "ChanArrFunc", + Kind: Function, + Detail: "func(items ...string) chan string", + InsertText: "ChanArrFunc(${1:items})", + InsertTextRules: InsertAsSnippet, + Documentation: NewMarkdownString("ChanArrFunc is stub\n\n"), }, { - Label: "SomeFunc2", - Kind: Function, - Detail: "func(m map[string]interface{}, v *int) []interface{}", - InsertText: "SomeFunc2()", - Documentation: NewMarkdownString("SomeFunc2 is func stub\n\n"), + Label: "SomeFunc2", + Kind: Function, + Detail: "func(m map[string]interface{}, v *int) []interface{}", + InsertText: "SomeFunc2(${1:m}, ${2:v})", + InsertTextRules: InsertAsSnippet, + Documentation: NewMarkdownString("SomeFunc2 is func stub\n\n"), }, { - Label: "IfaceFunc", - Kind: Function, - Detail: "func() Action", - InsertText: "IfaceFunc()", - Documentation: NewMarkdownString("IfaceFunc is stub with unterminated code block\n```\n\t2 + 2\n\n```\n"), + Label: "IfaceFunc", + Kind: Function, + Detail: "func() Action", + InsertText: "IfaceFunc()", + InsertTextRules: InsertAsSnippet, + Documentation: NewMarkdownString("IfaceFunc is stub with unterminated code block\n```\n\t2 + 2\n\n```\n"), }, { - Label: "FuncReturnFuncAndIface", - Kind: Function, - Detail: "func() (func() (string, error), interface{\nf func()\n})", - InsertText: "FuncReturnFuncAndIface()", - Documentation: NewMarkdownString("FuncReturnFuncAndIface is stub\n\n"), + Label: "FuncReturnFuncAndIface", + Kind: Function, + Detail: "func() (func() (string, error), interface{\nf func()\n})", + InsertText: "FuncReturnFuncAndIface()", + InsertTextRules: InsertAsSnippet, + Documentation: NewMarkdownString("FuncReturnFuncAndIface is stub\n\n"), + }, + { + Label: "XXX", + Kind: Function, + Detail: "func(a string, b string)", + InsertText: "XXX(${1:a}, ${2:b})", + InsertTextRules: InsertAsSnippet, + Documentation: NewMarkdownString("XXX is function example\n\n"), + }, + { + Label: "FuncUnnamedParams", + Kind: Function, + Detail: "func(string)", + InsertText: "FuncUnnamedParams($1)", + InsertTextRules: InsertAsSnippet, + Documentation: NewMarkdownString("FuncUnnamedParams is function with unnamed params\n\n"), }, }), Values: newSymbolIndex([]*CompletionItem{ diff --git a/pkg/analyzer/testdata/src/example/example.go b/pkg/analyzer/testdata/src/example/example.go index f88a7947..377c0d1a 100644 --- a/pkg/analyzer/testdata/src/example/example.go +++ b/pkg/analyzer/testdata/src/example/example.go @@ -77,3 +77,11 @@ func IfaceFunc() Action { func FuncReturnFuncAndIface() (func() (string, error), interface{ f() }) { return nil, nil } + +// XXX is function example +func XXX(a, b string) { + +} + +// FuncUnnamedParams is function with unnamed params +func FuncUnnamedParams(string) {} diff --git a/web/src/ChangeLogModal.tsx b/web/src/ChangeLogModal.tsx index 96c2c214..2f06a492 100644 --- a/web/src/ChangeLogModal.tsx +++ b/web/src/ChangeLogModal.tsx @@ -39,31 +39,14 @@ export default function ChangeLogModal(props: ChangeLogModalProps) {
Interface - Global
- Interface - Settings -
Interface - Editor
iferr - Error handling snippetswitch - Quick switch declarationtypestruct - Quickly declare structfmtprintf - fmt.Printf shorthand
diff --git a/web/src/Header.tsx b/web/src/Header.tsx
index 89468a37..427e2186 100644
--- a/web/src/Header.tsx
+++ b/web/src/Header.tsx
@@ -46,6 +46,17 @@ export class Header extends React.Component