Проблема
Автодополнение (textDocument/completion) матчит имена кандидатов по префиксу — CompletionProvider.matches():
private static boolean matches(String name, String lowerPrefix) {
if (lowerPrefix.isEmpty()) return true;
return name.toLowerCase(Locale.ROOT).startsWith(lowerPrefix);
}
и отдаёт список с isIncomplete=false. Подстрока внутри слова и подпоследовательность вразброс на сервере не находятся вообще, а клиент (VS Code) кэширует «полный» (isIncomplete=false) набор первого префикса и дальше фильтрует его своим fuzzy. Из-за рассинхрона возникает непоследовательное поведение.
Репро (OneScript, глобальные функции из builtin-oscript-globals.json)
Набираем Сцена + Ctrl+Space: СтартовыйСценарий() показывается, ТекущийСценарий() — нет. При этом ТекущийСценарий существует и находится по префиксу Текущ.
LSP-probe (что реально возвращает сервер):
| префикс |
total |
isIncomplete |
СтартовыйСценарий |
ТекущийСценарий |
С |
30 |
false |
✅ |
❌ |
Сцена |
0 |
false |
❌ |
❌ |
Сценарий |
0 |
false |
❌ |
❌ |
Текущ |
6 |
false |
❌ |
✅ |
Причина: на С сервер вернул набор, начинающихся на «С» (СтартовыйСценарий внутри, ТекущийСценарий — нет, он на «Т»), isIncomplete=false → клиент кэширует и фильтрует только этот набор. Сцена/Сценарий напрямую = 0 (середину слова startsWith не матчит). Единственная причина, по которой СтартовыйСценарий вообще виден — клиентский кэш «С*»-набора.
Чего хотим
Fuzzy-матчинг в completion как уже сделано в workspace/symbols (SymbolProvider → WorkspaceSymbolIndex):
- непрерывная подстрока внутри слова;
- разбросанная подпоследовательность;
- скоринг совпадения →
sortText (точные/префиксные выше подстрочных/подпоследовательных).
Тогда Сцена находит и СтартовыйСценарий, и ТекущийСценарий.
Заметка по реализации
В workspace/symbols ради сублинейного префиксного поиска по всему воркспейсу используется PatriciaTrie (WorkspaceSymbolIndex), а «грязный» fuzzy вынесен в searchFuzzyTail (линейный скан со скорингом).
Для completion trie, скорее всего, не нужен: наборы кандидатов малы и ограничены (глобалы ~сотни, классы, кейворды, символы текущего документа), поэтому достаточно линейного fuzzy-скана. Имеет смысл вынести fuzzy-матчер/скорер из WorkspaceSymbolIndex в переиспользуемый компонент и применить его в CompletionProvider.matches() (+ выставлять isIncomplete=true, чтобы клиент перезапрашивал по мере набора).
Затрагивает все ветки completion: глобальные функции/свойства, классы для Новый, составные имена MD-объектов, кейворды, локальные методы/переменные.
Выяснилось при работе над #3994 / #4135 (там matches=startsWith не менялся — поведение пре-существующее).
Проблема
Автодополнение (
textDocument/completion) матчит имена кандидатов по префиксу —CompletionProvider.matches():и отдаёт список с
isIncomplete=false. Подстрока внутри слова и подпоследовательность вразброс на сервере не находятся вообще, а клиент (VS Code) кэширует «полный» (isIncomplete=false) набор первого префикса и дальше фильтрует его своим fuzzy. Из-за рассинхрона возникает непоследовательное поведение.Репро (OneScript, глобальные функции из
builtin-oscript-globals.json)Набираем
Сцена+ Ctrl+Space:СтартовыйСценарий()показывается,ТекущийСценарий()— нет. При этомТекущийСценарийсуществует и находится по префиксуТекущ.LSP-probe (что реально возвращает сервер):
ССценаСценарийТекущПричина: на
Ссервер вернул набор, начинающихся на «С» (СтартовыйСценарийвнутри,ТекущийСценарий— нет, он на «Т»),isIncomplete=false→ клиент кэширует и фильтрует только этот набор.Сцена/Сценарийнапрямую = 0 (середину словаstartsWithне матчит). Единственная причина, по которойСтартовыйСценарийвообще виден — клиентский кэш «С*»-набора.Чего хотим
Fuzzy-матчинг в completion как уже сделано в
workspace/symbols(SymbolProvider→WorkspaceSymbolIndex):sortText(точные/префиксные выше подстрочных/подпоследовательных).Тогда
Сценанаходит иСтартовыйСценарий, иТекущийСценарий.Заметка по реализации
В
workspace/symbolsради сублинейного префиксного поиска по всему воркспейсу используетсяPatriciaTrie(WorkspaceSymbolIndex), а «грязный» fuzzy вынесен вsearchFuzzyTail(линейный скан со скорингом).Для completion trie, скорее всего, не нужен: наборы кандидатов малы и ограничены (глобалы ~сотни, классы, кейворды, символы текущего документа), поэтому достаточно линейного fuzzy-скана. Имеет смысл вынести fuzzy-матчер/скорер из
WorkspaceSymbolIndexв переиспользуемый компонент и применить его вCompletionProvider.matches()(+ выставлятьisIncomplete=true, чтобы клиент перезапрашивал по мере набора).Затрагивает все ветки completion: глобальные функции/свойства, классы для
Новый, составные имена MD-объектов, кейворды, локальные методы/переменные.Выяснилось при работе над #3994 / #4135 (там
matches=startsWithне менялся — поведение пре-существующее).