From 6d2518745db83da0b15f60e22c15c205fb1ed56f Mon Sep 17 00:00:00 2001 From: Giulia Ye Date: Fri, 25 Oct 2024 17:15:35 +0200 Subject: [PATCH] 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 --- README.md | 3 ++ lua/blink/cmp/config.lua | 7 +++ lua/blink/cmp/init.lua | 3 ++ lua/blink/cmp/windows/ghost-text.lua | 66 ++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 lua/blink/cmp/windows/ghost-text.lua diff --git a/README.md b/README.md index db339ffb..46bd1bc5 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,9 @@ MiniDeps.add({ border = 'padded', winhighlight = 'Normal:BlinkCmpSignatureHelp,FloatBorder:BlinkCmpSignatureHelpBorder', }, + ghost_text = { + enabled = false, + }, }, highlight = { diff --git a/lua/blink/cmp/config.lua b/lua/blink/cmp/config.lua index 1d6672d5..e34100e3 100644 --- a/lua/blink/cmp/config.lua +++ b/lua/blink/cmp/config.lua @@ -90,6 +90,7 @@ --- @field autocomplete? blink.cmp.AutocompleteConfig --- @field documentation? blink.cmp.DocumentationConfig --- @field signature_help? blink.cmp.SignatureHelpConfig +--- @field ghost_text? GhostTextConfig --- @class blink.cmp.HighlightConfig --- @field ns? number @@ -137,6 +138,9 @@ --- @field border? blink.cmp.WindowBorder --- @field winhighlight? string +--- @class GhostTextConfig +--- @field enabled? boolean + --- @class blink.cmp.Config --- @field keymap? blink.cmp.KeymapConfig --- @field accept? blink.cmp.AcceptConfig @@ -340,6 +344,9 @@ local config = { border = 'padded', winhighlight = 'Normal:BlinkCmpSignatureHelp,FloatBorder:BlinkCmpSignatureHelpBorder', }, + ghost_text = { + enabled = false, + }, }, highlight = { diff --git a/lua/blink/cmp/init.lua b/lua/blink/cmp/init.lua index 30104f38..b3e3fd38 100644 --- a/lua/blink/cmp/init.lua +++ b/lua/blink/cmp/init.lua @@ -30,6 +30,7 @@ cmp.setup = function(opts) cmp.windows = { autocomplete = require('blink.cmp.windows.autocomplete').setup(), documentation = require('blink.cmp.windows.documentation').setup(), + ghost_text = require('blink.cmp.windows.ghost-text').setup(), } cmp.trigger.listen_on_show(function(context) cmp.sources.request_completions(context) end) @@ -99,6 +100,8 @@ cmp.add_default_highlights = function() set_hl('BlinkCmpKind' .. kind, { link = use_nvim_cmp and 'CmpItemKind' .. kind or 'BlinkCmpKind' }) end + set_hl('BlinkCmpGhostText', { link = use_nvim_cmp and 'CmpGhostText' or 'Comment' }) + set_hl('BlinkCmpMenu', { link = 'Pmenu' }) set_hl('BlinkCmpMenuBorder', { link = 'Pmenu' }) set_hl('BlinkCmpMenuSelection', { link = 'PmenuSel' }) diff --git a/lua/blink/cmp/windows/ghost-text.lua b/lua/blink/cmp/windows/ghost-text.lua new file mode 100644 index 00000000..77ccc19a --- /dev/null +++ b/lua/blink/cmp/windows/ghost-text.lua @@ -0,0 +1,66 @@ +local config = require('blink.cmp.config') +local autocomplete = require('blink.cmp.windows.autocomplete') + +local ghost_text_config = config.windows.ghost_text + +local ghost_text = { + enabled = ghost_text_config and ghost_text_config.enabled, + extmark_id = 1, + ns_id = config.highlight.ns, +} + +function ghost_text.setup() + autocomplete.listen_on_select(function(item, context) + if ghost_text.enabled ~= true then return end + ghost_text.show_preview(item) + end) + autocomplete.listen_on_close(function() ghost_text.clear_preview() end) + + return ghost_text +end + +--- @param textEdit lsp.TextEdit +local function get_still_untyped_text(textEdit) + local type_text_length = textEdit.range['end'].character - textEdit.range.start.character + local result = textEdit.newText:sub(type_text_length + 1) + return result +end + +--- @param selected_item? blink.cmp.CompletionItem +function ghost_text.show_preview(selected_item) + if selected_item == nil then return end + local text_edits_lib = require('blink.cmp.accept.text-edits') + local text_edit = text_edits_lib.get_from_item(selected_item) + + if selected_item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then + local expanded_snippet = require('blink.cmp.sources.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 {} + + --- @type vim.api.keyset.set_extmark + local extmark = { + id = ghost_text.extmark_id, + virt_text_pos = 'inline', + virt_text = { { display_lines[1], 'BlinkCmpGhostText' } }, + hl_mode = 'combine', + } + + if #display_lines > 1 then + extmark.virt_lines = {} + for i = 2, #display_lines do + extmark.virt_lines[i - 1] = { { display_lines[i], 'BlinkCmpGhostText' } } + end + end + + local cursor_pos = { + text_edit.range.start.line, + text_edit.range['end'].character, + } + vim.api.nvim_buf_set_extmark(0, ghost_text.ns_id, cursor_pos[1], cursor_pos[2], extmark) +end + +function ghost_text.clear_preview() vim.api.nvim_buf_del_extmark(0, ghost_text.ns_id, ghost_text.extmark_id) end + +return ghost_text