]> Sergey Matveev's repositories - vim-lsp.git/commitdiff
Add "completionMatcher" which can be set to "icase", "case" or "fuzzy"
authorAndreas Louv <andreas@louv.dk>
Thu, 23 Mar 2023 06:28:00 +0000 (07:28 +0100)
committerAndreas Louv <andreas@louv.dk>
Thu, 23 Mar 2023 14:14:26 +0000 (15:14 +0100)
This only works for language servers which always sends the whole set of
completion items to the client.

autoload/lsp/completion.vim
autoload/lsp/options.vim
doc/lsp.txt

index c0a2197b5e9e8c6fedd3b5fad70b695d4a6923fd..51c35eaf7b85a2d75cd55d9ec694fc25a0a043b8 100644 (file)
@@ -91,7 +91,11 @@ export def CompletionReply(lspserver: dict<any>, 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<dict<any>> = []
@@ -100,7 +104,7 @@ export def CompletionReply(lspserver: dict<any>, 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<any>, 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<dict<any>> = 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
 
index c8dc2fbd5a696fe98822c1ccc1c2a229fc44291f..dea03a88106e30492ab828b082a8bcb2cad0a619 100644 (file)
@@ -45,7 +45,10 @@ export var lspOptions: dict<any> = {
   # 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
index 039eeee5d904e53183246c79366a53d0794a918e..01c183a3971b6fe1c7c2ae4a456358bcfd4ffd9c 100644 (file)
@@ -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.