github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/completion.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package lsp 6 7 import ( 8 "bytes" 9 "context" 10 "fmt" 11 "strings" 12 13 "github.com/powerman/golang-tools/internal/event" 14 "github.com/powerman/golang-tools/internal/lsp/debug/tag" 15 "github.com/powerman/golang-tools/internal/lsp/protocol" 16 "github.com/powerman/golang-tools/internal/lsp/source" 17 "github.com/powerman/golang-tools/internal/lsp/source/completion" 18 "github.com/powerman/golang-tools/internal/lsp/template" 19 "github.com/powerman/golang-tools/internal/lsp/work" 20 "github.com/powerman/golang-tools/internal/span" 21 ) 22 23 func (s *Server) completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) { 24 snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind) 25 defer release() 26 if !ok { 27 return nil, err 28 } 29 var candidates []completion.CompletionItem 30 var surrounding *completion.Selection 31 switch snapshot.View().FileKind(fh) { 32 case source.Go: 33 candidates, surrounding, err = completion.Completion(ctx, snapshot, fh, params.Position, params.Context) 34 case source.Mod: 35 candidates, surrounding = nil, nil 36 case source.Work: 37 cl, err := work.Completion(ctx, snapshot, fh, params.Position) 38 if err != nil { 39 break 40 } 41 return cl, nil 42 case source.Tmpl: 43 var cl *protocol.CompletionList 44 cl, err = template.Completion(ctx, snapshot, fh, params.Position, params.Context) 45 if err != nil { 46 break // use common error handling, candidates==nil 47 } 48 return cl, nil 49 } 50 if err != nil { 51 event.Error(ctx, "no completions found", err, tag.Position.Of(params.Position)) 52 } 53 if candidates == nil { 54 return &protocol.CompletionList{ 55 IsIncomplete: true, 56 Items: []protocol.CompletionItem{}, 57 }, nil 58 } 59 // We might need to adjust the position to account for the prefix. 60 rng, err := surrounding.Range() 61 if err != nil { 62 return nil, err 63 } 64 65 // internal/span treats end of file as the beginning of the next line, even 66 // when it's not newline-terminated. We correct for that behaviour here if 67 // end of file is not newline-terminated. See golang/go#41029. 68 src, err := fh.Read() 69 if err != nil { 70 return nil, err 71 } 72 numLines := len(bytes.Split(src, []byte("\n"))) 73 tok := snapshot.FileSet().File(surrounding.Start()) 74 eof := tok.Pos(tok.Size()) 75 76 // For newline-terminated files, the line count reported by go/token should 77 // be lower than the actual number of lines we see when splitting by \n. If 78 // they're the same, the file isn't newline-terminated. 79 if tok.Size() > 0 && tok.LineCount() == numLines { 80 // Get the span for the last character in the file-1. This is 81 // technically incorrect, but will get span to point to the previous 82 // line. 83 spn, err := span.NewRange(snapshot.FileSet(), eof-1, eof-1).Span() 84 if err != nil { 85 return nil, err 86 } 87 m := &protocol.ColumnMapper{ 88 URI: fh.URI(), 89 Converter: span.NewContentConverter(fh.URI().Filename(), src), 90 Content: src, 91 } 92 eofRng, err := m.Range(spn) 93 if err != nil { 94 return nil, err 95 } 96 // Instead of using the computed range, correct for our earlier 97 // position adjustment by adding 1 to the column, not the line number. 98 pos := protocol.Position{ 99 Line: eofRng.Start.Line, 100 Character: eofRng.Start.Character + 1, 101 } 102 if surrounding.Start() >= eof { 103 rng.Start = pos 104 } 105 if surrounding.End() >= eof { 106 rng.End = pos 107 } 108 } 109 110 // When using deep completions/fuzzy matching, report results as incomplete so 111 // client fetches updated completions after every key stroke. 112 options := snapshot.View().Options() 113 incompleteResults := options.DeepCompletion || options.Matcher == source.Fuzzy 114 115 items := toProtocolCompletionItems(candidates, rng, options) 116 117 return &protocol.CompletionList{ 118 IsIncomplete: incompleteResults, 119 Items: items, 120 }, nil 121 } 122 123 func toProtocolCompletionItems(candidates []completion.CompletionItem, rng protocol.Range, options *source.Options) []protocol.CompletionItem { 124 var ( 125 items = make([]protocol.CompletionItem, 0, len(candidates)) 126 numDeepCompletionsSeen int 127 ) 128 for i, candidate := range candidates { 129 // Limit the number of deep completions to not overwhelm the user in cases 130 // with dozens of deep completion matches. 131 if candidate.Depth > 0 { 132 if !options.DeepCompletion { 133 continue 134 } 135 if numDeepCompletionsSeen >= completion.MaxDeepCompletions { 136 continue 137 } 138 numDeepCompletionsSeen++ 139 } 140 insertText := candidate.InsertText 141 if options.InsertTextFormat == protocol.SnippetTextFormat { 142 insertText = candidate.Snippet() 143 } 144 145 // This can happen if the client has snippets disabled but the 146 // candidate only supports snippet insertion. 147 if insertText == "" { 148 continue 149 } 150 151 item := protocol.CompletionItem{ 152 Label: candidate.Label, 153 Detail: candidate.Detail, 154 Kind: candidate.Kind, 155 TextEdit: &protocol.TextEdit{ 156 NewText: insertText, 157 Range: rng, 158 }, 159 InsertTextFormat: options.InsertTextFormat, 160 AdditionalTextEdits: candidate.AdditionalTextEdits, 161 // This is a hack so that the client sorts completion results in the order 162 // according to their score. This can be removed upon the resolution of 163 // https://github.com/Microsoft/language-server-protocol/issues/348. 164 SortText: fmt.Sprintf("%05d", i), 165 166 // Trim operators (VSCode doesn't like weird characters in 167 // filterText). 168 FilterText: strings.TrimLeft(candidate.InsertText, "&*"), 169 170 Preselect: i == 0, 171 Documentation: candidate.Documentation, 172 Tags: candidate.Tags, 173 Deprecated: candidate.Deprecated, 174 } 175 items = append(items, item) 176 } 177 return items 178 }