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  }