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 <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/cmd/test/cmdtest.go b/internal/lsp/cmd/test/cmdtest.go
index d31dfe4..c04bb91 100644
--- a/internal/lsp/cmd/test/cmdtest.go
+++ b/internal/lsp/cmd/test/cmdtest.go
@@ -53,6 +53,10 @@
//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 be42932..d786218 100644
--- a/internal/lsp/completion_test.go
+++ b/internal/lsp/completion_test.go
@@ -98,6 +98,21 @@
}
}
+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 8fa06c3..12d036e 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -104,11 +104,21 @@
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 @@
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 7d35fa6..d3c95cd 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -76,6 +76,7 @@
type CompletionOptions struct {
Deep bool
FuzzyMatching bool
+ CaseSensitive bool
Unimported bool
Documentation bool
FullDocumentation bool
@@ -200,6 +201,8 @@
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 a91a28f..d4058e9 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -210,6 +210,24 @@
}
}
+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 0000000..6f49d36
--- /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 6e647ab..aa08245 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -29,26 +29,27 @@
// 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 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 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 @@
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 @@
// 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 @@
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 @@
// 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 @@
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 @@
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)