Skip to content

Commit a94bbaf

Browse files
fix(ghost-text): flickering using autocmds (#255)
* fix(ghost-text): flickering using decoration provider and redraw function * feat: use autocmds for ghost text redraw --------- Co-authored-by: Liam Dyer <[email protected]>
1 parent e9c9b41 commit a94bbaf

File tree

1 file changed

+62
-30
lines changed

1 file changed

+62
-30
lines changed

lua/blink/cmp/windows/ghost-text.lua

+62-30
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,98 @@
11
local config = require('blink.cmp.config')
22
local autocomplete = require('blink.cmp.windows.autocomplete')
3+
local text_edits_lib = require('blink.cmp.accept.text-edits')
4+
local snippets_utils = require('blink.cmp.sources.snippets.utils')
35

46
local ghost_text_config = config.windows.ghost_text
57

8+
--- @class blink.cmp.windows.ghost_text
9+
--- @field win integer?
10+
--- @field selected_item blink.cmp.CompletionItem?
11+
--- @field extmark_id integer?
612
local ghost_text = {
7-
enabled = ghost_text_config and ghost_text_config.enabled,
8-
extmark_id = 1,
9-
ns_id = config.highlight.ns,
13+
win = nil,
14+
selected_item = nil,
1015
}
1116

17+
--- @param textEdit lsp.TextEdit
18+
local function get_still_untyped_text(textEdit)
19+
local type_text_length = textEdit.range['end'].character - textEdit.range.start.character
20+
local result = textEdit.newText:sub(type_text_length + 1)
21+
return result
22+
end
23+
1224
function ghost_text.setup()
25+
-- immediately re-draw the preview when the cursor moves/text changes
26+
vim.api.nvim_create_autocmd({ 'CursorMovedI', 'TextChangedI' }, {
27+
callback = function()
28+
if not ghost_text_config.enabled or ghost_text.win == nil then return end
29+
ghost_text.draw_preview(vim.api.nvim_win_get_buf(ghost_text.win))
30+
end,
31+
})
32+
1333
autocomplete.listen_on_select(function(item)
14-
if ghost_text.enabled ~= true then return end
15-
ghost_text.show_preview(item)
34+
if ghost_text_config.enabled then ghost_text.show_preview(item) end
1635
end)
1736
autocomplete.listen_on_close(function() ghost_text.clear_preview() end)
1837

1938
return ghost_text
2039
end
2140

22-
--- @param textEdit lsp.TextEdit
23-
local function get_still_untyped_text(textEdit)
24-
local type_text_length = textEdit.range['end'].character - textEdit.range.start.character
25-
local result = textEdit.newText:sub(type_text_length + 1)
26-
return result
27-
end
28-
2941
--- @param selected_item? blink.cmp.CompletionItem
3042
function ghost_text.show_preview(selected_item)
31-
if selected_item == nil then return end
32-
local text_edits_lib = require('blink.cmp.accept.text-edits')
33-
local text_edit = text_edits_lib.get_from_item(selected_item)
43+
-- nothing to show, clear the preview
44+
if not selected_item then
45+
ghost_text.clear_preview()
46+
return
47+
end
48+
49+
-- update state and redraw
50+
local changed = ghost_text.selected_item ~= selected_item
51+
ghost_text.selected_item = selected_item
52+
ghost_text.win = vim.api.nvim_get_current_win()
53+
if changed then ghost_text.draw_preview(vim.api.nvim_win_get_buf(ghost_text.win)) end
54+
end
3455

35-
if selected_item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then
36-
local expanded_snippet = require('blink.cmp.sources.snippets.utils').safe_parse(text_edit.newText)
56+
function ghost_text.clear_preview()
57+
ghost_text.selected_item = nil
58+
ghost_text.win = nil
59+
if ghost_text.extmark_id ~= nil then
60+
vim.api.nvim_buf_del_extmark(0, config.highlight.ns, ghost_text.extmark_id)
61+
ghost_text.extmark_id = nil
62+
end
63+
end
64+
65+
function ghost_text.draw_preview(bufnr)
66+
if not ghost_text.selected_item then return end
67+
68+
local text_edit = text_edits_lib.get_from_item(ghost_text.selected_item)
69+
70+
if ghost_text.selected_item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then
71+
local expanded_snippet = snippets_utils.safe_parse(text_edit.newText)
3772
text_edit.newText = expanded_snippet and tostring(expanded_snippet) or text_edit.newText
3873
end
3974

4075
local display_lines = vim.split(get_still_untyped_text(text_edit), '\n', { plain = true }) or {}
4176

42-
--- @type vim.api.keyset.set_extmark
43-
local extmark = {
44-
id = ghost_text.extmark_id,
45-
virt_text_pos = 'inline',
46-
virt_text = { { display_lines[1], 'BlinkCmpGhostText' } },
47-
hl_mode = 'combine',
48-
}
49-
77+
local virt_lines = {}
5078
if #display_lines > 1 then
51-
extmark.virt_lines = {}
5279
for i = 2, #display_lines do
53-
extmark.virt_lines[i - 1] = { { display_lines[i], 'BlinkCmpGhostText' } }
80+
virt_lines[i - 1] = { { display_lines[i], 'BlinkCmpGhostText' } }
5481
end
5582
end
5683

5784
local cursor_pos = {
5885
text_edit.range.start.line,
5986
text_edit.range['end'].character,
6087
}
61-
vim.api.nvim_buf_set_extmark(0, ghost_text.ns_id, cursor_pos[1], cursor_pos[2], extmark)
62-
end
6388

64-
function ghost_text.clear_preview() vim.api.nvim_buf_del_extmark(0, ghost_text.ns_id, ghost_text.extmark_id) end
89+
ghost_text.extmark_id = vim.api.nvim_buf_set_extmark(bufnr, config.highlight.ns, cursor_pos[1], cursor_pos[2], {
90+
id = ghost_text.extmark_id,
91+
virt_text_pos = 'inline',
92+
virt_text = { { display_lines[1], 'BlinkCmpGhostText' } },
93+
virt_lines = virt_lines,
94+
hl_mode = 'combine',
95+
})
96+
end
6597

6698
return ghost_text

0 commit comments

Comments
 (0)