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)