1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
local config = require('blink.cmp.config').completion.ghost_text
local highlight_ns = require('blink.cmp.config').appearance.highlight_ns
local text_edits_lib = require('blink.cmp.lib.text_edits')
local snippets_utils = require('blink.cmp.sources.snippets.utils')
--- @class blink.cmp.windows.GhostText
--- @field win integer?
--- @field selected_item blink.cmp.CompletionItem?
--- @field extmark_id integer?
---
--- @field is_open fun(): boolean
--- @field show_preview fun(item: blink.cmp.CompletionItem)
--- @field clear_preview fun()
--- @field draw_preview fun(bufnr: number)
--- @type blink.cmp.windows.GhostText
--- @diagnostic disable-next-line: missing-fields
local ghost_text = {
win = nil,
selected_item = nil,
extmark_id = nil,
}
--- @param textEdit lsp.TextEdit
local function get_still_untyped_text(textEdit)
local type_text_length = textEdit.range['end'].character - textEdit.range.start.character
return textEdit.newText:sub(type_text_length + 1)
end
-- immediately re-draw the preview when the cursor moves/text changes
vim.api.nvim_create_autocmd({ 'CursorMovedI', 'TextChangedI' }, {
callback = function()
if config.enabled and ghost_text.win then ghost_text.draw_preview(vim.api.nvim_win_get_buf(ghost_text.win)) end
end,
})
function ghost_text.is_open() return ghost_text.extmark_id ~= nil end
--- @param selected_item? blink.cmp.CompletionItem
function ghost_text.show_preview(selected_item)
-- nothing to show, clear the preview
if not selected_item then
ghost_text.clear_preview()
return
end
-- doesn't work in command mode
-- TODO: integrate with noice.nvim?
if vim.api.nvim_get_mode().mode == 'c' then return end
-- update state and redraw
local changed = ghost_text.selected_item ~= selected_item
ghost_text.selected_item = selected_item
ghost_text.win = vim.api.nvim_get_current_win()
if changed then ghost_text.draw_preview(vim.api.nvim_win_get_buf(ghost_text.win)) end
end
function ghost_text.clear_preview()
ghost_text.selected_item = nil
ghost_text.win = nil
if ghost_text.extmark_id ~= nil then
vim.api.nvim_buf_del_extmark(0, highlight_ns, ghost_text.extmark_id)
ghost_text.extmark_id = nil
end
end
function ghost_text.draw_preview(bufnr)
if not ghost_text.selected_item then return end
local text_edit = text_edits_lib.get_from_item(ghost_text.selected_item)
if ghost_text.selected_item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then
local expanded_snippet = snippets_utils.safe_parse(text_edit.newText)
text_edit.newText = expanded_snippet and tostring(expanded_snippet) or text_edit.newText
end
local display_lines = vim.split(get_still_untyped_text(text_edit), '\n', { plain = true }) or {}
local virt_lines = {}
if #display_lines > 1 then
for i = 2, #display_lines do
virt_lines[i - 1] = { { display_lines[i], 'BlinkCmpGhostText' } }
end
end
local cursor_pos = {
text_edit.range.start.line,
text_edit.range['end'].character,
}
ghost_text.extmark_id = vim.api.nvim_buf_set_extmark(bufnr, highlight_ns, cursor_pos[1], cursor_pos[2], {
id = ghost_text.extmark_id,
virt_text_pos = 'inline',
virt_text = { { display_lines[1], 'BlinkCmpGhostText' } },
virt_lines = virt_lines,
hl_mode = 'combine',
})
end
return ghost_text
|