summaryrefslogtreecommitdiff
path: root/lua/blink/cmp/completion/windows/ghost_text.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua/blink/cmp/completion/windows/ghost_text.lua')
-rw-r--r--lua/blink/cmp/completion/windows/ghost_text.lua100
1 files changed, 100 insertions, 0 deletions
diff --git a/lua/blink/cmp/completion/windows/ghost_text.lua b/lua/blink/cmp/completion/windows/ghost_text.lua
new file mode 100644
index 0000000..2869e95
--- /dev/null
+++ b/lua/blink/cmp/completion/windows/ghost_text.lua
@@ -0,0 +1,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