Skip to content
This repository was archived by the owner on Oct 13, 2021. It is now read-only.

Commit 3bc48e8

Browse files
committed
feat: multiple matching strategy
feat: multiple matching strategy
1 parent 567174c commit 3bc48e8

File tree

4 files changed

+165
-85
lines changed

4 files changed

+165
-85
lines changed

lua/completion/matching.lua

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
local vim = vim
2+
local M = {}
3+
4+
-- Levenshtein algorithm for fuzzy matching
5+
-- https://gist.github.com/james2doyle/e406180e143da3bdd102
6+
local function fuzzy_score(str1, str2)
7+
local len1 = #str1
8+
local len2 = #str2
9+
local matrix = {}
10+
local cost
11+
local min = math.min;
12+
13+
-- quick cut-offs to save time
14+
if (len1 == 0) then
15+
return len2
16+
elseif (len2 == 0) then
17+
return len1
18+
elseif (str1 == str2) then
19+
return 0
20+
end
21+
22+
-- initialise the base matrix values
23+
for i = 0, len1, 1 do
24+
matrix[i] = {}
25+
matrix[i][0] = i
26+
end
27+
for j = 0, len2, 1 do
28+
matrix[0][j] = j
29+
end
30+
31+
-- actual Levenshtein algorithm
32+
for i = 1, len1, 1 do
33+
for j = 1, len2, 1 do
34+
if (str1:byte(i) == str2:byte(j)) then
35+
cost = 0
36+
else
37+
cost=1
38+
end
39+
matrix[i][j] = min(matrix[i-1][j] + 2, matrix[i][j-1], matrix[i-1][j-1] + cost)
40+
end
41+
end
42+
43+
-- return the last value - this is the Levenshtein distance
44+
return matrix[len1][len2]
45+
end
46+
47+
local function fuzzy_match(prefix, word)
48+
local score = fuzzy_score(prefix, word)
49+
if score < 1 then
50+
return true, score
51+
else
52+
return false
53+
end
54+
end
55+
56+
57+
local function substring_match(prefix, word)
58+
if string.find(word, prefix) then
59+
return true
60+
else
61+
return false
62+
end
63+
end
64+
65+
local function exact_match(prefix, word)
66+
if vim.startswith(word, prefix) then
67+
return true
68+
else
69+
return false
70+
end
71+
end
72+
73+
M.matching_strategy = {
74+
fuzzy = fuzzy_match,
75+
substr = substring_match,
76+
exact = exact_match
77+
}
78+
79+
return M

lua/completion/util.lua

Lines changed: 12 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
-- utility function that are modified from neovim's source --
33
------------------------------------------------------------------------
44

5-
local protocol = require 'vim.lsp.protocol'
65
local vim = vim
76
local api = vim.api
87
local M = {}
@@ -15,15 +14,6 @@ end
1514
------------------------
1615
-- completion items --
1716
------------------------
18-
local function get_completion_word(item)
19-
if item.textEdit ~= nil and item.textEdit ~= vim.NIL
20-
and item.textEdit.newText ~= nil and (item.insertTextFormat ~= 2 or vim.fn.exists('g:loaded_vsnip_integ')) then
21-
return item.textEdit.newText
22-
elseif item.insertText ~= nil and item.insertText ~= vim.NIL then
23-
return item.insertText
24-
end
25-
return item.label
26-
end
2717

2818
function M.sort_completion_items(items)
2919
table.sort(items, function(a, b)
@@ -39,79 +29,18 @@ function M.sort_completion_items(items)
3929
end)
4030
end
4131

42-
function M.text_document_completion_list_to_complete_items(result, prefix, score_func)
43-
local items = vim.lsp.util.extract_completion_items(result)
44-
if vim.tbl_isempty(items) then
45-
return {}
46-
end
47-
48-
local customize_label = vim.g.completion_customize_lsp_label
49-
-- items = remove_unmatch_completion_items(items, prefix)
50-
-- sort_completion_items(items)
51-
52-
local matches = {}
53-
54-
for _, completion_item in ipairs(items) do
55-
-- skip snippets items if snippet source are enabled
56-
if vim.fn.exists('g:loaded_vsnip_integ') or
57-
protocol.CompletionItemKind[completion_item.kind] ~= 'Snippet' then
58-
local info = ' '
59-
local documentation = completion_item.documentation
60-
if documentation then
61-
if type(documentation) == 'string' and documentation ~= '' then
62-
info = documentation
63-
elseif type(documentation) == 'table' and type(documentation.value) == 'string' then
64-
info = documentation.value
65-
-- else
66-
-- TODO(ashkan) Validation handling here?
67-
end
68-
end
69-
70-
local word = get_completion_word(completion_item)
71-
local user_data = {
72-
lsp = {
73-
completion_item = completion_item
74-
}
75-
}
76-
local kind = protocol.CompletionItemKind[completion_item.kind]
77-
local priority = vim.g.completion_items_priority[kind] or 1
78-
if vim.g.completion_enable_fuzzy_match == 1 then
79-
local score = score_func(prefix, word)
80-
if score <= 1 then
81-
table.insert(matches, {
82-
word = word,
83-
abbr = completion_item.label,
84-
kind = customize_label[kind] or kind or '',
85-
menu = completion_item.detail or '',
86-
info = info,
87-
priority = priority,
88-
score = score,
89-
icase = 1,
90-
user_data = user_data,
91-
dup = 1,
92-
empty = 1,
93-
})
94-
end
95-
else
96-
if vim.startswith(word, prefix) then
97-
table.insert(matches, {
98-
word = word,
99-
abbr = completion_item.label,
100-
kind = customize_label[kind] or kind or '',
101-
menu = completion_item.detail or '',
102-
info = info,
103-
priority = priority,
104-
icase = 1,
105-
user_data = user_data,
106-
dup = 1,
107-
empty = 1,
108-
})
109-
end
110-
end
111-
end
112-
end
113-
114-
return matches
32+
function M.addCompletionItems(item_table, item)
33+
table.insert(item_table, {
34+
word = item.word,
35+
abbr = item.abbr,
36+
kind = item.kind,
37+
menu = item.menu,
38+
info = item.info,
39+
icase = 1,
40+
dup = 1,
41+
empty = 1,
42+
user_data = item.user_data,
43+
})
11544
end
11645

11746
-- Levenshtein algorithm for fuzzy matching
@@ -157,7 +86,6 @@ function M.fuzzy_score(str1, str2)
15786
return matrix[len1][len2]
15887
end
15988

160-
16189
-- Check trigger character
16290
M.checkTriggerCharacter = function(line_to_cursor, trigger_character)
16391
if trigger_character == nil then return end

lua/source/lsp.lua

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
local vim = vim
2+
local protocol = require 'vim.lsp.protocol'
23
local api = vim.api
34
local util = require 'completion.util'
5+
local match = require 'completion.matching'
46
local M = {}
57

68
M.callback = false
@@ -9,6 +11,73 @@ M.getCompletionItems = function(_, _)
911
return M.items
1012
end
1113

14+
local function get_completion_word(item)
15+
if item.textEdit ~= nil and item.textEdit ~= vim.NIL
16+
and item.textEdit.newText ~= nil and (item.insertTextFormat ~= 2 or vim.fn.exists('g:loaded_vsnip_integ')) then
17+
return item.textEdit.newText
18+
elseif item.insertText ~= nil and item.insertText ~= vim.NIL then
19+
return item.insertText
20+
end
21+
return item.label
22+
end
23+
24+
local function text_document_completion_list_to_complete_items(result, prefix)
25+
local items = vim.lsp.util.extract_completion_items(result)
26+
if vim.tbl_isempty(items) then
27+
return {}
28+
end
29+
30+
local customize_label = vim.g.completion_customize_lsp_label
31+
-- items = remove_unmatch_completion_items(items, prefix)
32+
-- sort_completion_items(items)
33+
34+
local matches = {}
35+
36+
for _, completion_item in ipairs(items) do
37+
local item = {}
38+
-- skip snippets items if snippet parsing source in unavailable
39+
if vim.fn.exists('g:loaded_vsnip_integ') or
40+
protocol.CompletionItemKind[completion_item.kind] ~= 'Snippet' then
41+
local info = ' '
42+
local documentation = completion_item.documentation
43+
if documentation then
44+
if type(documentation) == 'string' and documentation ~= '' then
45+
info = documentation
46+
elseif type(documentation) == 'table' and type(documentation.value) == 'string' then
47+
info = documentation.value
48+
-- else
49+
-- TODO(ashkan) Validation handling here?
50+
end
51+
end
52+
item.info = info
53+
54+
item.word = get_completion_word(completion_item)
55+
item.user_data = {
56+
lsp = {
57+
completion_item = completion_item
58+
}
59+
}
60+
local kind = protocol.CompletionItemKind[completion_item.kind]
61+
item.kind = customize_label[kind] or kind or ''
62+
item.abbr = completion_item.label
63+
item.priority = vim.g.completion_items_priority[item.kind] or 1
64+
item.menu = completion_item.detail or ''
65+
local matching_piroity = 1
66+
for _, method in ipairs(vim.g.completion_matching_strategy_list) do
67+
local is_match, score = match.matching_strategy[method](prefix, item.word)
68+
item.score = score
69+
if is_match then
70+
item.user_data.matching_piroity = matching_piroity
71+
util.addCompletionItems(matches, item)
72+
break
73+
end
74+
end
75+
end
76+
end
77+
78+
return matches
79+
end
80+
1281
M.getCallback = function()
1382
return M.callback
1483
end
@@ -27,7 +96,7 @@ M.triggerFunction = function(prefix, _, bufnr, _)
2796
return
2897
end
2998
if api.nvim_get_mode()['mode'] == 'i' or api.nvim_get_mode()['mode'] == 'ic' then
30-
local matches = util.text_document_completion_list_to_complete_items(result, prefix, util.fuzzy_score)
99+
local matches = text_document_completion_list_to_complete_items(result, prefix, util.fuzzy_score)
31100
M.items = matches
32101
end
33102
M.callback = true

plugin/completion.vim

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ if ! exists('g:completion_fuzzy_match')
7777
let g:completion_enable_fuzzy_match = 0
7878
endif
7979

80+
if ! exists('g:completion_matching_strategy_list')
81+
let g:completion_matching_strategy_list = ['exact']
82+
endif
83+
8084
if ! exists('g:completion_chain_complete_list')
8185
let g:completion_chain_complete_list = {
8286
\ 'default' : {

0 commit comments

Comments
 (0)