Skip to content

Commit 98d2daf

Browse files
authored
Don't use locale-aware sorting in completions (#1668)
1 parent 1e359e7 commit 98d2daf

File tree

3 files changed

+32
-61
lines changed

3 files changed

+32
-61
lines changed

internal/fourslash/tests/util/util.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import (
88
"github.com/microsoft/typescript-go/internal/fourslash"
99
"github.com/microsoft/typescript-go/internal/ls"
1010
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
11-
"golang.org/x/text/collate"
12-
"golang.org/x/text/language"
11+
"github.com/microsoft/typescript-go/internal/stringutil"
1312
)
1413

1514
func PtrTo[T any](v T) *T {
@@ -13265,10 +13264,8 @@ var CompletionGlobals = sortCompletionItems(append(
1326513264
CompletionUndefinedVarItem,
1326613265
))
1326713266

13268-
var defaultLanguage = language.AmericanEnglish
13269-
1327013267
func sortCompletionItems(items []fourslash.CompletionsExpectedItem) []fourslash.CompletionsExpectedItem {
13271-
compareStringsUI := collate.New(defaultLanguage).CompareString
13268+
compareStrings := stringutil.CompareStringsCaseInsensitiveThenSensitive
1327213269
items = slices.Clone(items)
1327313270
slices.SortStableFunc(items, func(a fourslash.CompletionsExpectedItem, b fourslash.CompletionsExpectedItem) int {
1327413271
defaultSortText := string(ls.SortTextLocationPriority)
@@ -13287,7 +13284,7 @@ func sortCompletionItems(items []fourslash.CompletionsExpectedItem) []fourslash.
1328713284
}
1328813285
aSortText = core.OrElse(aSortText, defaultSortText)
1328913286
bSortText = core.OrElse(bSortText, defaultSortText)
13290-
bySortText := compareStringsUI(aSortText, bSortText)
13287+
bySortText := compareStrings(aSortText, bSortText)
1329113288
if bySortText != 0 {
1329213289
return bySortText
1329313290
}
@@ -13308,7 +13305,7 @@ func sortCompletionItems(items []fourslash.CompletionsExpectedItem) []fourslash.
1330813305
default:
1330913306
panic(fmt.Sprintf("unexpected completion item type: %T", b))
1331013307
}
13311-
return compareStringsUI(aLabel, bLabel)
13308+
return compareStrings(aLabel, bLabel)
1331213309
})
1331313310
return items
1331413311
}

internal/ls/completions.go

Lines changed: 20 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ import (
2929
"github.com/microsoft/typescript-go/internal/scanner"
3030
"github.com/microsoft/typescript-go/internal/stringutil"
3131
"github.com/microsoft/typescript-go/internal/tspath"
32-
"golang.org/x/text/collate"
33-
"golang.org/x/text/language"
3432
)
3533

3634
func (l *LanguageService) ProvideCompletion(
@@ -1884,7 +1882,6 @@ func (l *LanguageService) completionInfoFromData(
18841882
clientOptions,
18851883
)
18861884

1887-
compareCompletionEntries := getCompareCompletionEntries(ctx)
18881885
if data.keywordFilters != KeywordCompletionFiltersNone {
18891886
keywordCompletions := getKeywordCompletions(data.keywordFilters, !data.insideJSDocTagTypeExpression && ast.IsSourceFileJS(file))
18901887
for _, keywordEntry := range keywordCompletions {
@@ -1958,7 +1955,6 @@ func (l *LanguageService) getCompletionEntriesFromSymbols(
19581955
// true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports.
19591956
// So adding a completion for a local will prevent us from adding completions for external module exports sharing the same name.
19601957
uniques := make(uniqueNamesMap)
1961-
compareCompletionEntries := getCompareCompletionEntries(ctx)
19621958
for _, symbol := range data.symbols {
19631959
symbolId := ast.GetSymbolId(symbol)
19641960
origin := data.symbolToOriginInfoMap[symbolId]
@@ -3295,59 +3291,35 @@ func getCompletionsSymbolKind(kind ScriptElementKind) lsproto.CompletionItemKind
32953291
}
32963292
}
32973293

3298-
var collatorCache collections.SyncMap[language.Tag, *sync.Pool]
3299-
3300-
func getCollator(tag language.Tag) *collate.Collator {
3301-
pool, ok := collatorCache.Load(tag)
3302-
if !ok {
3303-
pool, _ = collatorCache.LoadOrStore(tag, &sync.Pool{
3304-
New: func() any {
3305-
return collate.New(tag)
3306-
},
3307-
})
3308-
}
3309-
return pool.Get().(*collate.Collator)
3310-
}
3311-
3312-
func putCollator(tag language.Tag, collator *collate.Collator) {
3313-
pool, _ := collatorCache.Load(tag)
3314-
pool.Put(collator)
3315-
}
3316-
33173294
// Editors will use the `sortText` and then fall back to `name` for sorting, but leave ties in response order.
33183295
// So, it's important that we sort those ties in the order we want them displayed if it matters. We don't
33193296
// strictly need to sort by name or SortText here since clients are going to do it anyway, but we have to
33203297
// do the work of comparing them so we can sort those ties appropriately; plus, it makes the order returned
33213298
// by the language service consistent with what TS Server does and what editors typically do. This also makes
33223299
// completions tests make more sense. We used to sort only alphabetically and only in the server layer, but
33233300
// this made tests really weird, since most fourslash tests don't use the server.
3324-
func getCompareCompletionEntries(ctx context.Context) func(entryInSlice *lsproto.CompletionItem, entryToInsert *lsproto.CompletionItem) int {
3325-
return func(entryInSlice *lsproto.CompletionItem, entryToInsert *lsproto.CompletionItem) int {
3326-
locale := core.GetLocale(ctx)
3327-
collator := getCollator(locale)
3328-
defer putCollator(locale, collator)
3329-
compareStrings := collator.CompareString
3330-
result := compareStrings(*entryInSlice.SortText, *entryToInsert.SortText)
3331-
if result == stringutil.ComparisonEqual {
3332-
result = compareStrings(entryInSlice.Label, entryToInsert.Label)
3333-
}
3334-
if result == stringutil.ComparisonEqual && entryInSlice.Data != nil && entryToInsert.Data != nil {
3335-
sliceEntryData, ok1 := (*entryInSlice.Data).(*completionEntryData)
3336-
insertEntryData, ok2 := (*entryToInsert.Data).(*completionEntryData)
3337-
if ok1 && ok2 && sliceEntryData.ModuleSpecifier != "" && insertEntryData.ModuleSpecifier != "" {
3338-
// Sort same-named auto-imports by module specifier
3339-
result = compareNumberOfDirectorySeparators(
3340-
sliceEntryData.ModuleSpecifier,
3341-
insertEntryData.ModuleSpecifier,
3342-
)
3343-
}
3344-
}
3345-
if result == stringutil.ComparisonEqual {
3346-
// Fall back to symbol order - if we return `EqualTo`, `insertSorted` will put later symbols first.
3347-
return stringutil.ComparisonLessThan
3301+
func compareCompletionEntries(entryInSlice *lsproto.CompletionItem, entryToInsert *lsproto.CompletionItem) int {
3302+
compareStrings := stringutil.CompareStringsCaseInsensitiveThenSensitive
3303+
result := compareStrings(*entryInSlice.SortText, *entryToInsert.SortText)
3304+
if result == stringutil.ComparisonEqual {
3305+
result = compareStrings(entryInSlice.Label, entryToInsert.Label)
3306+
}
3307+
if result == stringutil.ComparisonEqual && entryInSlice.Data != nil && entryToInsert.Data != nil {
3308+
sliceEntryData, ok1 := (*entryInSlice.Data).(*completionEntryData)
3309+
insertEntryData, ok2 := (*entryToInsert.Data).(*completionEntryData)
3310+
if ok1 && ok2 && sliceEntryData.ModuleSpecifier != "" && insertEntryData.ModuleSpecifier != "" {
3311+
// Sort same-named auto-imports by module specifier
3312+
result = compareNumberOfDirectorySeparators(
3313+
sliceEntryData.ModuleSpecifier,
3314+
insertEntryData.ModuleSpecifier,
3315+
)
33483316
}
3349-
return result
33503317
}
3318+
if result == stringutil.ComparisonEqual {
3319+
// Fall back to symbol order - if we return `EqualTo`, `insertSorted` will put later symbols first.
3320+
return stringutil.ComparisonLessThan
3321+
}
3322+
return result
33513323
}
33523324

33533325
// True if the first character of `lowercaseCharacters` is the first character
@@ -3582,7 +3554,6 @@ func (l *LanguageService) getJSCompletionEntries(
35823554
uniqueNames *collections.Set[string],
35833555
sortedEntries []*lsproto.CompletionItem,
35843556
) []*lsproto.CompletionItem {
3585-
compareCompletionEntries := getCompareCompletionEntries(ctx)
35863557
nameTable := getNameTable(file)
35873558
for name, pos := range nameTable {
35883559
// Skip identifiers produced only from the current location
@@ -4929,11 +4900,6 @@ func hasCompletionItem(clientOptions *lsproto.CompletionClientCapabilities) bool
49294900
return clientOptions != nil && clientOptions.CompletionItem != nil
49304901
}
49314902

4932-
// strada TODO: this function is, at best, poorly named. Use sites are pretty suspicious.
4933-
func compilerOptionsIndicateEsModules(options *core.CompilerOptions) bool {
4934-
return options.Module == core.ModuleKindNone || options.GetEmitScriptTarget() >= core.ScriptTargetES2015 || options.NoEmit.IsTrue()
4935-
}
4936-
49374903
func clientSupportsItemLabelDetails(clientOptions *lsproto.CompletionClientCapabilities) bool {
49384904
return hasCompletionItem(clientOptions) && ptrIsTrue(clientOptions.CompletionItem.LabelDetailsSupport)
49394905
}

internal/stringutil/compare.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,11 @@ func HasSuffix(s string, suffix string, caseSensitive bool) bool {
9090
}
9191
return strings.EqualFold(s[len(s)-len(suffix):], suffix)
9292
}
93+
94+
func CompareStringsCaseInsensitiveThenSensitive(a, b string) Comparison {
95+
cmp := CompareStringsCaseInsensitive(a, b)
96+
if cmp != ComparisonEqual {
97+
return cmp
98+
}
99+
return CompareStringsCaseSensitive(a, b)
100+
}

0 commit comments

Comments
 (0)