aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorglepnir <glephunter@gmail.com>2025-01-05 21:52:50 +0800
committerGitHub <noreply@github.com>2025-01-05 05:52:50 -0800
commitd288f7003d256f0f4f85254974239c5f47003ede (patch)
treea16d9d7c4d504104f191fef1f58b7905324a385b
parentb61051ccb4c23958d43d285b8b801af11620264f (diff)
downloadrneovim-d288f7003d256f0f4f85254974239c5f47003ede.tar.gz
rneovim-d288f7003d256f0f4f85254974239c5f47003ede.tar.bz2
rneovim-d288f7003d256f0f4f85254974239c5f47003ede.zip
fix(popup): wrong extmark data sync when lines changed in popup preview #30246
Problem: when popup preview buffer has filetype like markdown and ts is enabled, the extmark clean and update not correct, if add the extmark sync there has lots of duplicate codes like nvim_buf_set_lines. Solution: use nvim_buf_set_lines api internally to set info to popup preview buffer.
-rw-r--r--src/nvim/popupmenu.c70
-rw-r--r--test/functional/ui/popupmenu_spec.lua105
2 files changed, 137 insertions, 38 deletions
diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
index 75e5a52829..2b1dd22b1a 100644
--- a/src/nvim/popupmenu.c
+++ b/src/nvim/popupmenu.c
@@ -7,6 +7,7 @@
#include <stdint.h>
#include <string.h>
+#include "nvim/api/buffer.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ascii_defs.h"
@@ -32,6 +33,7 @@
#include "nvim/highlight_defs.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
+#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -787,36 +789,41 @@ void pum_redraw(void)
}
}
-/// set info text to preview buffer.
+/// Set the informational text in the preview buffer when the completion
+/// item does not include a dedicated preview or popup window.
+///
+/// @param[in] buf Buffer where the text will be set.
+/// @param[in] info Informational text to display in the preview buffer.
+/// @param[in] lnum Where to start the text. Incremented for each added line.
+/// @param[out] max_width Maximum width of the displayed text.
static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *max_width)
{
- bcount_t inserted_bytes = 0;
- for (char *p = info; *p != NUL;) {
- int text_width = 0;
- char *e = vim_strchr(p, '\n');
- if (e == NULL) {
- ml_append_buf(buf, (*lnum)++, p, 0, false);
- text_width = (int)mb_string2cells(p);
- if (text_width > *max_width) {
- *max_width = text_width;
- }
- break;
- }
- *e = NUL;
- ml_append_buf(buf, (*lnum)++, p, (int)(e - p + 1), false);
- inserted_bytes += (bcount_t)strlen(p) + 1;
- text_width = (int)mb_string2cells(p);
- if (text_width > *max_width) {
- *max_width = text_width;
- }
- *e = '\n';
- p = e + 1;
+ Error err = ERROR_INIT;
+ Arena arena = ARENA_EMPTY;
+ Array replacement = ARRAY_DICT_INIT;
+ char *token = NULL;
+ char *line = os_strtok(info, "\n", &token);
+ buf->b_p_ma = true;
+ while (line != NULL) {
+ ADD(replacement, STRING_OBJ(cstr_to_string(line)));
+ (*lnum)++;
+ (*max_width) = MAX(*max_width, (int)mb_string2cells(line));
+ line = os_strtok(NULL, "\n", &token);
}
- // delete the empty last line
- ml_delete_buf(buf, buf->b_ml.ml_line_count, false);
- if (get_cot_flags() & kOptCotFlagPopup) {
- extmark_splice(buf, 1, 0, 1, 0, 0, buf->b_ml.ml_line_count, 0, inserted_bytes, kExtmarkNoUndo);
+
+ int original_textlock = textlock;
+ if (textlock > 0) {
+ textlock = 0;
+ }
+ nvim_buf_set_lines(0, buf->handle, 0, -1, false, replacement, &arena, &err);
+ textlock = original_textlock;
+ if (ERROR_SET(&err)) {
+ emsg(err.msg);
+ api_clear_error(&err);
}
+ arena_mem_free(arena_finish(&arena));
+ api_free_array(replacement);
+ buf->b_p_ma = false;
}
/// adjust floating info preview window position
@@ -866,14 +873,6 @@ win_T *pum_set_info(int selected, char *info)
if (!wp) {
return NULL;
}
- } else {
- // clean exist buffer
- linenr_T count = wp->w_buffer->b_ml.ml_line_count;
- while (!buf_is_empty(wp->w_buffer)) {
- ml_delete_buf(wp->w_buffer, 1, false);
- }
- bcount_t deleted_bytes = get_region_bytecount(wp->w_buffer, 1, count, 0, 0);
- extmark_splice(wp->w_buffer, 1, 0, count, 0, deleted_bytes, 1, 0, 0, kExtmarkNoUndo);
}
linenr_T lnum = 0;
int max_info_width = 0;
@@ -1011,7 +1010,8 @@ static bool pum_set_selected(int n, int repeat)
&& (curbuf->b_nwindows == 1)
&& (curbuf->b_fname == NULL)
&& bt_nofile(curbuf)
- && (curbuf->b_p_bh[0] == 'w')) {
+ && (curbuf->b_p_bh[0] == 'w')
+ && !use_float) {
// Already a "wipeout" buffer, make it empty.
while (!buf_is_empty(curbuf)) {
ml_delete(1, false);
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
index d1228d3607..60d59190ce 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -1680,7 +1680,7 @@ describe('builtin popupmenu', function()
end)
end
- describe('floating window preview #popup', function()
+ describe('floating window preview popup', function()
it('pum popup preview', function()
--row must > 10
screen:try_resize(40, 11)
@@ -1693,14 +1693,29 @@ describe('builtin popupmenu', function()
endfunc
set omnifunc=Omni_test
set completeopt=menu,popup
-
funct Set_info()
let comp_info = complete_info()
if comp_info['selected'] == 2
call nvim__complete_set(comp_info['selected'], {"info": "3info"})
endif
endfunc
- autocmd CompleteChanged * call Set_info()
+ funct TsHl()
+ let comp_info = complete_info()
+ if get(comp_info, 'previewbufnr', 0) > 0
+ call v:lua.vim.treesitter.start(comp_info['preview_bufnr'], 'markdown')
+ endif
+ if comp_info['selected'] == 0
+ call nvim__complete_set(comp_info['selected'], {"info": "```lua\nfunction test()\n print('foo')\nend\n```"})
+ endif
+ endfunc
+ augroup Group
+ au!
+ autocmd CompleteChanged * :call Set_info()
+ augroup END
+ funct TestTs()
+ autocmd! Group
+ autocmd CompleteChanged * call TsHl()
+ endfunc
]])
feed('Gi<C-x><C-o>')
--floating preview in right
@@ -2004,6 +2019,90 @@ describe('builtin popupmenu', function()
]],
}
end
+ feed('<C-E><Esc>')
+
+ -- works when scroll with treesitter highlight
+ command('call TestTs()')
+ feed('S<C-x><C-o>')
+ if multigrid then
+ screen:expect({
+ grid = [[
+ ## grid 1
+ [2:----------------------------------------]|*10
+ [3:----------------------------------------]|
+ ## grid 2
+ one^ |
+ {1:~ }|*9
+ ## grid 3
+ {2:-- }{5:match 1 of 3} |
+ ## grid 5
+ {s:one }|
+ {n:two }|
+ {n:looooooooooooooong }|
+ ## grid 9
+ {n:```lua }|
+ {n:function test()}|
+ {n: print('foo') }|
+ {n:end }|
+ {n:``` }|
+ {n: }|
+ ]],
+ float_pos = {
+ [5] = { -1, 'NW', 2, 1, 0, false, 100 },
+ [9] = { 1005, 'NW', 1, 1, 19, false, 50 },
+ },
+ win_viewport = {
+ [2] = {
+ win = 1000,
+ topline = 0,
+ botline = 2,
+ curline = 0,
+ curcol = 3,
+ linecount = 1,
+ sum_scroll_delta = 0,
+ },
+ [9] = {
+ win = 1005,
+ topline = 0,
+ botline = 6,
+ curline = 0,
+ curcol = 0,
+ linecount = 5,
+ sum_scroll_delta = 0,
+ },
+ },
+ win_viewport_margins = {
+ [2] = {
+ bottom = 0,
+ left = 0,
+ right = 0,
+ top = 0,
+ win = 1000,
+ },
+ [9] = {
+ bottom = 0,
+ left = 0,
+ right = 0,
+ top = 0,
+ win = 1005,
+ },
+ },
+ })
+ else
+ screen:expect({
+ grid = [[
+ one^ |
+ {s:one }{n:```lua }{1: }|
+ {n:two function test()}{1: }|
+ {n:looooooooooooooong print('foo') }{1: }|
+ {1:~ }{n:end }{1: }|
+ {1:~ }{n:``` }{1: }|
+ {1:~ }{n: }{1: }|
+ {1:~ }|*3
+ {2:-- }{5:match 1 of 3} |
+ ]],
+ })
+ end
end)
end)