From 3cc2e99712e59c090f955813402f6ff0a0f19833 Mon Sep 17 00:00:00 2001 From: Andreas Louv Date: Thu, 23 Mar 2023 07:28:00 +0100 Subject: [PATCH] Add "completionMatcher" which can be set to "icase", "case" or "fuzzy" This only works for language servers which always sends the whole set of completion items to the client. --- autoload/lsp/completion.vim | 55 +++++++++++++++++++++++++++++-------- autoload/lsp/options.vim | 5 +++- doc/lsp.txt | 11 ++++++++ 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/autoload/lsp/completion.vim b/autoload/lsp/completion.vim index c0a2197..51c35ea 100644 --- a/autoload/lsp/completion.vim +++ b/autoload/lsp/completion.vim @@ -91,7 +91,11 @@ export def CompletionReply(lspserver: dict, cItems: any) # Get the keyword prefix before the current cursor column. var chcol = charcol('.') var starttext = chcol == 1 ? '' : getline('.')[ : chcol - 2] - var [prefix, start_idx, end_idx] = starttext->tolower()->matchstrpos('\k*$') + var [prefix, start_idx, end_idx] = starttext->matchstrpos('\k*$') + if opt.lspOptions.completionMatcher == 'icase' + prefix = prefix->tolower() + endif + var start_col = start_idx + 1 var completeItems: list> = [] @@ -100,7 +104,7 @@ export def CompletionReply(lspserver: dict, cItems: any) # TODO: Add proper support for item.textEdit.newText and item.textEdit.range # Keep in mind that item.textEdit.range can start be way before the typed keyword. - if item->has_key('textEdit') + if item->has_key('textEdit') && opt.lspOptions.completionMatcher != 'fuzzy' var start_charcol: number if prefix != '' start_charcol = charidx(starttext, start_idx) + 1 @@ -126,18 +130,39 @@ export def CompletionReply(lspserver: dict, cItems: any) # Remove all the snippet placeholders d.word = MakeValidWord(d.word) elseif !lspserver.completeItemsIsIncomplete - # plain text completion. If the completion item text doesn't start with - # the current (case ignored) keyword prefix, skip it. # Don't attempt to filter on the items, when "isIncomplete" is set - if prefix != '' && d.word->tolower()->stridx(prefix) != 0 - continue + + # plain text completion + if prefix != '' + # If the completion item text doesn't start with the current (case + # ignored) keyword prefix, skip it. + if opt.lspOptions.completionMatcher == 'icase' + if d.word->tolower()->stridx(prefix) != 0 + continue + endif + # If the completion item text doesn't fuzzy match with the current + # keyword prefix, skip it. + elseif opt.lspOptions.completionMatcher == 'fuzzy' + if matchfuzzy([d.word], prefix)->empty() + continue + endif + # If the completion item text doesn't start with the current keyword + # prefix, skip it. + else + if d.word->stridx(prefix) != 0 + continue + endif + endif endif endif d.abbr = item.label - d.icase = 1 d.dup = 1 + if opt.lspOptions.completionMatcher == 'icase' + d.icase = 1 + endif + if item->has_key('kind') # namespace CompletionItemKind # map LSP kind to complete-item-kind @@ -307,14 +332,22 @@ def g:LspOmniFunc(findstart: number, base: string): any var res: list> = lspserver.completeItems + var prefix = lspserver.omniCompleteKeyword + # Don't attempt to filter on the items, when "isIncomplete" is set - if lspserver.completeItemsIsIncomplete + if prefix == '' || lspserver.completeItemsIsIncomplete return res->empty() ? v:none : res endif - var prefix: string = lspserver.omniCompleteKeyword->tolower() - # To filter (case ignored) keyword prefixed compl items only. - return res->empty() ? v:none : res->filter((i, v) => v.word->tolower()->stridx(prefix) == 0) + if opt.lspOptions.completionMatcher == 'fuzzy' + return res->empty() ? v:none : res->matchfuzzy(prefix, { key: 'word' }) + endif + + if opt.lspOptions.completionMatcher == 'icase' + return res->empty() ? v:none : res->filter((i, v) => v.word->tolower()->stridx(prefix) == 0) + endif + + return res->empty() ? v:none : res->filter((i, v) => v.word->stridx(prefix) == 0) endif enddef diff --git a/autoload/lsp/options.vim b/autoload/lsp/options.vim index c8dc2fb..dea03a8 100644 --- a/autoload/lsp/options.vim +++ b/autoload/lsp/options.vim @@ -45,7 +45,10 @@ export var lspOptions: dict = { # enable inlay hints showInlayHints: false, # hide disabled code actions - hideDisabledCodeActions: false + hideDisabledCodeActions: false, + # icase | fuzzy | case match for language servers that replies with a full + # list of completion items + completionMatcher: 'case', } # set the LSP plugin options from the user provided option values diff --git a/doc/lsp.txt b/doc/lsp.txt index 039eeee..01c183a 100644 --- a/doc/lsp.txt +++ b/doc/lsp.txt @@ -282,6 +282,17 @@ diagLineHL Highlight used for diagnostic line. echoSignature In insert mode, echo the current symbol signature instead of showing it in a popup. By default this is set to false. +completionMatcher Enable fuzzy or incase sensitive completion for language + servers that replies with a full list of completion + items. Some language servers does completion filtering + in the server, while other relies on the client to do + the filtering. + + This option only works for language servers that expect + the client to filter the completion items. + + Can be either `fuzzy`, `icase` or `case`, default is + `case`. hideDisabledCodeActions Hide all disabled code actions ignoreMissingServer Do not print a missing language server executable. By default this is set to false. -- 2.48.1