From bcc93df80185d0dff818e177ce6d8f847193529b Mon Sep 17 00:00:00 2001 From: Tibor Schmidt Date: Sun, 8 Sep 2024 21:23:42 +0200 Subject: [PATCH] feat: chat macro support - starting with @context_file (issue: #206) --- after/ftplugin/gpchat.lua | 28 ++++++++++++ lua/gp/init.lua | 11 ++++- lua/gp/macro.lua | 91 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 2 deletions(-) diff --git a/after/ftplugin/gpchat.lua b/after/ftplugin/gpchat.lua index c09357c..e518890 100644 --- a/after/ftplugin/gpchat.lua +++ b/after/ftplugin/gpchat.lua @@ -151,3 +151,31 @@ vim.api.nvim_create_autocmd({ "User" }, { M.helpers.save_buffer(buf, "gpchat User GpRefresh autocmd") end, }) + +local has_cmp, cmp = pcall(require, "cmp") +if not has_cmp then + M.logger.debug("gpchat: cmp not found, skipping cmp setup") + return +end + +M.macro.build_cmp_source("gpchat", { + require("gp.macros.context_file"), +}) + +local sources = { + { name = "gpchat" }, +} +for _, source in pairs(cmp.get_config().sources) do + if source.name ~= "gpchat" and source.name ~= "buffer" then + table.insert(sources, source) + end +end + +M.logger.debug("gpchat: cmp sources " .. vim.inspect(sources)) + +cmp.setup.buffer({ + -- keyword_length = 1, + max_item_count = 100, + completion = { autocomplete = { require("cmp.types").cmp.TriggerEvent.TextChanged } }, + sources = sources, +}) diff --git a/lua/gp/init.lua b/lua/gp/init.lua index 40f2a92..cb5157b 100644 --- a/lua/gp/init.lua +++ b/lua/gp/init.lua @@ -229,7 +229,9 @@ M.setup = function(opts) require("gp.macros.context_file"), }) - M.logger.debug("command_parser done") + M.chat_parser = M.macro.build_parser({ + require("gp.macros.context_file"), + }) local completions = { ChatNew = { "popup", "split", "vsplit", "tabnew" }, @@ -1035,8 +1037,13 @@ M.chat_respond = function(params) table.insert(messages, 1, { role = "system", content = content }) end - -- strip whitespace from ends of content + local state = M.buffer_state.get(buf) for _, message in ipairs(messages) do + local response = M.chat_parser(message.content, {}, state) + if response then + message.content = M.render.template(response.template, response.artifacts) + state = response.state + end message.content = message.content:gsub("^%s*(.-)%s*$", "%1") end diff --git a/lua/gp/macro.lua b/lua/gp/macro.lua index d0bd157..48a569c 100644 --- a/lua/gp/macro.lua +++ b/lua/gp/macro.lua @@ -153,4 +153,95 @@ M.build_completion = function(macros, raw) return completion end +local registered_cmp_sources = {} +M.build_cmp_source = function(name, macros) + if registered_cmp_sources[name] then + logger.debug("cmp source " .. name .. " already registered") + return nil + end + local source = {} + + source.new = function() + return setmetatable({}, { __index = source }) + end + + source.get_trigger_characters = function() + return { "@", " " } + end + + local completion = M.build_completion(macros, true) + + source.complete = function(self, params, callback) + local ctx = params.context + local suggestions, triggered = completion(ctx.cursor_before_line:match("%S*$"), ctx.cursor_line, ctx.cursor.col) + + if not triggered and not ctx.cursor_before_line:match("%s*@%S*$") then + suggestions = {} + end + + logger.debug("macro completion suggestions: " .. vim.inspect(suggestions)) + + local items = {} + for _, suggestion in ipairs(suggestions) do + table.insert(items, { + label = suggestion, + kind = require("cmp").lsp.CompletionItemKind.Keyword, + documentation = name, + }) + end + logger.debug("macro cmp complete output: " .. vim.inspect(items)) + + callback(items) + end + + local has_cmp, cmp = pcall(require, "cmp") + if not has_cmp then + logger.warning("cmp not found, skipping cmp source registration") + return source + end + + cmp.register_source(name, source) + registered_cmp_sources[name] = true + + if true then + return source + end + + cmp.event:on("complete_done", function(event) + if not event or not event.entry or event.entry.source.name ~= name then + return + end + local ctx = event.entry.source.context + local suggestions, triggered = completion(ctx.cursor_before_line:match("%S*$"), ctx.cursor_line, ctx.cursor.col) + logger.debug( + "macro cmp complete_done suggestions: " .. vim.inspect(suggestions) .. " triggered: " .. vim.inspect(triggered) + ) + if not suggestions or not triggered then + return + end + + vim.schedule(function() + -- insert a space if not already present at the cursor + local cursor_col = vim.api.nvim_win_get_cursor(0)[2] + local line = vim.api.nvim_get_current_line() + logger.debug( + "macro cmp complete_done cursor_col: " + .. cursor_col + .. " line: " + .. line + .. " char: " + .. line:sub(cursor_col, cursor_col) + ) + if line:sub(cursor_col, cursor_col) ~= " " then + vim.api.nvim_put({ " " }, "c", false, true) + end + vim.schedule(function() + cmp.complete(suggestions) + end) + end) + end) + + return source +end + return M