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  }