Skip to content

Commit 6d25187

Browse files
feat: output preview with ghost text, including for snippets (#186)
* feat: show output preview with ghost-text * feat: handle ghost text in the middle of line * fix: buffer text being covered by ghost text * feat: ghost text preview supports snippets * feat: add highlight link for ghost text preview * feat: add option to enable/disable ghost text preview * refactor: rename ghost-text-preview to ghost-text * feat: add default to ghost text config and update readme * refactor: use text-edits libs textEdit item * refactor: listen to autocomplete on_select to decide when to show ghost text * feat: use smarter way to get ghost text result * chore: remove previously added line break
1 parent 70438ac commit 6d25187

File tree

4 files changed

+79
-0
lines changed

4 files changed

+79
-0
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,9 @@ MiniDeps.add({
346346
border = 'padded',
347347
winhighlight = 'Normal:BlinkCmpSignatureHelp,FloatBorder:BlinkCmpSignatureHelpBorder',
348348
},
349+
ghost_text = {
350+
enabled = false,
351+
},
349352
},
350353

351354
highlight = {

lua/blink/cmp/config.lua

+7
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
--- @field autocomplete? blink.cmp.AutocompleteConfig
9191
--- @field documentation? blink.cmp.DocumentationConfig
9292
--- @field signature_help? blink.cmp.SignatureHelpConfig
93+
--- @field ghost_text? GhostTextConfig
9394

9495
--- @class blink.cmp.HighlightConfig
9596
--- @field ns? number
@@ -137,6 +138,9 @@
137138
--- @field border? blink.cmp.WindowBorder
138139
--- @field winhighlight? string
139140

141+
--- @class GhostTextConfig
142+
--- @field enabled? boolean
143+
140144
--- @class blink.cmp.Config
141145
--- @field keymap? blink.cmp.KeymapConfig
142146
--- @field accept? blink.cmp.AcceptConfig
@@ -340,6 +344,9 @@ local config = {
340344
border = 'padded',
341345
winhighlight = 'Normal:BlinkCmpSignatureHelp,FloatBorder:BlinkCmpSignatureHelpBorder',
342346
},
347+
ghost_text = {
348+
enabled = false,
349+
},
343350
},
344351

345352
highlight = {

lua/blink/cmp/init.lua

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ cmp.setup = function(opts)
3030
cmp.windows = {
3131
autocomplete = require('blink.cmp.windows.autocomplete').setup(),
3232
documentation = require('blink.cmp.windows.documentation').setup(),
33+
ghost_text = require('blink.cmp.windows.ghost-text').setup(),
3334
}
3435

3536
cmp.trigger.listen_on_show(function(context) cmp.sources.request_completions(context) end)
@@ -99,6 +100,8 @@ cmp.add_default_highlights = function()
99100
set_hl('BlinkCmpKind' .. kind, { link = use_nvim_cmp and 'CmpItemKind' .. kind or 'BlinkCmpKind' })
100101
end
101102

103+
set_hl('BlinkCmpGhostText', { link = use_nvim_cmp and 'CmpGhostText' or 'Comment' })
104+
102105
set_hl('BlinkCmpMenu', { link = 'Pmenu' })
103106
set_hl('BlinkCmpMenuBorder', { link = 'Pmenu' })
104107
set_hl('BlinkCmpMenuSelection', { link = 'PmenuSel' })

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

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
local config = require('blink.cmp.config')
2+
local autocomplete = require('blink.cmp.windows.autocomplete')
3+
4+
local ghost_text_config = config.windows.ghost_text
5+
6+
local ghost_text = {
7+
enabled = ghost_text_config and ghost_text_config.enabled,
8+
extmark_id = 1,
9+
ns_id = config.highlight.ns,
10+
}
11+
12+
function ghost_text.setup()
13+
autocomplete.listen_on_select(function(item, context)
14+
if ghost_text.enabled ~= true then return end
15+
ghost_text.show_preview(item)
16+
end)
17+
autocomplete.listen_on_close(function() ghost_text.clear_preview() end)
18+
19+
return ghost_text
20+
end
21+
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+
--- @param selected_item? blink.cmp.CompletionItem
30+
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)
34+
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)
37+
text_edit.newText = expanded_snippet and tostring(expanded_snippet) or text_edit.newText
38+
end
39+
40+
local display_lines = vim.split(get_still_untyped_text(text_edit), '\n', { plain = true }) or {}
41+
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+
50+
if #display_lines > 1 then
51+
extmark.virt_lines = {}
52+
for i = 2, #display_lines do
53+
extmark.virt_lines[i - 1] = { { display_lines[i], 'BlinkCmpGhostText' } }
54+
end
55+
end
56+
57+
local cursor_pos = {
58+
text_edit.range.start.line,
59+
text_edit.range['end'].character,
60+
}
61+
vim.api.nvim_buf_set_extmark(0, ghost_text.ns_id, cursor_pos[1], cursor_pos[2], extmark)
62+
end
63+
64+
function ghost_text.clear_preview() vim.api.nvim_buf_del_extmark(0, ghost_text.ns_id, ghost_text.extmark_id) end
65+
66+
return ghost_text

0 commit comments

Comments
 (0)