X-Git-Url: https://git.josue.xyz/?a=blobdiff_plain;f=.config%2Fcoc%2Fextensions%2Fcoc-go-data%2Ftools%2Fpkg%2Fmod%2Fgolang.org%2Fx%2Ftools%40v0.1.1-0.20210319172145-bda8f5cee399%2Finternal%2Flsp%2Fsource%2Fcompletion%2Fpackage.go;fp=.config%2Fcoc%2Fextensions%2Fcoc-go-data%2Ftools%2Fpkg%2Fmod%2Fgolang.org%2Fx%2Ftools%40v0.1.1-0.20210319172145-bda8f5cee399%2Finternal%2Flsp%2Fsource%2Fcompletion%2Fpackage.go;h=483223a84295ac2e20659f1fb9209297c6097a0c;hb=3c06164f15bd10aed7d66b6314764a2961a14762;hp=0000000000000000000000000000000000000000;hpb=0e9c3ceb40901f4d44981c1376cb9e23a248e006;p=dotfiles%2F.git diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.1-0.20210319172145-bda8f5cee399/internal/lsp/source/completion/package.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.1-0.20210319172145-bda8f5cee399/internal/lsp/source/completion/package.go new file mode 100644 index 00000000..483223a8 --- /dev/null +++ b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.1.1-0.20210319172145-bda8f5cee399/internal/lsp/source/completion/package.go @@ -0,0 +1,286 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "context" + "fmt" + "go/ast" + "go/parser" + "go/scanner" + "go/token" + "go/types" + "path/filepath" + "strings" + + "golang.org/x/tools/internal/lsp/fuzzy" + "golang.org/x/tools/internal/lsp/protocol" + "golang.org/x/tools/internal/lsp/source" + "golang.org/x/tools/internal/span" + errors "golang.org/x/xerrors" +) + +// packageClauseCompletions offers completions for a package declaration when +// one is not present in the given file. +func packageClauseCompletions(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, pos protocol.Position) ([]CompletionItem, *Selection, error) { + // We know that the AST for this file will be empty due to the missing + // package declaration, but parse it anyway to get a mapper. + pgf, err := snapshot.ParseGo(ctx, fh, source.ParseFull) + if err != nil { + return nil, nil, err + } + + cursorSpan, err := pgf.Mapper.PointSpan(pos) + if err != nil { + return nil, nil, err + } + rng, err := cursorSpan.Range(pgf.Mapper.Converter) + if err != nil { + return nil, nil, err + } + + surrounding, err := packageCompletionSurrounding(snapshot.FileSet(), fh, pgf, rng.Start) + if err != nil { + return nil, nil, errors.Errorf("invalid position for package completion: %w", err) + } + + packageSuggestions, err := packageSuggestions(ctx, snapshot, fh.URI(), "") + if err != nil { + return nil, nil, err + } + + var items []CompletionItem + for _, pkg := range packageSuggestions { + insertText := fmt.Sprintf("package %s", pkg.name) + items = append(items, CompletionItem{ + Label: insertText, + Kind: protocol.ModuleCompletion, + InsertText: insertText, + Score: pkg.score, + }) + } + + return items, surrounding, nil +} + +// packageCompletionSurrounding returns surrounding for package completion if a +// package completions can be suggested at a given position. A valid location +// for package completion is above any declarations or import statements. +func packageCompletionSurrounding(fset *token.FileSet, fh source.FileHandle, pgf *source.ParsedGoFile, pos token.Pos) (*Selection, error) { + src, err := fh.Read() + if err != nil { + return nil, err + } + // If the file lacks a package declaration, the parser will return an empty + // AST. As a work-around, try to parse an expression from the file contents. + expr, _ := parser.ParseExprFrom(fset, fh.URI().Filename(), src, parser.Mode(0)) + if expr == nil { + return nil, fmt.Errorf("unparseable file (%s)", fh.URI()) + } + tok := fset.File(expr.Pos()) + cursor := tok.Pos(pgf.Tok.Offset(pos)) + m := &protocol.ColumnMapper{ + URI: pgf.URI, + Content: src, + Converter: span.NewContentConverter(fh.URI().Filename(), src), + } + + // If we were able to parse out an identifier as the first expression from + // the file, it may be the beginning of a package declaration ("pack "). + // We can offer package completions if the cursor is in the identifier. + if name, ok := expr.(*ast.Ident); ok { + if cursor >= name.Pos() && cursor <= name.End() { + if !strings.HasPrefix(PACKAGE, name.Name) { + return nil, fmt.Errorf("cursor in non-matching ident") + } + return &Selection{ + content: name.Name, + cursor: cursor, + MappedRange: source.NewMappedRange(fset, m, name.Pos(), name.End()), + }, nil + } + } + + // The file is invalid, but it contains an expression that we were able to + // parse. We will use this expression to construct the cursor's + // "surrounding". + + // First, consider the possibility that we have a valid "package" keyword + // with an empty package name ("package "). "package" is parsed as an + // *ast.BadDecl since it is a keyword. This logic would allow "package" to + // appear on any line of the file as long as it's the first code expression + // in the file. + lines := strings.Split(string(src), "\n") + cursorLine := tok.Line(cursor) + if cursorLine <= 0 || cursorLine > len(lines) { + return nil, fmt.Errorf("invalid line number") + } + if fset.Position(expr.Pos()).Line == cursorLine { + words := strings.Fields(lines[cursorLine-1]) + if len(words) > 0 && words[0] == PACKAGE { + content := PACKAGE + // Account for spaces if there are any. + if len(words) > 1 { + content += " " + } + + start := expr.Pos() + end := token.Pos(int(expr.Pos()) + len(content) + 1) + // We have verified that we have a valid 'package' keyword as our + // first expression. Ensure that cursor is in this keyword or + // otherwise fallback to the general case. + if cursor >= start && cursor <= end { + return &Selection{ + content: content, + cursor: cursor, + MappedRange: source.NewMappedRange(fset, m, start, end), + }, nil + } + } + } + + // If the cursor is after the start of the expression, no package + // declaration will be valid. + if cursor > expr.Pos() { + return nil, fmt.Errorf("cursor after expression") + } + + // If the cursor is in a comment, don't offer any completions. + if cursorInComment(fset, cursor, src) { + return nil, fmt.Errorf("cursor in comment") + } + + // The surrounding range in this case is the cursor except for empty file, + // in which case it's end of file - 1 + start, end := cursor, cursor + if tok.Size() == 0 { + start, end = tok.Pos(0)-1, tok.Pos(0)-1 + } + + return &Selection{ + content: "", + cursor: cursor, + MappedRange: source.NewMappedRange(fset, m, start, end), + }, nil +} + +func cursorInComment(fset *token.FileSet, cursor token.Pos, src []byte) bool { + var s scanner.Scanner + s.Init(fset.File(cursor), src, func(_ token.Position, _ string) {}, scanner.ScanComments) + for { + pos, tok, lit := s.Scan() + if pos <= cursor && cursor <= token.Pos(int(pos)+len(lit)) { + return tok == token.COMMENT + } + if tok == token.EOF { + break + } + } + return false +} + +// packageNameCompletions returns name completions for a package clause using +// the current name as prefix. +func (c *completer) packageNameCompletions(ctx context.Context, fileURI span.URI, name *ast.Ident) error { + cursor := int(c.pos - name.NamePos) + if cursor < 0 || cursor > len(name.Name) { + return errors.New("cursor is not in package name identifier") + } + + c.completionContext.packageCompletion = true + + prefix := name.Name[:cursor] + packageSuggestions, err := packageSuggestions(ctx, c.snapshot, fileURI, prefix) + if err != nil { + return err + } + + for _, pkg := range packageSuggestions { + c.deepState.enqueue(pkg) + } + return nil +} + +// packageSuggestions returns a list of packages from workspace packages that +// have the given prefix and are used in the same directory as the given +// file. This also includes test packages for these packages (_test) and +// the directory name itself. +func packageSuggestions(ctx context.Context, snapshot source.Snapshot, fileURI span.URI, prefix string) ([]candidate, error) { + workspacePackages, err := snapshot.WorkspacePackages(ctx) + if err != nil { + return nil, err + } + + dirPath := filepath.Dir(string(fileURI)) + dirName := filepath.Base(dirPath) + + seenPkgs := make(map[string]struct{}) + + toCandidate := func(name string, score float64) candidate { + obj := types.NewPkgName(0, nil, name, types.NewPackage("", name)) + return candidate{obj: obj, name: name, detail: name, score: score} + } + + matcher := fuzzy.NewMatcher(prefix) + + // The `go` command by default only allows one package per directory but we + // support multiple package suggestions since gopls is build system agnostic. + var packages []candidate + for _, pkg := range workspacePackages { + if pkg.Name() == "main" || pkg.Name() == "" { + continue + } + if _, ok := seenPkgs[pkg.Name()]; ok { + continue + } + + // Only add packages that are previously used in the current directory. + var relevantPkg bool + for _, pgf := range pkg.CompiledGoFiles() { + if filepath.Dir(string(pgf.URI)) == dirPath { + relevantPkg = true + break + } + } + if !relevantPkg { + continue + } + + // Add a found package used in current directory as a high relevance + // suggestion and the test package for it as a medium relevance + // suggestion. + if score := float64(matcher.Score(pkg.Name())); score > 0 { + packages = append(packages, toCandidate(pkg.Name(), score*highScore)) + } + seenPkgs[pkg.Name()] = struct{}{} + + testPkgName := pkg.Name() + "_test" + if _, ok := seenPkgs[testPkgName]; ok || strings.HasSuffix(pkg.Name(), "_test") { + continue + } + if score := float64(matcher.Score(testPkgName)); score > 0 { + packages = append(packages, toCandidate(testPkgName, score*stdScore)) + } + seenPkgs[testPkgName] = struct{}{} + } + + // Add current directory name as a low relevance suggestion. + if _, ok := seenPkgs[dirName]; !ok { + if score := float64(matcher.Score(dirName)); score > 0 { + packages = append(packages, toCandidate(dirName, score*lowScore)) + } + + testDirName := dirName + "_test" + if score := float64(matcher.Score(testDirName)); score > 0 { + packages = append(packages, toCandidate(testDirName, score*lowScore)) + } + } + + if score := float64(matcher.Score("main")); score > 0 { + packages = append(packages, toCandidate("main", score*lowScore)) + } + + return packages, nil +}