Skip to content

Commit

Permalink
feat: Switch from virt text to virt lines for help (CopilotC-Nvim#172)
Browse files Browse the repository at this point in the history
Allows nice multiline display

Signed-off-by: Tomas Slusny <[email protected]>
  • Loading branch information
deathbeam authored Mar 16, 2024
1 parent 4523d1b commit 55722d7
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 95 deletions.
21 changes: 19 additions & 2 deletions lua/CopilotChat/chat.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
---@field close fun(self: CopilotChat.Chat)
---@field focus fun(self: CopilotChat.Chat)
---@field follow fun(self: CopilotChat.Chat)
---@field finish fun(self: CopilotChat.Chat)

local Spinner = require('CopilotChat.spinner')
local utils = require('CopilotChat.utils')
Expand Down Expand Up @@ -38,7 +39,9 @@ local function create_buf()
return bufnr
end

local Chat = class(function(self, on_buf_create)
local Chat = class(function(self, ns, help, on_buf_create)
self.ns = ns
self.help = help
self.on_buf_create = on_buf_create
self.bufnr = nil
self.spinner = nil
Expand All @@ -62,7 +65,7 @@ function Chat:validate()

self.bufnr = create_buf()
if not self.spinner then
self.spinner = Spinner(self.bufnr, 'copilot-chat')
self.spinner = Spinner(self.bufnr, self.ns, 'copilot-chat')
else
self.spinner.bufnr = self.bufnr
end
Expand All @@ -87,6 +90,11 @@ end

function Chat:append(str)
self:validate()

if self.spinner then
self.spinner:start()
end

local last_line, last_column, _ = self:last()
vim.api.nvim_buf_set_text(
self.bufnr,
Expand Down Expand Up @@ -162,6 +170,7 @@ function Chat:open(config)
vim.wo[self.winnr].conceallevel = 2
vim.wo[self.winnr].concealcursor = 'niv'
vim.wo[self.winnr].foldlevel = 99
vim.wo[self.winnr].relativenumber = false
if config.show_folds then
vim.wo[self.winnr].foldcolumn = '1'
vim.wo[self.winnr].foldmethod = 'expr'
Expand Down Expand Up @@ -200,4 +209,12 @@ function Chat:follow()
vim.api.nvim_win_set_cursor(self.winnr, { last_line + 1, last_column })
end

function Chat:finish()
if not self.spinner then
return
end

self.spinner:finish(self.help, true)
end

return Chat
26 changes: 17 additions & 9 deletions lua/CopilotChat/diff.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,24 @@ local function create_buf()
return bufnr
end

local Diff = class(function(self, on_buf_create, help)
local Diff = class(function(self, ns, help, on_buf_create)
self.ns = ns
self.help = help
self.on_buf_create = on_buf_create
self.current = nil
self.ns = vim.api.nvim_create_namespace('copilot-diff')
self.mark_ns = vim.api.nvim_create_namespace('copilot-diff-mark')
self.hl_ns = vim.api.nvim_create_namespace('copilot-diff-mark')
self.bufnr = nil
vim.api.nvim_set_hl(self.ns, '@diff.plus', { bg = blend_color_with_neovim_bg('DiffAdd', 20) })
vim.api.nvim_set_hl(self.ns, '@diff.minus', { bg = blend_color_with_neovim_bg('DiffDelete', 20) })
vim.api.nvim_set_hl(self.ns, '@diff.delta', { bg = blend_color_with_neovim_bg('DiffChange', 20) })
vim.api.nvim_set_hl(self.hl_ns, '@diff.plus', { bg = blend_color_with_neovim_bg('DiffAdd', 20) })
vim.api.nvim_set_hl(
self.hl_ns,
'@diff.minus',
{ bg = blend_color_with_neovim_bg('DiffDelete', 20) }
)
vim.api.nvim_set_hl(
self.hl_ns,
'@diff.delta',
{ bg = blend_color_with_neovim_bg('DiffChange', 20) }
)
end)

function Diff:valid()
Expand Down Expand Up @@ -75,13 +83,13 @@ function Diff:show(a, b, filetype, winnr)
diff = '\n' .. diff

vim.api.nvim_win_set_buf(winnr, self.bufnr)
vim.api.nvim_win_set_hl_ns(winnr, self.ns)
vim.api.nvim_win_set_hl_ns(winnr, self.hl_ns)
vim.bo[self.bufnr].modifiable = true
vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, vim.split(diff, '\n'))
vim.bo[self.bufnr].modifiable = false

local opts = {
id = self.mark_ns,
id = self.ns,
hl_mode = 'combine',
priority = 100,
virt_text = { { self.help, 'CursorColumn' } },
Expand All @@ -92,7 +100,7 @@ function Diff:show(a, b, filetype, winnr)
opts.virt_text_pos = 'inline'
end

vim.api.nvim_buf_set_extmark(self.bufnr, self.mark_ns, 0, 0, opts)
vim.api.nvim_buf_set_extmark(self.bufnr, self.ns, 0, 0, opts)
local ok, parser = pcall(vim.treesitter.get_parser, self.bufnr, 'diff')
if ok and parser then
vim.treesitter.start(self.bufnr, 'diff')
Expand Down
117 changes: 56 additions & 61 deletions lua/CopilotChat/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -111,24 +111,6 @@ local function append(str)
end)
end

local function show_help()
local out = 'Press '
for name, key in pairs(M.config.mappings) do
if key then
out = out .. "'" .. key .. "' to " .. name:gsub('_', ' ') .. ', '
end
end

out = out
.. 'use @'
.. M.config.mappings.complete
.. ' or /'
.. M.config.mappings.complete
.. ' for different options.'
state.chat.spinner:finish()
state.chat.spinner:set(out, -1)
end

local function complete()
local line = vim.api.nvim_get_current_line()
local col = vim.api.nvim_win_get_cursor(0)[2]
Expand Down Expand Up @@ -325,7 +307,6 @@ function M.ask(prompt, config, source)
append(updated_prompt)
append('\n\n **' .. config.name .. '** ' .. config.separator .. '\n\n')
state.chat:follow()
state.chat.spinner:start()

local selected_context = config.context
if string.find(prompt, '@buffers') then
Expand All @@ -339,7 +320,7 @@ function M.ask(prompt, config, source)
append('\n\n **Error** ' .. config.separator .. '\n\n')
append('```\n' .. err .. '\n```')
append('\n\n' .. config.separator .. '\n\n')
show_help()
state.chat:finish()
end

context.find_for_query(state.copilot, {
Expand All @@ -365,7 +346,7 @@ function M.ask(prompt, config, source)
append('\n\n' .. token_count .. ' tokens used')
end
append('\n\n' .. config.separator .. '\n\n')
show_help()
state.chat:finish()
end,
on_progress = function(token)
append(token)
Expand All @@ -380,7 +361,7 @@ function M.reset()
state.copilot:reset()
state.chat:clear()
append('\n')
show_help()
state.chat:finish()
end

--- Enables/disables debug
Expand All @@ -401,48 +382,62 @@ end
function M.setup(config)
M.config = vim.tbl_deep_extend('force', default_config, config or {})
state.copilot = Copilot(M.config.proxy, M.config.allow_insecure)
local mark_ns = vim.api.nvim_create_namespace('copilot-chat')

state.diff = Diff(
function(bufnr)
if M.config.mappings.close then
vim.keymap.set('n', M.config.mappings.close, function()
state.diff:restore(state.chat.winnr, state.chat.bufnr)
end, { buffer = bufnr })
end
if M.config.mappings.accept_diff then
vim.keymap.set('n', M.config.mappings.accept_diff, function()
local selection = get_selection()
if not selection.start_row or not selection.end_row then
return
end
local diff_help = "'"
.. M.config.mappings.close
.. "' to close diff.\n'"
.. M.config.mappings.accept_diff
.. "' to accept diff."

local current = state.diff.current
if not current then
return
end
state.diff = Diff(mark_ns, diff_help, function(bufnr)
if M.config.mappings.close then
vim.keymap.set('n', M.config.mappings.close, function()
state.diff:restore(state.chat.winnr, state.chat.bufnr)
end, { buffer = bufnr })
end
if M.config.mappings.accept_diff then
vim.keymap.set('n', M.config.mappings.accept_diff, function()
local selection = get_selection()
if not selection.start_row or not selection.end_row then
return
end

local lines = vim.split(current, '\n')
if #lines > 0 then
vim.api.nvim_buf_set_text(
state.source.bufnr,
selection.start_row - 1,
selection.start_col - 1,
selection.end_row - 1,
selection.end_col,
lines
)
end
end, { buffer = bufnr })
end
end,
"Press '"
.. M.config.mappings.close
.. "' to close diff, '"
.. M.config.mappings.accept_diff
.. "' to accept diff."
)

state.chat = Chat(function(bufnr)
local current = state.diff.current
if not current then
return
end

local lines = vim.split(current, '\n')
if #lines > 0 then
vim.api.nvim_buf_set_text(
state.source.bufnr,
selection.start_row - 1,
selection.start_col - 1,
selection.end_row - 1,
selection.end_col,
lines
)
end
end, { buffer = bufnr })
end
end)

local chat_help = ''
for name, key in pairs(M.config.mappings) do
if key then
chat_help = chat_help .. "'" .. key .. "' to " .. name:gsub('_', ' ') .. '\n'
end
end

chat_help = chat_help
.. '@'
.. M.config.mappings.complete
.. ' or /'
.. M.config.mappings.complete
.. ' for different completion options.'

state.chat = Chat(mark_ns, chat_help, function(bufnr)
if M.config.mappings.complete then
vim.keymap.set('i', M.config.mappings.complete, complete, { buffer = bufnr })
end
Expand Down
57 changes: 34 additions & 23 deletions lua/CopilotChat/spinner.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---@class CopilotChat.Spinner
---@field bufnr number
---@field set fun(self: CopilotChat.Spinner, text: string, offset: number)
---@field set fun(self: CopilotChat.Spinner, text: string, virt_line: boolean)
---@field start fun(self: CopilotChat.Spinner)
---@field finish fun(self: CopilotChat.Spinner)

Expand All @@ -21,67 +21,78 @@ local spinner_frames = {
'',
}

local Spinner = class(function(self, bufnr, title)
self.ns = vim.api.nvim_create_namespace('copilot-spinner')
local Spinner = class(function(self, bufnr, ns, title)
self.ns = ns
self.bufnr = bufnr
self.title = title
self.timer = nil
self.index = 1
end)

function Spinner:set(text, offset)
offset = offset or 0

function Spinner:set(text, virt_line)
vim.schedule(function()
if not vim.api.nvim_buf_is_valid(self.bufnr) then
self:finish()
return
end

local line = vim.api.nvim_buf_line_count(self.bufnr) - 1 + offset
line = math.max(0, line)
local line = vim.api.nvim_buf_line_count(self.bufnr) - 1

local opts = {
id = self.ns,
hl_mode = 'combine',
priority = 100,
virt_text = vim.tbl_map(function(t)
return { t, 'CursorColumn' }
end, vim.split(text, '\n')),
}

-- stable do not supports virt_text_pos
if not is_stable() then
opts.virt_text_pos = offset ~= 0 and 'inline' or 'eol'
if virt_line then
line = line - 1
opts.virt_lines_leftcol = true
opts.virt_lines = vim.tbl_map(function(t)
return { { '| ' .. t, 'DiagnosticInfo' } }
end, vim.split(text, '\n'))
else
opts.virt_text = vim.tbl_map(function(t)
return { t, 'CursorColumn' }
end, vim.split(text, '\n'))
end

vim.api.nvim_buf_set_extmark(self.bufnr, self.ns, line, 0, opts)
vim.api.nvim_buf_set_extmark(self.bufnr, self.ns, math.max(0, line), 0, opts)
end)
end

function Spinner:start()
if self.timer then
return
end

self.timer = vim.loop.new_timer()
self.timer:start(0, 100, function()
self:set(spinner_frames[self.index])
self.index = self.index % #spinner_frames + 1
end)
end

function Spinner:finish()
if self.timer then
function Spinner:finish(msg, offset)
vim.schedule(function()
if not self.timer then
return
end

self.timer:stop()
self.timer:close()
self.timer = nil

vim.schedule(function()
if not vim.api.nvim_buf_is_valid(self.bufnr) then
return
end
if not vim.api.nvim_buf_is_valid(self.bufnr) then
return
end

if msg then
self:set(msg, offset)
else
vim.api.nvim_buf_del_extmark(self.bufnr, self.ns, self.ns)
vim.notify('Done!', vim.log.levels.INFO, { title = self.title })
end)
end
end
end)
end

return Spinner

0 comments on commit 55722d7

Please sign in to comment.