Skip to content

feat(completion): fuzzy-поиск в автодополнении (подстрока + подпоследовательность), как в workspace/symbols #4142

Description

@nixel2007

Проблема

Автодополнение (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 (SymbolProviderWorkspaceSymbolIndex):

  • непрерывная подстрока внутри слова;
  • разбросанная подпоследовательность;
  • скоринг совпадения → sortText (точные/префиксные выше подстрочных/подпоследовательных).

Тогда Сцена находит и СтартовыйСценарий, и ТекущийСценарий.

Заметка по реализации

В workspace/symbols ради сублинейного префиксного поиска по всему воркспейсу используется PatriciaTrie (WorkspaceSymbolIndex), а «грязный» fuzzy вынесен в searchFuzzyTail (линейный скан со скорингом).

Для completion trie, скорее всего, не нужен: наборы кандидатов малы и ограничены (глобалы ~сотни, классы, кейворды, символы текущего документа), поэтому достаточно линейного fuzzy-скана. Имеет смысл вынести fuzzy-матчер/скорер из WorkspaceSymbolIndex в переиспользуемый компонент и применить его в CompletionProvider.matches() (+ выставлять isIncomplete=true, чтобы клиент перезапрашивал по мере набора).

Затрагивает все ветки completion: глобальные функции/свойства, классы для Новый, составные имена MD-объектов, кейворды, локальные методы/переменные.

Выяснилось при работе над #3994 / #4135 (там matches=startsWith не менялся — поведение пре-существующее).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions