diff --git a/pkg/analyzer/decl.go b/pkg/analyzer/decl.go index 0b314a02..d70d1cdd 100644 --- a/pkg/analyzer/decl.go +++ b/pkg/analyzer/decl.go @@ -81,7 +81,7 @@ func funcToItem(fn *ast.FuncDecl) *CompletionItem { ci := &CompletionItem{ Label: fn.Name.String(), Kind: Function, - Documentation: fn.Doc.Text(), + Documentation: formatDoc(fn.Doc.Text()), } ci.Detail = funcToString(fn.Type) diff --git a/pkg/analyzer/docfmt.go b/pkg/analyzer/docfmt.go new file mode 100644 index 00000000..cf5c8469 --- /dev/null +++ b/pkg/analyzer/docfmt.go @@ -0,0 +1,79 @@ +package analyzer + +import "strings" + +const ( + newLineChar = '\n' + tabChar = '\t' +) + +var ( + mdCodeTag = []byte("```\n") + spaceIdent = " " + spaceIdentLen = len(spaceIdent) +) + +func isDocLine(line string) bool { + // Source code in Go usually doc starts with tab char + if line[0] == tabChar { + return true + } + + // Workaround for some packages with double space as doc indent (line "net/http") + if (len(line) > spaceIdentLen) && (line[:spaceIdentLen] == spaceIdent) { + return true + } + + return false +} + +func formatDoc(str string) MarkdownString { + if str == "" { + return MarkdownString{Value: str} + } + + w := strings.Builder{} + docStart := false + + lines := strings.Split(str, "\n") + for _, line := range lines { + if len(line) == 0 { + w.WriteRune(newLineChar) + continue + } + + // Source code in Go doc starts with tab char + if isDocLine(line) { + if !docStart { + // Put markdown code section + // if we met first source code line + docStart = true + w.Write(mdCodeTag) + } + + w.WriteString(line) + w.WriteRune(newLineChar) + continue + } + + // Else - regular text + if docStart { + // Terminate code block if previous + // was open and not terminated + docStart = false + w.Write(mdCodeTag) + } + + w.WriteString(line) + w.WriteRune(newLineChar) + } + + if docStart { + // close markdown code block if wasn't closed + w.Write(mdCodeTag) + } + + return MarkdownString{ + Value: w.String(), + } +} diff --git a/pkg/analyzer/scanner.go b/pkg/analyzer/scanner.go index ba206eac..e2949fdf 100644 --- a/pkg/analyzer/scanner.go +++ b/pkg/analyzer/scanner.go @@ -79,7 +79,7 @@ func (p *PackageScanner) appendFunc(fn *ast.FuncDecl, dest *SymbolIndex) { func (p *PackageScanner) Scan() (PackageSummary, error) { sum := NewPackageSummary() set := token.NewFileSet() - packs, err := parser.ParseDir(set, p.path, nil, 0) + packs, err := parser.ParseDir(set, p.path, nil, parser.ParseComments) if err != nil { return sum, err } diff --git a/web/src/editor/provider.ts b/web/src/editor/provider.ts index b882168b..6c59e81e 100644 --- a/web/src/editor/provider.ts +++ b/web/src/editor/provider.ts @@ -15,28 +15,28 @@ const COMPL_REGEXP = /([a-zA-Z0-9_]+)(\.([A-Za-z0-9_]+))?$/; const R_GROUP_PKG = 1; const R_GROUP_METHOD = 3; -class GoCompletionItemProvider implements monaco.languages.CompletionItemProvider { - constructor(private client: IAPIClient) {} +const parseExpression = (expr: string) => { + COMPL_REGEXP.lastIndex = 0; // Reset regex state + const m = COMPL_REGEXP.exec(expr); + if (!m) { + return null; + } - private parseExpression(expr: string) { - COMPL_REGEXP.lastIndex = 0; // Reset regex state - const m = COMPL_REGEXP.exec(expr); - if (!m) { - return null; - } + const varName = m[R_GROUP_PKG]; + const propValue = m[R_GROUP_METHOD]; - const varName = m[R_GROUP_PKG]; - const propValue = m[R_GROUP_METHOD]; + if (!propValue) { + return {value: varName}; + } - if (!propValue) { - return {value: varName}; - } + return { + packageName: varName, + value: propValue + }; +}; - return { - packageName: varName, - value: propValue - }; - } +class GoCompletionItemProvider implements monaco.languages.CompletionItemProvider { + constructor(private client: IAPIClient) {} async provideCompletionItems(model: ITextModel, position: Position, context: CompletionContext, token: CancellationToken): Promise { const val = model.getValueInRange({ @@ -46,7 +46,7 @@ class GoCompletionItemProvider implements monaco.languages.CompletionItemProvide endColumn: position.column, }).trim(); - const query = this.parseExpression(val); + const query = parseExpression(val); if (!query) { return Promise.resolve({suggestions: []}); }