|
1 | 1 | local config = require('blink.cmp.config')
|
2 | 2 | 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') |
3 | 5 |
|
4 | 6 | local ghost_text_config = config.windows.ghost_text
|
5 | 7 |
|
| 8 | +--- @class blink.cmp.windows.ghost_text |
| 9 | +--- @field win integer? |
| 10 | +--- @field selected_item blink.cmp.CompletionItem? |
| 11 | +--- @field extmark_id integer? |
6 | 12 | 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, |
10 | 15 | }
|
11 | 16 |
|
| 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 | + |
12 | 24 | 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 | + |
13 | 33 | 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 |
16 | 35 | end)
|
17 | 36 | autocomplete.listen_on_close(function() ghost_text.clear_preview() end)
|
18 | 37 |
|
19 | 38 | return ghost_text
|
20 | 39 | end
|
21 | 40 |
|
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 |
| - |
29 | 41 | --- @param selected_item? blink.cmp.CompletionItem
|
30 | 42 | 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 |
34 | 55 |
|
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) |
37 | 72 | text_edit.newText = expanded_snippet and tostring(expanded_snippet) or text_edit.newText
|
38 | 73 | end
|
39 | 74 |
|
40 | 75 | local display_lines = vim.split(get_still_untyped_text(text_edit), '\n', { plain = true }) or {}
|
41 | 76 |
|
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 = {} |
50 | 78 | if #display_lines > 1 then
|
51 |
| - extmark.virt_lines = {} |
52 | 79 | 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' } } |
54 | 81 | end
|
55 | 82 | end
|
56 | 83 |
|
57 | 84 | local cursor_pos = {
|
58 | 85 | text_edit.range.start.line,
|
59 | 86 | text_edit.range['end'].character,
|
60 | 87 | }
|
61 |
| - vim.api.nvim_buf_set_extmark(0, ghost_text.ns_id, cursor_pos[1], cursor_pos[2], extmark) |
62 |
| -end |
63 | 88 |
|
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 |
65 | 97 |
|
66 | 98 | return ghost_text
|
0 commit comments