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