From a8d5d34286bd3ab7c348812aa4699f674c05245b Mon Sep 17 00:00:00 2001 From: Paul Jolly Date: Thu, 26 Sep 2019 12:59:06 +0100 Subject: [PATCH] internal/lsp: provide option for case sensitive completion In CL 192137 deep fuzzy matching was enabled by default. We also have options independent options "deepCompletion" and "fuzzyMatching" to control this. When fuzzy matching is disabled, case insensitive prefix matching is used. Provide an option, "caseSensitiveCompletion", which allows for case sensitive prefix matching when fuzzy matching is disabled. Change-Id: I17c8fa310b2ef79e36cc2f7303e98870690b5903 Reviewed-on: https://go-review.googlesource.com/c/tools/+/194757 Run-TryBot: Rebecca Stambler TryBot-Result: Gobot Gobot Reviewed-by: Rebecca Stambler --- internal/lsp/cmd/test/cmdtest.go | 4 + internal/lsp/completion_test.go | 15 ++ internal/lsp/source/completion.go | 18 +- internal/lsp/source/options.go | 3 + internal/lsp/source/source_test.go | 18 ++ .../testdata/casesensitive/casesensitive.go | 16 ++ internal/lsp/tests/tests.go | 185 ++++++++++-------- 7 files changed, 174 insertions(+), 85 deletions(-) create mode 100644 internal/lsp/testdata/casesensitive/casesensitive.go diff --git a/internal/lsp/cmd/test/cmdtest.go b/internal/lsp/cmd/test/cmdtest.go index d31dfe4f02..c04bb9138c 100644 --- a/internal/lsp/cmd/test/cmdtest.go +++ b/internal/lsp/cmd/test/cmdtest.go @@ -53,6 +53,10 @@ func (r *runner) FuzzyCompletions(t *testing.T, data tests.FuzzyCompletions, ite //TODO: add command line completions tests when it works } +func (r *runner) CaseSensitiveCompletions(t *testing.T, data tests.CaseSensitiveCompletions, items tests.CompletionItems) { + //TODO: add command line completions tests when it works +} + func (r *runner) RankCompletions(t *testing.T, data tests.RankCompletions, items tests.CompletionItems) { //TODO: add command line completions tests when it works } diff --git a/internal/lsp/completion_test.go b/internal/lsp/completion_test.go index be42932a4f..d7862184ed 100644 --- a/internal/lsp/completion_test.go +++ b/internal/lsp/completion_test.go @@ -98,6 +98,21 @@ func (r *runner) FuzzyCompletions(t *testing.T, data tests.FuzzyCompletions, ite } } +func (r *runner) CaseSensitiveCompletions(t *testing.T, data tests.CaseSensitiveCompletions, items tests.CompletionItems) { + for src, test := range data { + got := r.callCompletion(t, src, source.CompletionOptions{ + CaseSensitive: true, + }) + if !strings.Contains(string(src.URI()), "builtins") { + got = tests.FilterBuiltins(got) + } + want := expected(t, test, items) + if msg := tests.DiffCompletionItems(want, got); msg != "" { + t.Errorf("%s: %s", src, msg) + } + } +} + func (r *runner) RankCompletions(t *testing.T, data tests.RankCompletions, items tests.CompletionItems) { for src, test := range data { got := r.callCompletion(t, src, source.CompletionOptions{ diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go index 8fa06c3480..12d036e974 100644 --- a/internal/lsp/source/completion.go +++ b/internal/lsp/source/completion.go @@ -104,11 +104,21 @@ type matcher interface { Score(candidateLabel string) (score float32) } -// prefixMatcher implements case insensitive prefix matching. +// prefixMatcher implements case sensitive prefix matching. type prefixMatcher string func (pm prefixMatcher) Score(candidateLabel string) float32 { - if strings.HasPrefix(strings.ToLower(candidateLabel), string(pm)) { + if strings.HasPrefix(candidateLabel, string(pm)) { + return 1 + } + return -1 +} + +// insensitivePrefixMatcher implements case insensitive prefix matching. +type insensitivePrefixMatcher string + +func (ipm insensitivePrefixMatcher) Score(candidateLabel string) float32 { + if strings.HasPrefix(strings.ToLower(candidateLabel), string(ipm)) { return 1 } return -1 @@ -233,8 +243,10 @@ func (c *completer) setSurrounding(ident *ast.Ident) { if c.opts.FuzzyMatching { c.matcher = fuzzy.NewMatcher(c.surrounding.Prefix(), fuzzy.Symbol) + } else if c.opts.CaseSensitive { + c.matcher = prefixMatcher(c.surrounding.Prefix()) } else { - c.matcher = prefixMatcher(strings.ToLower(c.surrounding.Prefix())) + c.matcher = insensitivePrefixMatcher(strings.ToLower(c.surrounding.Prefix())) } } diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go index 7d35fa69d9..d3c95cd2b8 100644 --- a/internal/lsp/source/options.go +++ b/internal/lsp/source/options.go @@ -76,6 +76,7 @@ type Options struct { type CompletionOptions struct { Deep bool FuzzyMatching bool + CaseSensitive bool Unimported bool Documentation bool FullDocumentation bool @@ -200,6 +201,8 @@ func (o *Options) set(name string, value interface{}) OptionResult { result.setBool(&o.Completion.Deep) case "fuzzyMatching": result.setBool(&o.Completion.FuzzyMatching) + case "caseSensitiveCompletion": + result.setBool(&o.Completion.CaseSensitive) case "completeUnimported": result.setBool(&o.Completion.Unimported) diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index a91a28f23e..d4058e91af 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -210,6 +210,24 @@ func (r *runner) FuzzyCompletions(t *testing.T, data tests.FuzzyCompletions, ite } } +func (r *runner) CaseSensitiveCompletions(t *testing.T, data tests.CaseSensitiveCompletions, items tests.CompletionItems) { + for src, test := range data { + var want []protocol.CompletionItem + for _, pos := range test.CompletionItems { + want = append(want, tests.ToProtocolCompletionItem(*items[pos])) + } + _, list := r.callCompletion(t, src, source.CompletionOptions{ + CaseSensitive: true, + }) + if !strings.Contains(string(src.URI()), "builtins") { + list = tests.FilterBuiltins(list) + } + if diff := tests.DiffCompletionItems(want, list); diff != "" { + t.Errorf("%s: %s", src, diff) + } + } +} + func (r *runner) RankCompletions(t *testing.T, data tests.RankCompletions, items tests.CompletionItems) { for src, test := range data { var want []protocol.CompletionItem diff --git a/internal/lsp/testdata/casesensitive/casesensitive.go b/internal/lsp/testdata/casesensitive/casesensitive.go new file mode 100644 index 0000000000..6f49d36ffe --- /dev/null +++ b/internal/lsp/testdata/casesensitive/casesensitive.go @@ -0,0 +1,16 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package casesensitive + +func _() { + var lower int //@item(lower, "lower", "int", "var") + var Upper int //@item(upper, "Upper", "int", "var") + + l //@casesensitive(" //", lower) + U //@casesensitive(" //", upper) + + L //@casesensitive(" //") + u //@casesensitive(" //") +} diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index 6e647abb55..aa082458e2 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -29,26 +29,27 @@ import ( // We hardcode the expected number of test cases to ensure that all tests // are being executed. If a test is added, this number must be changed. const ( - ExpectedCompletionsCount = 169 - ExpectedCompletionSnippetCount = 36 - ExpectedUnimportedCompletionsCount = 1 - ExpectedDeepCompletionsCount = 5 - ExpectedFuzzyCompletionsCount = 6 - ExpectedRankedCompletionsCount = 1 - ExpectedDiagnosticsCount = 21 - ExpectedFormatCount = 6 - ExpectedImportCount = 2 - ExpectedSuggestedFixCount = 1 - ExpectedDefinitionsCount = 39 - ExpectedTypeDefinitionsCount = 2 - ExpectedFoldingRangesCount = 2 - ExpectedHighlightsCount = 2 - ExpectedReferencesCount = 6 - ExpectedRenamesCount = 20 - ExpectedPrepareRenamesCount = 8 - ExpectedSymbolsCount = 1 - ExpectedSignaturesCount = 21 - ExpectedLinksCount = 4 + ExpectedCompletionsCount = 169 + ExpectedCompletionSnippetCount = 36 + ExpectedUnimportedCompletionsCount = 1 + ExpectedDeepCompletionsCount = 5 + ExpectedFuzzyCompletionsCount = 6 + ExpectedCaseSensitiveCompletionsCount = 4 + ExpectedRankedCompletionsCount = 1 + ExpectedDiagnosticsCount = 21 + ExpectedFormatCount = 6 + ExpectedImportCount = 2 + ExpectedSuggestedFixCount = 1 + ExpectedDefinitionsCount = 39 + ExpectedTypeDefinitionsCount = 2 + ExpectedFoldingRangesCount = 2 + ExpectedHighlightsCount = 2 + ExpectedReferencesCount = 6 + ExpectedRenamesCount = 20 + ExpectedPrepareRenamesCount = 8 + ExpectedSymbolsCount = 1 + ExpectedSignaturesCount = 21 + ExpectedLinksCount = 4 ) const ( @@ -67,6 +68,7 @@ type CompletionSnippets map[span.Span]CompletionSnippet type UnimportedCompletions map[span.Span]Completion type DeepCompletions map[span.Span]Completion type FuzzyCompletions map[span.Span]Completion +type CaseSensitiveCompletions map[span.Span]Completion type RankCompletions map[span.Span]Completion type FoldingRanges []span.Span type Formats []span.Span @@ -83,29 +85,30 @@ type Signatures map[span.Span]*source.SignatureInformation type Links map[span.URI][]Link type Data struct { - Config packages.Config - Exported *packagestest.Exported - Diagnostics Diagnostics - CompletionItems CompletionItems - Completions Completions - CompletionSnippets CompletionSnippets - UnimportedCompletions UnimportedCompletions - DeepCompletions DeepCompletions - FuzzyCompletions FuzzyCompletions - RankCompletions RankCompletions - FoldingRanges FoldingRanges - Formats Formats - Imports Imports - SuggestedFixes SuggestedFixes - Definitions Definitions - Highlights Highlights - References References - Renames Renames - PrepareRenames PrepareRenames - Symbols Symbols - symbolsChildren SymbolsChildren - Signatures Signatures - Links Links + Config packages.Config + Exported *packagestest.Exported + Diagnostics Diagnostics + CompletionItems CompletionItems + Completions Completions + CompletionSnippets CompletionSnippets + UnimportedCompletions UnimportedCompletions + DeepCompletions DeepCompletions + FuzzyCompletions FuzzyCompletions + CaseSensitiveCompletions CaseSensitiveCompletions + RankCompletions RankCompletions + FoldingRanges FoldingRanges + Formats Formats + Imports Imports + SuggestedFixes SuggestedFixes + Definitions Definitions + Highlights Highlights + References References + Renames Renames + PrepareRenames PrepareRenames + Symbols Symbols + symbolsChildren SymbolsChildren + Signatures Signatures + Links Links t testing.TB fragments map[string]string @@ -123,6 +126,7 @@ type Tests interface { UnimportedCompletions(*testing.T, UnimportedCompletions, CompletionItems) DeepCompletions(*testing.T, DeepCompletions, CompletionItems) FuzzyCompletions(*testing.T, FuzzyCompletions, CompletionItems) + CaseSensitiveCompletions(*testing.T, CaseSensitiveCompletions, CompletionItems) RankCompletions(*testing.T, RankCompletions, CompletionItems) FoldingRange(*testing.T, FoldingRanges) Format(*testing.T, Formats) @@ -160,6 +164,9 @@ const ( // Fuzzy tests deep completion and fuzzy matching. CompletionFuzzy + // CaseSensitive tests case sensitive completion + CompletionCaseSensitve + // CompletionRank candidates in test must be valid and in the right relative order. CompletionRank ) @@ -209,23 +216,24 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data { t.Helper() data := &Data{ - Diagnostics: make(Diagnostics), - CompletionItems: make(CompletionItems), - Completions: make(Completions), - CompletionSnippets: make(CompletionSnippets), - UnimportedCompletions: make(UnimportedCompletions), - DeepCompletions: make(DeepCompletions), - FuzzyCompletions: make(FuzzyCompletions), - RankCompletions: make(RankCompletions), - Definitions: make(Definitions), - Highlights: make(Highlights), - References: make(References), - Renames: make(Renames), - PrepareRenames: make(PrepareRenames), - Symbols: make(Symbols), - symbolsChildren: make(SymbolsChildren), - Signatures: make(Signatures), - Links: make(Links), + Diagnostics: make(Diagnostics), + CompletionItems: make(CompletionItems), + Completions: make(Completions), + CompletionSnippets: make(CompletionSnippets), + UnimportedCompletions: make(UnimportedCompletions), + DeepCompletions: make(DeepCompletions), + FuzzyCompletions: make(FuzzyCompletions), + RankCompletions: make(RankCompletions), + CaseSensitiveCompletions: make(CaseSensitiveCompletions), + Definitions: make(Definitions), + Highlights: make(Highlights), + References: make(References), + Renames: make(Renames), + PrepareRenames: make(PrepareRenames), + Symbols: make(Symbols), + symbolsChildren: make(SymbolsChildren), + Signatures: make(Signatures), + Links: make(Links), t: t, dir: dir, @@ -295,28 +303,29 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data { // Collect any data that needs to be used by subsequent tests. if err := data.Exported.Expect(map[string]interface{}{ - "diag": data.collectDiagnostics, - "item": data.collectCompletionItems, - "complete": data.collectCompletions(CompletionDefault), - "unimported": data.collectCompletions(CompletionUnimported), - "deep": data.collectCompletions(CompletionDeep), - "fuzzy": data.collectCompletions(CompletionFuzzy), - "rank": data.collectCompletions(CompletionRank), - "snippet": data.collectCompletionSnippets, - "fold": data.collectFoldingRanges, - "format": data.collectFormats, - "import": data.collectImports, - "godef": data.collectDefinitions, - "typdef": data.collectTypeDefinitions, - "hover": data.collectHoverDefinitions, - "highlight": data.collectHighlights, - "refs": data.collectReferences, - "rename": data.collectRenames, - "prepare": data.collectPrepareRenames, - "symbol": data.collectSymbols, - "signature": data.collectSignatures, - "link": data.collectLinks, - "suggestedfix": data.collectSuggestedFixes, + "diag": data.collectDiagnostics, + "item": data.collectCompletionItems, + "complete": data.collectCompletions(CompletionDefault), + "unimported": data.collectCompletions(CompletionUnimported), + "deep": data.collectCompletions(CompletionDeep), + "fuzzy": data.collectCompletions(CompletionFuzzy), + "casesensitive": data.collectCompletions(CompletionCaseSensitve), + "rank": data.collectCompletions(CompletionRank), + "snippet": data.collectCompletionSnippets, + "fold": data.collectFoldingRanges, + "format": data.collectFormats, + "import": data.collectImports, + "godef": data.collectDefinitions, + "typdef": data.collectTypeDefinitions, + "hover": data.collectHoverDefinitions, + "highlight": data.collectHighlights, + "refs": data.collectReferences, + "rename": data.collectRenames, + "prepare": data.collectPrepareRenames, + "symbol": data.collectSymbols, + "signature": data.collectSignatures, + "link": data.collectLinks, + "suggestedfix": data.collectSuggestedFixes, }); err != nil { t.Fatal(err) } @@ -382,6 +391,14 @@ func Run(t *testing.T, tests Tests, data *Data) { tests.FuzzyCompletions(t, data.FuzzyCompletions, data.CompletionItems) }) + t.Run("CaseSensitiveCompletion", func(t *testing.T) { + t.Helper() + if len(data.CaseSensitiveCompletions) != ExpectedCaseSensitiveCompletionsCount { + t.Errorf("got %v case sensitive completions expected %v", len(data.CaseSensitiveCompletions), ExpectedCaseSensitiveCompletionsCount) + } + tests.CaseSensitiveCompletions(t, data.CaseSensitiveCompletions, data.CompletionItems) + }) + t.Run("RankCompletions", func(t *testing.T) { t.Helper() if len(data.RankCompletions) != ExpectedRankedCompletionsCount { @@ -638,6 +655,10 @@ func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []t return func(src span.Span, expected []token.Pos) { result(data.RankCompletions, src, expected) } + case CompletionCaseSensitve: + return func(src span.Span, expected []token.Pos) { + result(data.CaseSensitiveCompletions, src, expected) + } default: return func(src span.Span, expected []token.Pos) { result(data.Completions, src, expected)