diff --git a/README.md b/README.md index f340f46d..960fdb2b 100644 --- a/README.md +++ b/README.md @@ -201,8 +201,6 @@ Also see [here](/lua/CopilotChat/config.lua): proxy = nil, -- [protocol://]host[:port] Use this proxy allow_insecure = false, -- Allow insecure server connections - yank_diff_register = '"', -- Allow overriding the register for yanking diffs - system_prompt = prompts.COPILOT_INSTRUCTIONS, -- System prompt to use model = 'gpt-4o', -- GPT model to use, 'gpt-3.5-turbo', 'gpt-4', or 'gpt-4o' temperature = 0.1, -- GPT temperature @@ -215,7 +213,8 @@ Also see [here](/lua/CopilotChat/config.lua): show_folds = true, -- Shows folds for sections in chat show_help = true, -- Shows help message as virtual lines when waiting for user input auto_follow_cursor = true, -- Auto-follow cursor in chat - auto_insert_mode = false, -- Automatically enter insert mode when opening window and if auto follow cursor is enabled on new prompt + auto_insert_mode = false, -- Automatically enter insert mode when opening window and on new prompt + insert_at_end = false, -- Move cursor to end of buffer when inserting text clear_chat_on_new_prompt = false, -- Clears chat on every new prompt highlight_selection = true, -- Highlight selection in the source buffer when in the chat window @@ -306,6 +305,7 @@ Also see [here](/lua/CopilotChat/config.lua): }, yank_diff = { normal = 'gy', + register = '"', }, show_diff = { normal = 'gd' diff --git a/lua/CopilotChat/chat.lua b/lua/CopilotChat/chat.lua index ccdbcaf8..9a494c0f 100644 --- a/lua/CopilotChat/chat.lua +++ b/lua/CopilotChat/chat.lua @@ -32,9 +32,10 @@ function CopilotChatFoldExpr(lnum, separator) return '=' end -local Chat = class(function(self, help, on_buf_create) +local Chat = class(function(self, help, auto_insert, on_buf_create) self.header_ns = vim.api.nvim_create_namespace('copilot-chat-headers') self.help = help + self.auto_insert = auto_insert self.on_buf_create = on_buf_create self.bufnr = nil self.winnr = nil @@ -118,6 +119,10 @@ end function Chat:append(str) self:validate() + if self:active() then + utils.return_to_normal_mode() + end + if self.spinner then self.spinner:start() end @@ -217,11 +222,16 @@ function Chat:close(bufnr) end if self:visible() then + if self:active() then + utils.return_to_normal_mode() + end + if self.layout == 'replace' then self:restore(self.winnr, bufnr) else vim.api.nvim_win_close(self.winnr, true) end + self.winnr = nil end end @@ -229,6 +239,9 @@ end function Chat:focus() if self:visible() then vim.api.nvim_set_current_win(self.winnr) + if self.auto_insert and self:active() then + vim.cmd('startinsert') + end end end @@ -261,6 +274,9 @@ function Chat:finish(msg) end self:show_help(msg, -2) + if self.auto_insert and self:active() then + vim.cmd('startinsert') + end end return Chat diff --git a/lua/CopilotChat/config.lua b/lua/CopilotChat/config.lua index 644c946d..48de280d 100644 --- a/lua/CopilotChat/config.lua +++ b/lua/CopilotChat/config.lua @@ -58,7 +58,6 @@ local select = require('CopilotChat.select') ---@field proxy string? ---@field allow_insecure boolean? ---@field system_prompt string? ----@field yank_diff_register string? ---@field model string? ---@field temperature number? ---@field question_header string? @@ -83,8 +82,6 @@ return { proxy = nil, -- [protocol://]host[:port] Use this proxy allow_insecure = false, -- Allow insecure server connections - yank_diff_register = '"', -- Allows overriding the register for yanking diffs - system_prompt = prompts.COPILOT_INSTRUCTIONS, -- System prompt to use model = 'gpt-4o-2024-05-13', -- GPT model to use, 'gpt-3.5-turbo', 'gpt-4', or `gpt-4o-2024-05-13` temperature = 0.1, -- GPT temperature @@ -97,7 +94,8 @@ return { show_folds = true, -- Shows folds for sections in chat show_help = true, -- Shows help message as virtual lines when waiting for user input auto_follow_cursor = true, -- Auto-follow cursor in chat - auto_insert_mode = false, -- Automatically enter insert mode when opening window and if auto follow cursor is enabled on new prompt + auto_insert_mode = false, -- Automatically enter insert mode when opening window and on new prompt + insert_at_end = false, -- Move cursor to end of buffer when inserting text clear_chat_on_new_prompt = false, -- Clears chat on every new prompt highlight_selection = true, -- Highlight selection @@ -197,7 +195,7 @@ return { zindex = 1, -- determines if window is on top or below other floating windows }, - -- default mappings (in tables first is normal mode, second is insert mode) + -- default mappings mappings = { complete = { detail = 'Use @ or / for options.', @@ -221,6 +219,7 @@ return { }, yank_diff = { normal = 'gy', + register = '"', }, show_diff = { normal = 'gd', diff --git a/lua/CopilotChat/init.lua b/lua/CopilotChat/init.lua index 7198c61c..98d69fc3 100644 --- a/lua/CopilotChat/init.lua +++ b/lua/CopilotChat/init.lua @@ -307,7 +307,7 @@ end --- Open the chat window. ---@param config CopilotChat.config|CopilotChat.config.prompt|nil ---@param source CopilotChat.config.source? -function M.open(config, source, no_insert) +function M.open(config, source) config = vim.tbl_deep_extend('force', M.config, config or {}) local should_reset = state.config and not utils.table_equals(config.window, state.config.window) state.config = config @@ -316,8 +316,7 @@ function M.open(config, source, no_insert) winnr = vim.api.nvim_get_current_win(), }) - vim.cmd('stopinsert') - utils.exit_visual_mode() + utils.return_to_normal_mode() -- Recreate the window if the layout has changed if should_reset then @@ -325,22 +324,12 @@ function M.open(config, source, no_insert) end state.chat:open(config) - state.chat:focus() state.chat:follow() - - if - not no_insert - and not state.copilot:running() - and M.config.auto_insert_mode - and state.chat:active() - then - vim.cmd('startinsert') - end + state.chat:focus() end --- Close the chat window. function M.close() - vim.cmd('stopinsert') state.chat:close(state.source and state.source.bufnr or nil) end @@ -387,10 +376,10 @@ function M.ask(prompt, config, source) return end - M.open(config, source, true) + M.open(config, source) if config.clear_chat_on_new_prompt then - M.stop(true, true) + M.stop(true) end state.last_system_prompt = system_prompt @@ -412,7 +401,6 @@ function M.ask(prompt, config, source) append(updated_prompt) append('\n\n' .. config.answer_header .. config.separator .. '\n\n') - state.chat:follow() local selected_context = config.context if string.find(prompt, '@buffers') then @@ -428,9 +416,6 @@ function M.ask(prompt, config, source) append('```\n' .. err .. '\n```') append('\n\n' .. config.question_header .. config.separator .. '\n\n') state.chat:finish() - if M.config.auto_follow_cursor and M.config.auto_insert_mode and state.chat:active() then - vim.cmd('startinsert') - end end) end @@ -466,9 +451,6 @@ function M.ask(prompt, config, source) if config.callback then config.callback(response, state.source) end - if config.auto_follow_cursor and config.auto_insert_mode and state.chat:active() then - vim.cmd('startinsert') - end end) end, on_progress = function(token) @@ -483,7 +465,7 @@ end --- Stop current copilot output and optionally reset the chat ten show the help message. ---@param reset boolean? -function M.stop(reset, no_insert) +function M.stop(reset) state.response = nil local stopped = reset and state.copilot:reset() or state.copilot:stop() local wrap = vim.schedule @@ -501,11 +483,6 @@ function M.stop(reset, no_insert) end append(M.config.question_header .. M.config.separator .. '\n\n') state.chat:finish() - state.chat:follow() - - if not no_insert and M.config.auto_insert_mode and state.chat:active() then - vim.cmd('startinsert') - end end) end @@ -589,19 +566,29 @@ end function M.setup(config) -- Handle old mapping format and show error local found_old_format = false - if config and config.mappings then - for name, key in pairs(config.mappings) do - if type(key) == 'string' then - vim.notify( - 'config.mappings.' - .. name - .. ": 'mappings' format have changed, please update your configuration, for now revering to default settings. See ':help CopilotChat-configuration' for current format", - vim.log.levels.ERROR - ) - found_old_format = true + if config then + if config.mappings then + for name, key in pairs(config.mappings) do + if type(key) == 'string' then + vim.notify( + 'config.mappings.' + .. name + .. ": 'mappings' format have changed, please update your configuration, for now revering to default settings. See ':help CopilotChat-configuration' for current format", + vim.log.levels.ERROR + ) + found_old_format = true + end end end + + if config.yank_diff_register then + vim.notify( + 'config.yank_diff_register: This option has been removed, please use mappings.yank_diff.register instead', + vim.log.levels.ERROR + ) + end end + if found_old_format then config.mappings = nil end @@ -722,7 +709,7 @@ function M.setup(config) state.chat:close(state.source and state.source.bufnr or nil) state.chat:delete() end - state.chat = Chat(chat_help, function(bufnr) + state.chat = Chat(chat_help, M.config.auto_insert_mode, function(bufnr) map_key(M.config.mappings.complete, bufnr, complete) map_key(M.config.mappings.reset, bufnr, M.reset) map_key(M.config.mappings.close, bufnr, M.close) @@ -775,7 +762,7 @@ function M.setup(config) local lines = find_lines_between_separator(section_lines, '^```%w*$', true) if #lines > 0 then local content = table.concat(lines, '\n') - vim.fn.setreg(M.config.yank_diff_register, content) + vim.fn.setreg(M.config.mappings.yank_diff.register, content) end end) @@ -841,6 +828,17 @@ function M.setup(config) end, }) + if M.config.insert_at_end then + vim.api.nvim_create_autocmd({ 'InsertEnter' }, { + buffer = state.chat.bufnr, + callback = function() + vim.cmd('normal! 0') + vim.cmd('normal! G$') + vim.v.char = 'x' + end, + }) + end + append(M.config.question_header .. M.config.separator .. '\n\n') state.chat:finish() end) diff --git a/lua/CopilotChat/integrations/fzflua.lua b/lua/CopilotChat/integrations/fzflua.lua index ff1f3cfd..2091e71b 100644 --- a/lua/CopilotChat/integrations/fzflua.lua +++ b/lua/CopilotChat/integrations/fzflua.lua @@ -12,7 +12,7 @@ function M.pick(pick_actions, opts) return end - utils.exit_visual_mode() + utils.return_to_normal_mode() opts = vim.tbl_extend('force', { prompt = pick_actions.prompt .. '> ', preview = fzflua.shell.raw_preview_action_cmd(function(items) diff --git a/lua/CopilotChat/integrations/telescope.lua b/lua/CopilotChat/integrations/telescope.lua index 0d733053..e0c4dcee 100644 --- a/lua/CopilotChat/integrations/telescope.lua +++ b/lua/CopilotChat/integrations/telescope.lua @@ -18,7 +18,7 @@ function M.pick(pick_actions, opts) return end - utils.exit_visual_mode() + utils.return_to_normal_mode() opts = themes.get_dropdown(opts or {}) pickers .new(opts, { diff --git a/lua/CopilotChat/utils.lua b/lua/CopilotChat/utils.lua index 8759499a..7074b806 100644 --- a/lua/CopilotChat/utils.lua +++ b/lua/CopilotChat/utils.lua @@ -100,11 +100,14 @@ function M.table_equals(a, b) return true end ---- Exit visual mode if we are in it -function M.exit_visual_mode() - if vim.fn.mode():lower():find('v') then +--- Return to normal mode +function M.return_to_normal_mode() + local mode = vim.fn.mode():lower() + if mode:find('v') then -- NOTE: vim.cmd('normal! v') does not work properly when executed from keymap vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('', true, false, true), 'x', false) + elseif mode:find('i') then + vim.cmd('stopinsert') end end