Experimental: Ghost text (#119)

* Inline suggest experimental

* Fix flicker

* Fix ci fails

* Reduce flicker

* Improve menu functions

* Default false

* README.md

* Add require

* Rename inline_preview to ghost_text
This commit is contained in:
hrsh7th
2021-09-03 18:34:10 +09:00
committed by GitHub
parent 6cc8b82267
commit e867bf233e
5 changed files with 74 additions and 31 deletions

View File

@@ -354,6 +354,12 @@ Specify preselect mode. The following modes are available.
Default: `cmp.PreselectMode.Item` Default: `cmp.PreselectMode.Item`
#### experimental.ghost_text (type: boolean)
Specify whether to display ghost text.
Default: `false`
Programatic API Programatic API
==================== ====================

View File

@@ -61,6 +61,10 @@ return function()
end end
}, },
experimental = {
ghost_text = false,
},
sources = {}, sources = {},
} }
end end

View File

@@ -1,5 +1,6 @@
local debug = require('cmp.utils.debug') local debug = require('cmp.utils.debug')
local char = require('cmp.utils.char') local char = require('cmp.utils.char')
local str = require('cmp.utils.str')
local pattern = require('cmp.utils.pattern') local pattern = require('cmp.utils.pattern')
local async = require('cmp.utils.async') local async = require('cmp.utils.async')
local keymap = require('cmp.utils.keymap') local keymap = require('cmp.utils.keymap')
@@ -18,6 +19,16 @@ core.SOURCE_TIMEOUT = 500
---Suspending state. ---Suspending state.
core.suspending = false core.suspending = false
core.GHOST_TEXT_NS = vim.api.nvim_create_namespace('cmp:GHOST_TEXT');
vim.api.nvim_set_decoration_provider(core.GHOST_TEXT_NS, {
on_win = function()
if config.get().experimental.ghost_text then
core.ghost_text(core.menu:get_first_entry())
end
end,
})
---@type cmp.Menu ---@type cmp.Menu
core.menu = menu.new({ core.menu = menu.new({
on_select = function(e) on_select = function(e)
@@ -27,6 +38,43 @@ core.menu = menu.new({
end, end,
}) })
---Show ghost text if possible
---@param e cmp.Entry
core.ghost_text = function(e)
if not e then
return
end
local ctx = context.new()
if ctx.cursor_after_line ~= '' then
return
end
local diff = ctx.cursor.col - e:get_offset()
local text = e:get_insert_text()
if e.completion_item.insertTextFormat == types.lsp.InsertTextFormat.Snippet then
text = vim.lsp.util.parse_snippet(text)
end
text = string.sub(str.oneline(text), diff + 1)
if #text > 0 then
vim.api.nvim_buf_set_extmark(
ctx.bufnr,
core.GHOST_TEXT_NS,
ctx.cursor.row - 1,
ctx.cursor.col - 1,
{
right_gravity = true,
virt_text = { { text, 'Comment' } },
virt_text_pos = 'overlay',
virt_text_win_col = ctx.cursor.col - 1,
hl_mode = 'blend',
priority = 0,
ephemeral = true,
}
)
end
end
---@type table<number, cmp.Source> ---@type table<number, cmp.Source>
core.sources = {} core.sources = {}

View File

@@ -193,58 +193,39 @@ end
---Geta current active entry ---Geta current active entry
---@return cmp.Entry|nil ---@return cmp.Entry|nil
menu.get_active_entry = function(self) menu.get_active_entry = function(self)
local completed_item = vim.v.completed_item or {} if vim.fn.pumvisible() == 0 or not (vim.v.completed_item or {}).user_data then
if vim.fn.pumvisible() == 0 or not completed_item.user_data then
return nil return nil
end end
return self:get_selected_entry()
local id = completed_item.user_data.cmp
if id then
return self.entry_map[id]
end
return nil
end end
---Get current selected entry ---Get current selected entry
---@return cmp.Entry|nil ---@return cmp.Entry|nil
menu.get_selected_entry = function(self) menu.get_selected_entry = function(self)
local e = self:get_active_entry() if not self:is_valid_mode() then
if e then return nil
return e
end end
local selected = vim.fn.complete_info({ 'selected' }).selected local selected = vim.fn.complete_info({ 'selected' }).selected
if selected == -1 then if selected == -1 then
return nil return nil
end end
local items = vim.fn.complete_info({ 'items' }).items return self.entries[math.max(selected, 0) + 1]
local completed_item = items[math.max(selected, 0) + 1] or {}
if not completed_item.user_data then
return nil
end
local id = completed_item.user_data.cmp
if id then
return self.entry_map[id]
end
return nil
end end
---Get first entry ---Get first entry
---@param self cmp.Entry|nil ---@param self cmp.Entry|nil
menu.get_first_entry = function(self) menu.get_first_entry = function(self)
local info = vim.fn.complete_info({ 'items' }) if not self:is_valid_mode() then
local completed_item = info.items[1] or {}
if not completed_item.user_data then
return nil return nil
end end
return self.entries[1]
end
local id = completed_item.user_data.cmp ---Return the completion menu is visible or not.
if id then ---@return boolean
return self.entry_map[id] menu.is_valid_mode = function()
end return vim.fn.complete_info({ 'mode' }).mode == 'eval'
return nil
end end
return menu return menu

View File

@@ -57,6 +57,7 @@ cmp.PreselectMode.None = 'none'
---@field public event cmp.EventConfig ---@field public event cmp.EventConfig
---@field public mapping table<string, fun(core: cmp.Core, fallback: function)> ---@field public mapping table<string, fun(core: cmp.Core, fallback: function)>
---@field public sources cmp.SourceConfig[] ---@field public sources cmp.SourceConfig[]
---@field public experimental cmp.ExperimentalConfig
---@class cmp.CompletionConfig ---@class cmp.CompletionConfig
---@field public autocomplete cmp.TriggerEvent[] ---@field public autocomplete cmp.TriggerEvent[]
@@ -88,6 +89,9 @@ cmp.PreselectMode.None = 'none'
---@class cmp.EventConfig ---@class cmp.EventConfig
---@field on_confirm_done function(e: cmp.Entry) ---@field on_confirm_done function(e: cmp.Entry)
---@class cmp.ExperimentalConfig
---@field public ghost_text boolean
---@class cmp.SourceConfig ---@class cmp.SourceConfig
---@field public name string ---@field public name string
---@field public opts table ---@field public opts table