diff --git a/lua/CopilotChat/config/prompts.lua b/lua/CopilotChat/config/prompts.lua index ead305b5..084f7ac3 100644 --- a/lua/CopilotChat/config/prompts.lua +++ b/lua/CopilotChat/config/prompts.lua @@ -94,10 +94,10 @@ return { system_prompt = COPILOT_GENERATE, }, Explain = { - prompt = '> /COPILOT_EXPLAIN\n\nWrite an explanation for the selected code as paragraphs of text.', + prompt = '> !COPILOT_EXPLAIN\n\nWrite an explanation for the selected code as paragraphs of text.', }, Review = { - prompt = '> /COPILOT_REVIEW\n\nReview the selected code.', + prompt = '> !COPILOT_REVIEW\n\nReview the selected code.', callback = function(response, source) local diagnostics = {} for line in response:gmatch('[^\r\n]+') do @@ -139,16 +139,16 @@ return { end, }, Fix = { - prompt = '> /COPILOT_GENERATE\n\nThere is a problem in this code. Rewrite the code to show it with the bug fixed.', + prompt = '> !COPILOT_GENERATE\n\nThere is a problem in this code. Rewrite the code to show it with the bug fixed.', }, Optimize = { - prompt = '> /COPILOT_GENERATE\n\nOptimize the selected code to improve performance and readability.', + prompt = '> !COPILOT_GENERATE\n\nOptimize the selected code to improve performance and readability.', }, Docs = { - prompt = '> /COPILOT_GENERATE\n\nPlease add documentation comments to the selected code.', + prompt = '> !COPILOT_GENERATE\n\nPlease add documentation comments to the selected code.', }, Tests = { - prompt = '> /COPILOT_GENERATE\n\nPlease generate tests for my code.', + prompt = '> !COPILOT_GENERATE\n\nPlease generate tests for my code.', }, Commit = { prompt = '> #git:staged\n\nWrite commit message for the change with commitizen convention. Make sure the title has maximum 50 characters and message is wrapped at 72 characters. Wrap the whole message in code block with language gitcommit.', diff --git a/lua/CopilotChat/init.lua b/lua/CopilotChat/init.lua index 87f0b64b..77ec750c 100644 --- a/lua/CopilotChat/init.lua +++ b/lua/CopilotChat/init.lua @@ -11,9 +11,11 @@ local WORD = '([^%s]+)' --- @class CopilotChat.source --- @field bufnr number --- @field winnr number +--- @field mode string --- @class CopilotChat.state --- @field source CopilotChat.source? +--- @field source_mode string? --- @field last_prompt string? --- @field last_response string? --- @field chat CopilotChat.ui.Chat? @@ -23,6 +25,9 @@ local state = { -- Current state tracking source = nil, + -- Window visual selection tracking + source_mode = nil, + -- Last state tracking last_prompt = nil, last_response = nil, @@ -56,11 +61,26 @@ local function highlight_selection(clear, config) return end - vim.api.nvim_buf_set_extmark(selection.bufnr, selection_ns, selection.start_line - 1, 0, { - hl_group = 'CopilotChatSelection', - end_row = selection.end_line, - strict = false, - }) + if selection.start_col and selection.end_col then + vim.api.nvim_buf_set_extmark( + selection.bufnr, + selection_ns, + selection.start_line - 1, + selection.start_col, + { + hl_group = 'CopilotChatSelection', + end_row = selection.end_line - 1, + end_col = selection.end_col, + strict = false, + } + ) + else + vim.api.nvim_buf_set_extmark(selection.bufnr, selection_ns, selection.start_line - 1, 0, { + hl_group = 'CopilotChatSelection', + end_row = selection.end_line, + strict = false, + }) + end end ---@param start_of_chat boolean? @@ -384,8 +404,8 @@ end ---@return table function M.complete_info() return { - triggers = { '@', '/', '#', '$' }, - pattern = [[\%(@\|/\|#\|\$\)\S*]], + triggers = { '@', '!', '#', '$' }, + pattern = [[\%(@\|!\|#\|\$\)\S*]], } end @@ -410,7 +430,7 @@ function M.complete_items(callback) end items[#items + 1] = { - word = '/' .. name, + word = '!' .. name, abbr = name, kind = kind, info = info, @@ -492,6 +512,8 @@ end --- Open the chat window. ---@param config CopilotChat.config.shared? function M.open(config) + local source_mode = vim.fn.mode() + -- If we are already in chat window, do nothing if state.chat:active() then return @@ -502,6 +524,7 @@ function M.open(config) state.source = { bufnr = vim.api.nvim_get_current_buf(), winnr = vim.api.nvim_get_current_win(), + mode = source_mode, } return end @@ -520,6 +543,7 @@ end --- Toggle the chat window. ---@param config CopilotChat.config.shared? function M.toggle(config) + state.source_mode = vim.fn.mode() if state.chat:visible() then M.close() else @@ -599,6 +623,8 @@ end ---@param prompt string? ---@param config CopilotChat.config.shared? function M.ask(prompt, config) + state.source_mode = vim.fn.mode() + M.open(config) prompt = vim.trim(prompt or '') @@ -859,11 +885,11 @@ function M.setup(config) -- Handle removed commands vim.api.nvim_create_user_command('CopilotChatFixDiagnostic', function() utils.deprecate('CopilotChatFixDiagnostic', 'CopilotChatFix') - M.ask('/Fix') + M.ask('!Fix') end, { force = true }) vim.api.nvim_create_user_command('CopilotChatCommitStaged', function() utils.deprecate('CopilotChatCommitStaged', 'CopilotChatCommit') - M.ask('/Commit') + M.ask('!Commit') end, { force = true }) M.config = vim.tbl_deep_extend('force', require('CopilotChat.config'), config or {}) diff --git a/lua/CopilotChat/integrations/telescope.lua b/lua/CopilotChat/integrations/telescope.lua index e636aeb6..fb61c4f1 100644 --- a/lua/CopilotChat/integrations/telescope.lua +++ b/lua/CopilotChat/integrations/telescope.lua @@ -13,7 +13,8 @@ local M = {} --- Pick an action from a list of actions ---@param pick_actions CopilotChat.integrations.actions?: A table with the actions to pick from ---@param opts table?: Telescope options -function M.pick(pick_actions, opts) +---@param action_callback function?: A callback to run after the action is picked +function M.pick(pick_actions, opts, action_callback) if not pick_actions or not pick_actions.actions or vim.tbl_isempty(pick_actions.actions) then return end @@ -52,7 +53,11 @@ function M.pick(pick_actions, opts) end vim.defer_fn(function() - chat.ask(pick_actions.actions[selected[1]].prompt, pick_actions.actions[selected[1]]) + if not action_callback then + chat.ask(pick_actions.actions[selected[1]].prompt, pick_actions.actions[selected[1]]) + return + end + action_callback(selected) end, 100) end) return true diff --git a/lua/CopilotChat/select.lua b/lua/CopilotChat/select.lua index e6b76587..baa3507d 100644 --- a/lua/CopilotChat/select.lua +++ b/lua/CopilotChat/select.lua @@ -8,6 +8,8 @@ ---@field content string ---@field start_line number ---@field end_line number +---@field start_col number +---@field end_col number ---@field filename string ---@field filetype string ---@field bufnr number @@ -52,8 +54,9 @@ end --- @return CopilotChat.select.selection|nil function M.visual(source) local bufnr = source.bufnr - local start_line = unpack(vim.api.nvim_buf_get_mark(bufnr, '<')) - local finish_line = unpack(vim.api.nvim_buf_get_mark(bufnr, '>')) + local mode = source.mode + local start_line, start_col = unpack(vim.api.nvim_buf_get_mark(bufnr, '<')) + local finish_line, finish_col = unpack(vim.api.nvim_buf_get_mark(bufnr, '>')) if start_line == 0 or finish_line == 0 then return nil end @@ -61,10 +64,28 @@ function M.visual(source) start_line, finish_line = finish_line, start_line end - local ok, lines = pcall(vim.api.nvim_buf_get_lines, bufnr, start_line - 1, finish_line, false) - if not ok then - return nil + -- Visual Line mode, adjusting the end column + local ok, lines + if mode == 'V' then + ok, lines = pcall(vim.api.nvim_buf_get_lines, bufnr, start_line - 1, finish_line, false) + if not ok then + return nil + end + else + ok, lines = pcall( + vim.api.nvim_buf_get_text, + bufnr, + start_line - 1, + start_col, + finish_line - 1, + finish_col + 1, + {} + ) + if not ok then + return nil + end end + local lines_content = table.concat(lines, '\n') if vim.trim(lines_content) == '' then return nil @@ -76,6 +97,8 @@ function M.visual(source) filetype = vim.bo[bufnr].filetype, start_line = start_line, end_line = finish_line, + start_col = start_col, + end_col = finish_col, bufnr = bufnr, diagnostics = get_diagnostics_in_range(bufnr, start_line, finish_line), } @@ -85,6 +108,7 @@ end --- @param source CopilotChat.source --- @return CopilotChat.select.selection|nil function M.buffer(source) + source.mode = nil local bufnr = source.bufnr local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) if not lines or #lines == 0 then