Skip to content

feat: output preview with ghost text, including for snippets #186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lua/blink/cmp/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
--- @field autocomplete? blink.cmp.AutocompleteConfig
--- @field documentation? blink.cmp.DocumentationConfig
--- @field signature_help? blink.cmp.SignatureHelpConfig
--- @field ghost_text_preview? blink.cmp.GhostTextPreviewConfig

--- @class blink.cmp.HighlightConfig
--- @field ns? number
Expand Down Expand Up @@ -137,6 +138,9 @@
--- @field border? blink.cmp.WindowBorder
--- @field winhighlight? string

--- @class blink.cmp.GhostTextPreviewConfig
--- @field enabled? boolean

--- @class blink.cmp.Config
--- @field keymap? blink.cmp.KeymapConfig
--- @field accept? blink.cmp.AcceptConfig
Expand Down
2 changes: 2 additions & 0 deletions lua/blink/cmp/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,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' })
Expand Down
7 changes: 7 additions & 0 deletions lua/blink/cmp/windows/autocomplete.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ local config = require('blink.cmp.config')
local renderer = require('blink.cmp.windows.lib.render')
local text_edits_lib = require('blink.cmp.accept.text-edits')
local autocmp_config = config.windows.autocomplete
local ghost_text_preview = require('blink.cmp.windows.ghost-text-preview')

local autocomplete = {
---@type blink.cmp.CompletionItem[]
items = {},
Expand Down Expand Up @@ -79,6 +81,7 @@ function autocomplete.open_with_items(context, items)

autocomplete.update_position(context)
autocomplete.set_has_selected(autocmp_config.selection == 'preselect')
ghost_text_preview.show_preview(autocomplete.get_selected_item())

-- todo: some logic to maintain the selection if the user moved the cursor?
vim.api.nvim_win_set_cursor(autocomplete.win:get_win(), { 1, 0 })
Expand All @@ -89,6 +92,8 @@ function autocomplete.open()
if autocomplete.win:is_open() then return end
vim.iter(autocomplete.event_targets.on_open):each(function(callback) callback() end)
autocomplete.win:open()

-- ghost_text_preview.show_preview(auto)
autocomplete.set_has_selected(autocmp_config.selection == 'preselect')
end

Expand All @@ -98,6 +103,7 @@ function autocomplete.close()
autocomplete.win:close()
autocomplete.set_has_selected(autocmp_config.selection == 'preselect')

ghost_text_preview.clear_preview()
vim.iter(autocomplete.event_targets.on_close):each(function(callback) callback() end)
end

Expand Down Expand Up @@ -181,6 +187,7 @@ local function select(line, skip_auto_insert)
vim.api.nvim_win_set_cursor(autocomplete.win:get_win(), { line, 0 })

local selected_item = autocomplete.get_selected_item()
ghost_text_preview.show_preview(selected_item)

-- when auto_insert is enabled, we immediately apply the text edit
if config.windows.autocomplete.selection == 'auto_insert' and selected_item ~= nil and not skip_auto_insert then
Expand Down
68 changes: 68 additions & 0 deletions lua/blink/cmp/windows/ghost-text-preview.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
local config = require('blink.cmp.config')

local ghost_text_preview_config = config.windows.ghost_text_preview

local ghost_text_preview = {
enabled = ghost_text_preview_config and ghost_text_preview_config.enabled or false,
extmark_id = 1,
ns_id = config.highlight.ns,
}

local function get_text_before_cursor()
local current_line = vim.api.nvim_get_current_line()
local _, col = unpack(vim.api.nvim_win_get_cursor(0))

return string.gsub(string.sub(current_line, 1, col), '^%s*', '')
end

--- @param str1 string
--- @param str2 string
--- @return string
local function get_overlapping_text_from(str1, str2)
for i = 1, #str2 do
local sub_str2 = string.sub(str2, i)
if string.sub(str1, 1, #sub_str2) == sub_str2 then return sub_str2 end
end
return ''
end

--- @param selected_item? blink.cmp.CompletionItem
function ghost_text_preview.show_preview(selected_item)
if ghost_text_preview.enabled ~= true then return end
if selected_item == nil then return end

local text_to_display = selected_item.insertText or selected_item.label

if selected_item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then
local expanded_snippet = require('blink.cmp.sources.snippets.utils').safe_parse(text_to_display)
text_to_display = expanded_snippet and tostring(expanded_snippet) or text_to_display
end

local display_lines = vim.split(text_to_display, '\n', { plain = true }) or {}
display_lines[1] =
string.gsub(display_lines[1], '^' .. get_overlapping_text_from(display_lines[1], get_text_before_cursor()), '')

--- @type vim.api.keyset.set_extmark
local extmark = {
id = ghost_text_preview.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 row, col = unpack(vim.api.nvim_win_get_cursor(0))
vim.api.nvim_buf_set_extmark(0, ghost_text_preview.ns_id, row - 1, col, extmark)
end

function ghost_text_preview.clear_preview()
vim.api.nvim_buf_del_extmark(0, ghost_text_preview.ns_id, ghost_text_preview.extmark_id)
end

return ghost_text_preview