golang.org/x/tools/gopls@v0.15.3/internal/server/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 server
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"strings"
    11  
    12  	"golang.org/x/tools/gopls/internal/file"
    13  	"golang.org/x/tools/gopls/internal/golang"
    14  	"golang.org/x/tools/gopls/internal/golang/completion"
    15  	"golang.org/x/tools/gopls/internal/protocol"
    16  	"golang.org/x/tools/gopls/internal/settings"
    17  	"golang.org/x/tools/gopls/internal/telemetry"
    18  	"golang.org/x/tools/gopls/internal/template"
    19  	"golang.org/x/tools/gopls/internal/work"
    20  	"golang.org/x/tools/internal/event"
    21  	"golang.org/x/tools/internal/event/tag"
    22  )
    23  
    24  func (s *server) Completion(ctx context.Context, params *protocol.CompletionParams) (_ *protocol.CompletionList, rerr error) {
    25  	recordLatency := telemetry.StartLatencyTimer("completion")
    26  	defer func() {
    27  		recordLatency(ctx, rerr)
    28  	}()
    29  
    30  	ctx, done := event.Start(ctx, "lsp.Server.completion", tag.URI.Of(params.TextDocument.URI))
    31  	defer done()
    32  
    33  	fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  	defer release()
    38  
    39  	var candidates []completion.CompletionItem
    40  	var surrounding *completion.Selection
    41  	switch snapshot.FileKind(fh) {
    42  	case file.Go:
    43  		candidates, surrounding, err = completion.Completion(ctx, snapshot, fh, params.Position, params.Context)
    44  	case file.Mod:
    45  		candidates, surrounding = nil, nil
    46  	case file.Work:
    47  		cl, err := work.Completion(ctx, snapshot, fh, params.Position)
    48  		if err != nil {
    49  			break
    50  		}
    51  		return cl, nil
    52  	case file.Tmpl:
    53  		var cl *protocol.CompletionList
    54  		cl, err = template.Completion(ctx, snapshot, fh, params.Position, params.Context)
    55  		if err != nil {
    56  			break // use common error handling, candidates==nil
    57  		}
    58  		return cl, nil
    59  	}
    60  	if err != nil {
    61  		event.Error(ctx, "no completions found", err, tag.Position.Of(params.Position))
    62  	}
    63  	if candidates == nil {
    64  		return &protocol.CompletionList{
    65  			IsIncomplete: true,
    66  			Items:        []protocol.CompletionItem{},
    67  		}, nil
    68  	}
    69  
    70  	rng, err := surrounding.Range()
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	// When using deep completions/fuzzy matching, report results as incomplete so
    76  	// client fetches updated completions after every key stroke.
    77  	options := snapshot.Options()
    78  	incompleteResults := options.DeepCompletion || options.Matcher == settings.Fuzzy
    79  
    80  	items := toProtocolCompletionItems(candidates, rng, options)
    81  
    82  	return &protocol.CompletionList{
    83  		IsIncomplete: incompleteResults,
    84  		Items:        items,
    85  	}, nil
    86  }
    87  
    88  func toProtocolCompletionItems(candidates []completion.CompletionItem, rng protocol.Range, options *settings.Options) []protocol.CompletionItem {
    89  	var (
    90  		items                  = make([]protocol.CompletionItem, 0, len(candidates))
    91  		numDeepCompletionsSeen int
    92  	)
    93  	for i, candidate := range candidates {
    94  		// Limit the number of deep completions to not overwhelm the user in cases
    95  		// with dozens of deep completion matches.
    96  		if candidate.Depth > 0 {
    97  			if !options.DeepCompletion {
    98  				continue
    99  			}
   100  			if numDeepCompletionsSeen >= completion.MaxDeepCompletions {
   101  				continue
   102  			}
   103  			numDeepCompletionsSeen++
   104  		}
   105  		insertText := candidate.InsertText
   106  		if options.InsertTextFormat == protocol.SnippetTextFormat {
   107  			insertText = candidate.Snippet()
   108  		}
   109  
   110  		// This can happen if the client has snippets disabled but the
   111  		// candidate only supports snippet insertion.
   112  		if insertText == "" {
   113  			continue
   114  		}
   115  
   116  		doc := &protocol.Or_CompletionItem_documentation{
   117  			Value: protocol.MarkupContent{
   118  				Kind:  protocol.Markdown,
   119  				Value: golang.CommentToMarkdown(candidate.Documentation, options),
   120  			},
   121  		}
   122  		if options.PreferredContentFormat != protocol.Markdown {
   123  			doc.Value = candidate.Documentation
   124  		}
   125  		item := protocol.CompletionItem{
   126  			Label:  candidate.Label,
   127  			Detail: candidate.Detail,
   128  			Kind:   candidate.Kind,
   129  			TextEdit: &protocol.TextEdit{
   130  				NewText: insertText,
   131  				Range:   rng,
   132  			},
   133  			InsertTextFormat:    &options.InsertTextFormat,
   134  			AdditionalTextEdits: candidate.AdditionalTextEdits,
   135  			// This is a hack so that the client sorts completion results in the order
   136  			// according to their score. This can be removed upon the resolution of
   137  			// https://github.com/Microsoft/language-server-protocol/issues/348.
   138  			SortText: fmt.Sprintf("%05d", i),
   139  
   140  			// Trim operators (VSCode doesn't like weird characters in
   141  			// filterText).
   142  			FilterText: strings.TrimLeft(candidate.InsertText, "&*"),
   143  
   144  			Preselect:     i == 0,
   145  			Documentation: doc,
   146  			Tags:          protocol.NonNilSlice(candidate.Tags),
   147  			Deprecated:    candidate.Deprecated,
   148  		}
   149  		items = append(items, item)
   150  	}
   151  	return items
   152  }