vim-patch:9.1.1049: insert-completed items are always sorted

Problem:  insert-completed items are always sorted, although the LSP
          spec[1] standard defines sortText in the returned
          completionitem list. This means that the server has sorted the
          results. When fuzzy is enabled, this will break the server's
          sorting results.
Solution: disable sorting of candidates when "nosort" is set in
          'completeopt'

[1]
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItem

closes: vim/vim#16501

f400a0cc41

Co-authored-by: glepnir <glephunter@gmail.com>
This commit is contained in:
zeertzjq
2025-01-24 06:44:23 +08:00
parent 851137f679
commit 63aa167f94
5 changed files with 40 additions and 9 deletions

View File

@ -1571,6 +1571,10 @@ A jump table for the options with a short description can be found at |Q_op|.
list of alternatives, but not how the candidates are
collected (using different completion types).
nosort Disable sorting of completion candidates based on fuzzy
scores when "fuzzy" is enabled. Candidates will appear
in their original order.
*'completeslash'* *'csl'*
'completeslash' 'csl' string (default "")
local to buffer

View File

@ -1098,6 +1098,10 @@ vim.go.cia = vim.go.completeitemalign
--- list of alternatives, but not how the candidates are
--- collected (using different completion types).
---
--- nosort Disable sorting of completion candidates based on fuzzy
--- scores when "fuzzy" is enabled. Candidates will appear
--- in their original order.
---
--- @type string
vim.o.completeopt = "menu,preview"
vim.o.cot = vim.o.completeopt

View File

@ -1212,7 +1212,8 @@ static int ins_compl_build_pum(void)
int max_fuzzy_score = 0;
unsigned cur_cot_flags = get_cot_flags();
bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0;
bool compl_fuzzy_match = (cur_cot_flags & kOptCotFlagFuzzy) != 0;
bool fuzzy_nosort = (cur_cot_flags & kOptCotFlagNosort) != 0;
bool fuzzy_filter = fuzzy_nosort || (cur_cot_flags & kOptCotFlagFuzzy) != 0;
compl_T *match_head = NULL, *match_tail = NULL;
// If the current match is the original text don't find the first
@ -1232,14 +1233,14 @@ static int ins_compl_build_pum(void)
comp->cp_in_match_array = false;
// When 'completeopt' contains "fuzzy" and leader is not NULL or empty,
// set the cp_score for later comparisons.
if (compl_fuzzy_match && compl_leader.data != NULL && compl_leader.size > 0) {
if (fuzzy_filter && compl_leader.data != NULL && compl_leader.size > 0) {
comp->cp_score = fuzzy_match_str(comp->cp_str.data, compl_leader.data);
}
if (!match_at_original_text(comp)
&& (compl_leader.data == NULL
|| ins_compl_equal(comp, compl_leader.data, compl_leader.size)
|| (compl_fuzzy_match && comp->cp_score > 0))) {
|| (fuzzy_filter && comp->cp_score > 0))) {
compl_match_arraysize++;
comp->cp_in_match_array = true;
if (match_head == NULL) {
@ -1248,7 +1249,7 @@ static int ins_compl_build_pum(void)
match_tail->cp_match_next = comp;
}
match_tail = comp;
if (!shown_match_ok && !compl_fuzzy_match) {
if (!shown_match_ok && !fuzzy_filter) {
if (comp == compl_shown_match || did_find_shown_match) {
// This item is the shown match or this is the
// first displayed item after the shown match.
@ -1261,18 +1262,20 @@ static int ins_compl_build_pum(void)
shown_compl = comp;
}
cur = i;
} else if (compl_fuzzy_match) {
} else if (fuzzy_filter) {
if (i == 0) {
shown_compl = comp;
}
// Update the maximum fuzzy score and the shown match
// if the current item's score is higher
if (comp->cp_score > max_fuzzy_score) {
if (!fuzzy_nosort && comp->cp_score > max_fuzzy_score) {
did_find_shown_match = true;
max_fuzzy_score = comp->cp_score;
if (!compl_no_select) {
compl_shown_match = comp;
}
} else if (fuzzy_nosort && i == 0 && !compl_no_select) {
compl_shown_match = shown_compl;
}
if (!shown_match_ok && comp == compl_shown_match && !compl_no_select) {
cur = i;
@ -1282,7 +1285,7 @@ static int ins_compl_build_pum(void)
i++;
}
if (comp == compl_shown_match && !compl_fuzzy_match) {
if (comp == compl_shown_match && !fuzzy_filter) {
did_find_shown_match = true;
// When the original text is the shown match don't set
// compl_shown_match.
@ -1323,7 +1326,7 @@ static int ins_compl_build_pum(void)
comp = match_next;
}
if (compl_fuzzy_match && compl_leader.data != NULL && compl_leader.size > 0) {
if (fuzzy_filter && !fuzzy_nosort && compl_leader.data != NULL && compl_leader.size > 0) {
for (i = 0; i < compl_match_arraysize; i++) {
compl_match_array[i].pum_idx = i;
}

View File

@ -1493,6 +1493,7 @@ local options = {
'noinsert',
'noselect',
'fuzzy',
'nosort',
},
flags = true,
deny_duplicates = true,
@ -1537,6 +1538,10 @@ local options = {
difference how completion candidates are reduced from the
list of alternatives, but not how the candidates are
collected (using different completion types).
nosort Disable sorting of completion candidates based on fuzzy
scores when "fuzzy" is enabled. Candidates will appear
in their original order.
]=],
full_name = 'completeopt',
list = 'onecomma',

View File

@ -2778,7 +2778,7 @@ func Test_complete_fuzzy_match()
if a:findstart
return col(".")
endif
return [#{word: "foo"}, #{word: "foobar"}, #{word: "fooBaz"}, #{word: "foobala"}]
return [#{word: "foo"}, #{word: "foobar"}, #{word: "fooBaz"}, #{word: "foobala"}, #{word: "你好吗"}, #{word: "我好"}]
endfunc
new
@ -2837,6 +2837,21 @@ func Test_complete_fuzzy_match()
call feedkeys("STe\<C-X>\<C-N>x\<CR>\<Esc>0", 'tx!')
call assert_equal('Tex', getline('.'))
" test case for nosort option
set cot=menuone,menu,noinsert,fuzzy,nosort
" fooBaz" should have a higher score when the leader is "fb".
" With `nosort`, "foobar" should still be shown first in the popup menu.
call feedkeys("S\<C-x>\<C-o>fb", 'tx')
call assert_equal('foobar', g:word)
call feedkeys("S\<C-x>\<C-o>好", 'tx')
call assert_equal("你好吗", g:word)
set cot+=noselect
call feedkeys("S\<C-x>\<C-o>好", 'tx')
call assert_equal(v:null, g:word)
call feedkeys("S\<C-x>\<C-o>好\<C-N>", 'tx')
call assert_equal('你好吗', g:word)
" clean up
set omnifunc=
bw!