From 8062ea471870b359f117df551ab0192dcecf310b Mon Sep 17 00:00:00 2001 From: Huy Le Date: Sat, 3 Feb 2024 11:08:00 -0700 Subject: [PATCH] making changes to work with edgy --- lua/ogpt.lua | 2 +- lua/ogpt/api.lua | 4 +- lua/ogpt/common/classes.lua | 95 ----- lua/ogpt/common/input_widget.lua | 2 +- lua/ogpt/common/object.lua | 170 +++++++++ lua/ogpt/common/popup.lua | 15 + lua/ogpt/common/simple_parameters.lua | 323 ----------------- lua/ogpt/common/ui/window.lua | 4 +- lua/ogpt/config.lua | 34 +- lua/ogpt/flows/actions/base.lua | 34 +- lua/ogpt/flows/actions/completions/init.lua | 22 +- lua/ogpt/flows/actions/edits/init.lua | 58 +-- lua/ogpt/flows/actions/edits/layouts.lua | 141 -------- lua/ogpt/flows/actions/init.lua | 4 +- lua/ogpt/flows/actions/popup/init.lua | 64 +--- .../actions/popup/window.lua} | 22 +- lua/ogpt/flows/actions/simple_edit/init.lua | 339 ------------------ lua/ogpt/flows/chat/base.lua | 18 +- lua/ogpt/flows/chat/init.lua | 6 +- lua/ogpt/flows/chat/session.lua | 8 +- lua/ogpt/flows/chat/sessions.lua | 10 +- lua/ogpt/flows/chat/system_window.lua | 6 +- lua/ogpt/flows/code_completions/init.lua | 2 +- lua/ogpt/input.lua | 14 +- lua/ogpt/models.lua | 2 +- lua/ogpt/parameters.lua | 10 +- lua/ogpt/prompts.lua | 2 +- lua/ogpt/provider/init.lua | 2 +- lua/ogpt/utils.lua | 2 +- 29 files changed, 335 insertions(+), 1080 deletions(-) delete mode 100644 lua/ogpt/common/classes.lua create mode 100644 lua/ogpt/common/object.lua create mode 100644 lua/ogpt/common/popup.lua delete mode 100644 lua/ogpt/common/simple_parameters.lua delete mode 100644 lua/ogpt/flows/actions/edits/layouts.lua rename lua/ogpt/{common/popup_window.lua => flows/actions/popup/window.lua} (89%) delete mode 100644 lua/ogpt/flows/actions/simple_edit/init.lua diff --git a/lua/ogpt.lua b/lua/ogpt.lua index 1b8e637..e8b7657 100644 --- a/lua/ogpt.lua +++ b/lua/ogpt.lua @@ -59,7 +59,7 @@ function M.select_action(opts) height = 0.5, }, results_title = "Select Ollama action", - prompt_prefix = Config.options.popup_input.prompt, + prompt_prefix = Config.options.input_window.prompt, selection_caret = Config.options.chat.answer_sign .. " ", prompt_title = "actions", finder = finder(action_definitions), diff --git a/lua/ogpt/api.lua b/lua/ogpt/api.lua index 474d659..5aa221f 100644 --- a/lua/ogpt/api.lua +++ b/lua/ogpt/api.lua @@ -1,10 +1,10 @@ local job = require("plenary.job") local Config = require("ogpt.config") local logger = require("ogpt.common.logger") -local classes = require("ogpt.common.classes") +local Object = require("ogpt.common.object") local utils = require("ogpt.utils") -local Api = classes.class() +local Api = Object("Api") function Api:init(provider, action, opts) self.opts = opts diff --git a/lua/ogpt/common/classes.lua b/lua/ogpt/common/classes.lua deleted file mode 100644 index 0a43a62..0000000 --- a/lua/ogpt/common/classes.lua +++ /dev/null @@ -1,95 +0,0 @@ ---- classes.lua --- --- The classes library enables simple OOP constructs using prototypes and meta-tables. --- --- @author Paul Moore --- --- Copyright (C) 2011 by Strange Ideas Software --- --- Permission is hereby granted, free of charge, to any person obtaining a copy --- of this software and associated documentation files (the "Software"), to deal --- in the Software without restriction, including without limitation the rights --- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --- copies of the Software, and to permit persons to whom the Software is --- furnished to do so, subject to the following conditions: --- --- The above copyright notice and this permission notice shall be included in --- all copies or substantial portions of the Software. --- --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN --- THE SOFTWARE. - -local classes = {} - --- Baseclass of all objects. -classes.Object = {} -classes.Object.class = classes.Object ---- Nullary constructor. -function classes.Object:init(...) end ---- Base alloc method. -function classes.Object.alloc(mastertable) - return setmetatable({}, { __index = classes.Object, __newindex = mastertable }) -end ---- Base new method. -function classes.Object.new(...) - return classes.Object.alloc({}):init(...) -end ---- Checks if this object is an instance of class. --- @param class The class object to check. --- @return Returns true if this object is an instance of class, false otherwise. -function classes.Object:instanceOf(class) - -- Recurse up the supertypes until class is found, or until the supertype is not part of the inheritance tree. - if self.class == class then - return true - end - if self.super then - return self.super:instanceOf(class) - end - return false -end - ---- Creates a new class. --- @param baseclass The Baseclass of this class, or nil. --- @return A new class reference. -function classes.class(baseclass) - -- Create the class definition and metatable. - local classdef = {} - -- Find the super class, either Object or user-defined. - baseclass = baseclass or classes.Object - -- If this class definition does not know of a function, it will 'look up' to the Baseclass via the __index of the metatable. - setmetatable(classdef, { __index = baseclass }) - -- All class instances have a reference to the class object. - classdef.class = classdef - --- Recursivly allocates the inheritance tree of the instance. - -- @param mastertable The 'root' of the inheritance tree. - -- @return Returns the instance with the allocated inheritance tree. - function classdef.alloc(mastertable) - -- All class instances have a reference to a superclass object. - local instance = { super = baseclass.alloc(mastertable) } - -- Any functions this instance does not know of will 'look up' to the superclass definition. - setmetatable(instance, { __index = classdef, __newindex = mastertable }) - return instance - end - --- Constructs a new instance from this class definition. - -- @param arg Arguments to this class' constructor - -- @return Returns a new instance of this class. - function classdef.new(...) - -- Create the empty object. - local instance = {} - -- Start the process of creating the inheritance tree. - instance.super = baseclass.alloc(instance) - setmetatable(instance, { __index = classdef }) - -- Finally, init the object, it is up to the programmer to choose to call the super init method. - instance:init(...) - return instance - end - -- Finally, return the class we created. - return classdef -end - -return classes diff --git a/lua/ogpt/common/input_widget.lua b/lua/ogpt/common/input_widget.lua index 95ec849..fbded1b 100644 --- a/lua/ogpt/common/input_widget.lua +++ b/lua/ogpt/common/input_widget.lua @@ -20,7 +20,7 @@ return function(name, on_submit) winhighlight = "Normal:Normal,FloatBorder:FloatBorder", }, }, { - prompt = Config.options.popup_input.prompt, + prompt = Config.options.input_window.prompt, on_submit = on_submit, }) diff --git a/lua/ogpt/common/object.lua b/lua/ogpt/common/object.lua new file mode 100644 index 0000000..716a2e4 --- /dev/null +++ b/lua/ogpt/common/object.lua @@ -0,0 +1,170 @@ +-- source: https://github.com/kikito/middleclass + +local idx = { + subclasses = { "" }, +} + +local function __tostring(self) + return "class " .. self.name +end + +local function __call(self, ...) + return self:new(...) +end + +local function create_index_wrapper(class, index) + if type(index) == "table" then + return function(self, key) + local value = self.class.__meta[key] + if value == nil then + return index[key] + end + return value + end + elseif type(index) == "function" then + return function(self, key) + local value = self.class.__meta[key] + if value == nil then + return index(self, key) + end + return value + end + else + return class.__meta + end +end + +local function propagate_instance_property(class, key, value) + value = key == "__index" and create_index_wrapper(class, value) or value + + class.__meta[key] = value + + for subclass in pairs(class[idx.subclasses]) do + if subclass.__properties[key] == nil then + propagate_instance_property(subclass, key, value) + end + end +end + +local function declare_instance_property(class, key, value) + class.__properties[key] = value + + if value == nil and class.super then + value = class.super.__meta[key] + end + + propagate_instance_property(class, key, value) +end + +local function is_subclass(subclass, class) + if not subclass.super then + return false + end + if subclass.super == class then + return true + end + return is_subclass(subclass.super, class) +end + +local function is_instance(instance, class) + if instance.class == class then + return true + end + return is_subclass(instance.class, class) +end + +local function create_class(name, super) + assert(name, "missing name") + + local meta = { + is_instance_of = is_instance, + } + meta.__index = meta + + local class = { + super = super, + name = name, + static = { + is_subclass_of = is_subclass, + }, + + [idx.subclasses] = setmetatable({}, { __mode = "k" }), + + __meta = meta, + __properties = {}, + } + + setmetatable(class.static, { + __index = function(_, key) + local value = rawget(class.__meta, key) + if value == nil and super then + return super.static[key] + end + return value + end, + }) + + setmetatable(class, { + __call = __call, + __index = class.static, + __name = class.name, + __newindex = declare_instance_property, + __tostring = __tostring, + }) + + return class +end + +---@param name string +local function create_object(_, name) + local Class = create_class(name) + + ---@return string + function Class:__tostring() + return "instance of " .. tostring(self.class) + end + + ---@return nil + function Class:init() end -- luacheck: no unused args + + function Class.static:new(...) + local instance = setmetatable({ class = self }, self.__meta) + instance:init(...) + return instance + end + + ---@param name string + function Class.static:extend(name) -- luacheck: no redefined + local subclass = create_class(name, self) + + for key, value in pairs(self.__meta) do + if not (key == "__index" and type(value) == "table") then + propagate_instance_property(subclass, key, value) + end + end + + function subclass.init(instance, ...) + self.init(instance, ...) + end + + self[idx.subclasses][subclass] = true + + return subclass + end + + return Class +end + +--luacheck: push no max line length + +---@type (fun(name: string): table)|{ is_subclass: (fun(subclass: table, class: table): boolean), is_instance: (fun(instance: table, class: table): boolean) } +local Object = setmetatable({ + is_subclass = is_subclass, + is_instance = is_instance, +}, { + __call = create_object, +}) + +--luacheck: pop + +return Object diff --git a/lua/ogpt/common/popup.lua b/lua/ogpt/common/popup.lua new file mode 100644 index 0000000..931809c --- /dev/null +++ b/lua/ogpt/common/popup.lua @@ -0,0 +1,15 @@ +local NuiPopup = require("nui.popup") + +Popup = NuiPopup:extend("OgptPopup") + +function Popup:init(options, edgy) + options = options or {} + self.edgy = false + if options.edgy and options.border or edgy then + self.edgy = true + options.border = nil + end + Popup.super.init(self, options) +end + +return Popup diff --git a/lua/ogpt/common/simple_parameters.lua b/lua/ogpt/common/simple_parameters.lua deleted file mode 100644 index d2e7467..0000000 --- a/lua/ogpt/common/simple_parameters.lua +++ /dev/null @@ -1,323 +0,0 @@ -local pickers = require("telescope.pickers") -local SimpleWindow = require("ogpt.common.ui.window") -local utils = require("ogpt.utils") -local conf = require("telescope.config").values -local actions = require("telescope.actions") -local action_state = require("telescope.actions.state") - -local M = {} -M.vts = {} - -local Config = require("ogpt.config") - -local namespace_id = vim.api.nvim_create_namespace("OGPTNS") - -local float_validator = function(min, max) - return function(value) - return tonumber(value) - end -end - -local bool_validator = function(min, max) - return function(value) - local stringtoboolean = { ["true"] = true, ["false"] = false } - return stringtoboolean(value) - end -end - -local integer_validator = function(min, max) - return function(value) - return tonumber(value) - end -end - -local model_validator = function() - return function(value) - return value - end -end - -local function preview_command(entry, bufnr, width) - vim.api.nvim_buf_call(bufnr, function() - local preview = utils.wrapTextToTable(entry.value, width - 5) - table.insert(preview, 1, "---") - table.insert(preview, 1, entry.display) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, preview) - end) -end - -local finder = function(opts) - return setmetatable({ - close = function() - -- TODO: check if we need to make some cleanup - end, - }, { - __call = function(_, prompt, process_result, process_complete) - local _params = { - values = opts.parameters, - } - for _, param in ipairs(_params.values) do - process_result({ - value = param, - display = param, - ordinal = param, - preview_command = preview_command, - }) - end - process_complete() - end, - }) -end - -M.params_order = { - "provider", - "model", - "embedding_only", - "f16_kv", - "frequency_penalty", - "logits_all", - "low_vram", - "main_gpu", - "max_tokens", - "mirostat", - "mirostat_eta", - "mirostat_tau", - "num_batch", - "num_ctx", - "num_gpu", - "num_gqa", - "num_keep", - "num_predict", - "num_thread", - "presence_penalty", - "repeat_last_n", - "repeat_penalty", - "rope_frequency_base", - "rope_frequency_scale", - "seed", - "stop", - "temperature", - "tfs_z", - "top_k", - "top_p", - "typical_p", - "use_mlock", - "use_mmap", - "vocab_only", -} -local params_validators = { - provider = model_validator(), - model = model_validator(), - embedding_only = model_validator(), - f16_kv = model_validator(), - frequency_penalty = float_validator(), - mirostat = integer_validator(), - mirostat_eta = float_validator(), - mirostat_tau = float_validator(), - num_batch = integer_validator(), - num_ctx = integer_validator(), - num_gpu = integer_validator(), - num_gqa = integer_validator(), - num_keep = integer_validator(), - num_predict = integer_validator(), - num_thread = integer_validator(), - presence_penalty = float_validator(), - repeat_last_n = integer_validator(), - repeat_penalty = float_validator(), - seed = integer_validator(), - stop = model_validator(), - temperature = float_validator(), - tfs_z = float_validator(), - top_k = float_validator(), - top_p = float_validator(), - logits_all = bool_validator(), - vocab_only = bool_validator(), - use_mmap = bool_validator(), - use_mlock = bool_validator(), - low_vram = bool_validator(), -} - -local function write_virtual_text(bufnr, ns, line, chunks, mode) - mode = mode or "extmark" - if mode == "extmark" then - return vim.api.nvim_buf_set_extmark(bufnr, ns, line, 0, { virt_text = chunks, virt_text_pos = "overlay" }) - elseif mode == "vt" then - pcall(vim.api.nvim_buf_set_virtual_text, bufnr, ns, line, chunks, {}) - end -end - -function M.select_parameter(opts) - opts = opts or {} - pickers - .new(opts, { - sorting_strategy = "ascending", - layout_config = { - height = 0.5, - }, - results_title = "Select Additional Parameter", - prompt_prefix = Config.options.popup_input.prompt, - selection_caret = Config.options.chat.answer_sign .. " ", - prompt_title = "Parameter", - finder = finder({ - parameters = M.params_order, - }), - sorter = conf.generic_sorter(opts), - attach_mappings = function(prompt_bufnr) - actions.select_default:replace(function() - actions.close(prompt_bufnr) - local selection = action_state.get_selected_entry() - opts.cb(selection.display, vim.fn.input("value: ")) - end) - return true - end, - }) - :find() -end - -M.read_config = function(session) - if not session then - local home = os.getenv("HOME") or os.getenv("USERPROFILE") - local file = io.open(home .. "/" .. ".ogpt-" .. M.type .. "-params.json", "rb") - if not file then - return nil - end - - local jsonString = file:read("*a") - file:close() - return vim.json.decode(jsonString) - else - return session.parameters - end -end - -M.write_config = function(config, session) - if not session then - local home = os.getenv("HOME") or os.getenv("USERPROFILE") - local file, err = io.open(home .. "/" .. ".ogpt-" .. M.type .. "-params.json", "w") - if file ~= nil then - local json_string = vim.json.encode(config) - file:write(json_string) - file:close() - else - vim.notify("Cannot save parameters: " .. err, vim.log.levels.ERROR) - end - else - session.parameters = config - session:save() - end -end - -M.refresh_panel = function() - -- write details as virtual text - local details = {} - for _, key in pairs(M.params_order) do - if M.params[key] ~= nil then - local display_text = M.params[key] - if type(display_text) == "table" then - if display_text.name then - display_text = display_text.name - else - display_text = table.concat(M.params[key], ", ") - end - end - - local vt = { - { Config.options.parameters_window.setting_sign .. key .. ": ", "ErrorMsg" }, - { display_text .. "", "Identifier" }, - } - table.insert(details, vt) - end - end - - vim.api.nvim_buf_clear_namespace(M.panel.bufnr, namespace_id, 0, -1) - - local line = 1 - local empty_lines = {} - for _ = 1, #details do - table.insert(empty_lines, "") - end - - vim.api.nvim_buf_set_option(M.panel.bufnr, "modifiable", true) - vim.api.nvim_buf_set_lines(M.panel.bufnr, line - 1, line - 1 + #empty_lines, false, empty_lines) - vim.api.nvim_buf_set_option(M.panel.bufnr, "modifiable", false) - for _, d in ipairs(details) do - M.vts[line - 1] = write_virtual_text(M.panel.bufnr, namespace_id, line - 1, d) - line = line + 1 - end -end - -M.get_parameters_panel = function(type, default_params, session, parent) - M.type = type - M.name = "ogpt_parameters" - local custom_params = M.read_config(session or {}) - - M.params = vim.tbl_deep_extend("force", {}, default_params, custom_params or {}) - if session then - M.params = session.parameters - end - - -- M.panel = Popup(Config.options.parameters_window) - M.panel = SimpleWindow.new(M.name, { params = M.params }) - -- M.panel:mount() - -- - -- vim.api.nvim_buf_set_option(M.panel.bufnr, "modifiable", true) - -- - -- M.refresh_panel() - - return M.panel -end - -M.update_property = function(key, row, new_value, session) - if not key or not new_value then - M.params[key] = nil - vim.api.nvim_buf_set_option(M.panel.bufnr, "modifiable", true) - vim.api.nvim_del_current_line() - vim.api.nvim_buf_set_option(M.panel.bufnr, "modifiable", false) - else - M.params[key] = params_validators[key](new_value) - end - M.write_config(M.params, session) - M.refresh_panel() -end - -M.get_panel = function(session, parent) - return M.get_parameters_panel(" ", session.parameters or {}, session, parent) -end - -M.open_edit_property_input = function(key, value, row, cb) - -- convert table to string first - if type(value) == "table" then - value = table.concat(value, ", ") - end - - local Input = require("nui.input") - - local input = Input({ - relative = { - type = "win", - winid = M.panel.winid, - }, - position = { - row = row - 1, - col = 0, - }, - size = { - width = 38, - }, - border = { - style = "none", - }, - win_options = { - winhighlight = "Normal:Normal,FloatBorder:Normal", - }, - }, { - prompt = Config.options.popup_input.prompt .. key .. ": ", - default_value = "" .. value, - on_submit = cb, - }) - - -- mount/open the component - input:mount() -end - -return M diff --git a/lua/ogpt/common/ui/window.lua b/lua/ogpt/common/ui/window.lua index f48a5e4..19b60bd 100644 --- a/lua/ogpt/common/ui/window.lua +++ b/lua/ogpt/common/ui/window.lua @@ -1,7 +1,7 @@ -local classes = require("ogpt.common.classes") +local Object = require("ogpt.common.object") local utils = require("ogpt.utils") -local SimpleView = classes.class() +local SimpleView = Object("SimpleView") function SimpleView:init(name, opts) local _defaults = { diff --git a/lua/ogpt/config.lua b/lua/ogpt/config.lua index 16acbf3..2fe0651 100644 --- a/lua/ogpt/config.lua +++ b/lua/ogpt/config.lua @@ -115,6 +115,7 @@ function M.defaults() edit = { layout = "default", + edgy = false, diff = false, keymaps = { close = "", @@ -126,6 +127,7 @@ function M.defaults() }, }, popup = { + edgy = false, position = 1, size = { width = "40%", @@ -141,7 +143,8 @@ function M.defaults() buf_options = { modifiable = false, readonly = false, - filetype = "markdown", + filetype = "ogpt-popup", + syntax = "markdown", }, win_options = { wrap = true, @@ -166,6 +169,7 @@ function M.defaults() border_left_sign = "|", border_right_sign = "|", max_line_length = 120, + edgy = false, sessions_window = { active_sign = " 󰄵 ", inactive_sign = " 󰄱 ", @@ -179,18 +183,21 @@ function M.defaults() win_options = { winhighlight = "Normal:Normal,FloatBorder:FloatBorder", }, + buf_options = { + filetype = "ogpt-sessions", + }, }, keymaps = { close = { "" }, yank_last = "", - yank_last_code = "", + yank_last_code = "", scroll_up = "", scroll_down = "", new_session = "", cycle_windows = "", cycle_modes = "", - next_message = "", - prev_message = "", + next_message = "J", + prev_message = "K", select_session = "", rename_session = "r", delete_session = "d", @@ -214,7 +221,7 @@ function M.defaults() width_parameters_open = "50%", }, }, - popup_window = { + output_window = { border = { highlight = "FloatBorder", style = "rounded", @@ -229,7 +236,8 @@ function M.defaults() winhighlight = "Normal:Normal,FloatBorder:FloatBorder", }, buf_options = { - filetype = "markdown", + filetype = "ogpt-window", + syntax = "markdown", }, }, system_window = { @@ -246,8 +254,11 @@ function M.defaults() foldcolumn = "2", winhighlight = "Normal:Normal,FloatBorder:FloatBorder", }, + buf_options = { + filetype = "ogpt-system-window", + }, }, - popup_input = { + input_window = { prompt = "  ", border = { highlight = "FloatBorder", @@ -260,6 +271,9 @@ function M.defaults() win_options = { winhighlight = "Normal:Normal,FloatBorder:FloatBorder", }, + buf_options = { + filetype = "ogpt-input", + }, submit = "", submit_n = "", max_visible_lines = 20, @@ -275,6 +289,9 @@ function M.defaults() win_options = { winhighlight = "Normal:Normal,FloatBorder:FloatBorder", }, + buf_options = { + filetype = "ogpt-parameters-window", + }, }, actions = { -- all strategy "edit" have instruction as input @@ -348,7 +365,7 @@ function M.get_provider(provider_name, action, override) local envs = provider.load_envs(override.envs) provider = vim.tbl_extend("force", provider, override) provider.envs = envs - provider.api = Api.new(provider, action, {}) + provider.api = Api(provider, action, {}) return provider end @@ -356,6 +373,7 @@ function M.get_action_params(provider, override) provider = provider or M.options.default_provider local default_params = M.options.providers[provider].api_params default_params.model = default_params.model or M.options.providers[provider].model + default_params.provider = provider return vim.tbl_extend("force", default_params, override or {}) end diff --git a/lua/ogpt/flows/actions/base.lua b/lua/ogpt/flows/actions/base.lua index 2c761b1..07ca17e 100644 --- a/lua/ogpt/flows/actions/base.lua +++ b/lua/ogpt/flows/actions/base.lua @@ -1,11 +1,10 @@ -local classes = require("ogpt.common.classes") +local Object = require("ogpt.common.object") local Signs = require("ogpt.signs") local Spinner = require("ogpt.spinner") local utils = require("ogpt.utils") local Config = require("ogpt.config") -local PopupWindow = require("ogpt.common.popup_window") -local BaseAction = classes.class() +local BaseAction = Object("BaseAction") local namespace_id = vim.api.nvim_create_namespace("OGPTNS") @@ -20,17 +19,8 @@ end function BaseAction:init(opts) self.opts = opts self.stop = true -end - -function BaseAction:post_init() - self.popup = PopupWindow() - self.spinner = Spinner:new(function(state) - -- vim.schedule(function() - -- self:display_input_suffix(state) - -- end) - end) - - self:update_variables() + self.output_panel = nil + self.spinner = Spinner:new(function(state) end) end function BaseAction:get_bufnr() @@ -164,7 +154,9 @@ function BaseAction:run_spinner(flag) self.spinner:start() else self.spinner:stop() - self:set_lines(self.popup.bufnr, 0, -1, false, {}) + if self.output_panel then + self:set_lines(self.output_panel.bufnr, 0, -1, false, {}) + end end end @@ -177,12 +169,16 @@ function BaseAction:set_lines(bufnr, start_idx, end_idx, strict_indexing, lines) end function BaseAction:display_input_suffix(suffix) - if self.stop and self.extmark_id and utils.is_buf_exists(self.popup.bufnr) then - vim.api.nvim_buf_del_extmark(self.popup.bufnr, Config.namespace_id, self.extmark_id) + if not self.output_panel then + return + end + + if self.stop and self.extmark_id and utils.is_buf_exists(self.output_panel.bufnr) then + vim.api.nvim_buf_del_extmark(self.output_panel.bufnr, Config.namespace_id, self.extmark_id) end - if not self.stop and suffix and vim.fn.bufexists(self.popup.bufnr) then - self.extmark_id = vim.api.nvim_buf_set_extmark(self.popup.bufnr, Config.namespace_id, 0, -1, { + if not self.stop and suffix and vim.fn.bufexists(self.output_panel.bufnr) then + self.extmark_id = vim.api.nvim_buf_set_extmark(self.output_panel.bufnr, Config.namespace_id, 0, -1, { virt_text = { { Config.options.chat.border_left_sign, "OGPTTotalTokensBorder" }, { "" .. suffix, "OGPTTotalTokens" }, diff --git a/lua/ogpt/flows/actions/completions/init.lua b/lua/ogpt/flows/actions/completions/init.lua index 0966458..2f64614 100644 --- a/lua/ogpt/flows/actions/completions/init.lua +++ b/lua/ogpt/flows/actions/completions/init.lua @@ -1,25 +1,9 @@ -local classes = require("ogpt.common.classes") local BaseAction = require("ogpt.flows.actions.base") local Api = require("ogpt.api") local Utils = require("ogpt.utils") local Config = require("ogpt.config") --- curl code to insert code between prompt and suffix --- curl https://api.openai.com/v1/completions \ --- -H "Content-Type: application/json" \ --- -H "Authorization: Bearer $OLLAMA_API_KEY" \ --- -d '{ --- "model": "text-davinci-003", --- "prompt": "Insert a roxygen skeleton to document this R function:\n\n", --- "suffix": " code ", --- "temperature": 0.7, --- "max_tokens": 565, --- "top_p": 1, --- "frequency_penalty": 0, --- "presence_penalty": 0 --- }' - -local CompletionAction = classes.class(BaseAction) +local CompletionAction = BaseAction("CompletionAction") local STRATEGY_REPLACE = "replace" local STRATEGY_APPEND = "append" @@ -27,7 +11,7 @@ local STRATEGY_PREPEND = "prepend" local STRATEGY_DISPLAY = "display" function CompletionAction:init(opts) - self.super:init(opts) + CompletionAction.super.init(self, opts) self.params = opts.params or {} self.template = opts.template or "{{input}}" self.variables = opts.variables or {} @@ -88,7 +72,7 @@ function CompletionAction:on_result(answer, usage) if self.strategy ~= STRATEGY_DISPLAY then vim.api.nvim_buf_set_text(bufnr, start_row - 1, start_col - 1, end_row - 1, end_col, lines) else - local Popup = require("nui.popup") + local Popup = require("ogpt.common.popup") local ui = vim.tbl_deep_extend("keep", self.ui, { position = 1, size = { diff --git a/lua/ogpt/flows/actions/edits/init.lua b/lua/ogpt/flows/actions/edits/init.lua index 624687a..80b58f7 100644 --- a/lua/ogpt/flows/actions/edits/init.lua +++ b/lua/ogpt/flows/actions/edits/init.lua @@ -1,30 +1,35 @@ -local classes = require("ogpt.common.classes") local BaseAction = require("ogpt.flows.actions.base") +local Spinner = require("ogpt.spinner") +local PopupWindow = require("ogpt.flows.actions.popup.window") local utils = require("ogpt.utils") local Config = require("ogpt.config") local Layout = require("nui.layout") -local Split = require("nui.split") -local Popup = require("nui.popup") +local Popup = require("ogpt.common.popup") local ChatInput = require("ogpt.input") local Parameters = require("ogpt.parameters") -local EditAction = classes.class(BaseAction) +local EditAction = BaseAction:extend("EditAction") local STRATEGY_EDIT = "edit" local STRATEGY_EDIT_CODE = "edit_code" +local instructions_input, layout, input_window, output_window, output, timer, filetype, bufnr, extmark_id + function EditAction:init(name, opts) self.name = name or "" opts = opts or {} - self.super:init(opts) + EditAction.super.init(self, opts) self.provider = Config.get_provider(opts.provider, self) self.params = Config.get_action_params(self.provider.name, opts.params or {}) self.system = type(opts.system) == "function" and opts.system() or opts.system or "" self.template = type(opts.template) == "function" and opts.template() or opts.template or "{{input}}" self.variables = opts.variables or {} self.strategy = opts.strategy or STRATEGY_EDIT + self.edgy = Config.options.edit.edgy + self.ui = opts.ui or {} - self:post_init() + + self:update_variables() end function EditAction:run() @@ -60,8 +65,6 @@ function EditAction:run() end) end -local instructions_input, layout, input_window, output_window, output, timer, filetype, bufnr, extmark_id - local setup_and_mount = vim.schedule_wrap(function(lines, output_lines, ...) layout:mount() -- set input @@ -96,17 +99,20 @@ function EditAction:edit_with_instructions(output_lines, selection, opts, ...) else visual_lines, start_row, start_col, end_row, end_col = unpack(selection) end - local parameters_panel = Parameters.get_parameters_panel("edits", api_params) - input_window = Popup(Config.options.popup_window) - output_window = Popup(Config.options.popup_window) - instructions_input = ChatInput(Config.options.popup_input, { - prompt = Config.options.popup_input.prompt, + local parameters_panel = Parameters.get_parameters_panel("edits", api_params, nil, self) + input_window = Popup(Config.options.input_window, Config.options.edit.edgy) + + output_window = Popup(Config.options.output_window, Config.options.edit.edgy) + self.output_panel = output_window + instructions_input = ChatInput(Config.options.input_window, { + edgy = Config.options.edit.edgy, + prompt = Config.options.input_window.prompt, default_value = opts.instruction or "", on_close = function() -- if self.spinner:is_running() then -- self.spinner:stop() -- end - self:run_spinner(false) + self:run_spinner(instructions_input, false) if timer ~= nil then timer:stop() end @@ -116,7 +122,7 @@ function EditAction:edit_with_instructions(output_lines, selection, opts, ...) vim.api.nvim_buf_set_lines(instructions_input.bufnr, 0, -1, false, { "" }) vim.api.nvim_buf_set_lines(output_window.bufnr, 0, -1, false, { "" }) -- show_progress() - self:run_spinner(true) + self:run_spinner(instructions_input, true) local input = table.concat(vim.api.nvim_buf_get_lines(input_window.bufnr, 0, -1, false), "\n") @@ -161,22 +167,15 @@ function EditAction:edit_with_instructions(output_lines, selection, opts, ...) end), }) - local _layout - if Config.options.edit.layout == "default" then - _layout = { + layout = Layout( + { relative = "editor", position = "50%", size = { width = Config.options.popup_layout.center.width, height = Config.options.popup_layout.center.height, }, - } - else - _layout = Split(Config.options.edit.layout) - end - - layout = Layout( - _layout, + }, Layout.Box({ Layout.Box({ @@ -258,7 +257,7 @@ function EditAction:edit_with_instructions(output_lines, selection, opts, ...) -- set input and output settings -- TODO for _, window in ipairs({ input_window, output_window }) do - vim.api.nvim_buf_set_option(window.bufnr, "filetype", filetype) + vim.api.nvim_buf_set_option(window.bufnr, "syntax", filetype) vim.api.nvim_win_set_option(window.winid, "number", true) end end, {}) @@ -317,6 +316,13 @@ function EditAction:edit_with_instructions(output_lines, selection, opts, ...) end end + -- set events + for _, popup in ipairs({ parameters_panel, instructions_input, output_window, input_window }) do + popup:on({ "BufUnload" }, function() + self:set_loading(false) + end) + end + setup_and_mount(visual_lines, output_lines) end diff --git a/lua/ogpt/flows/actions/edits/layouts.lua b/lua/ogpt/flows/actions/edits/layouts.lua deleted file mode 100644 index d7be6c3..0000000 --- a/lua/ogpt/flows/actions/edits/layouts.lua +++ /dev/null @@ -1,141 +0,0 @@ -local Config = require("ogpt.config") -local utils = require("ogpt.utils") -local SimpleParameters = require("ogpt.common.simple_parameters") -local Layout = require("nui.layout") - -local M = {} - -M.edit_with_nui_layout = function(layout, parent, input, instruction, output, parameters, opts) - opts = opts or {} - local _boxes - if opts.show_parameters then - _boxes = Layout.Box({ - Layout.Box({ - Layout.Box(input, { grow = 1 }), - Layout.Box(instruction, { size = 3 }), - }, { dir = "col", grow = 1 }), - Layout.Box(output, { grow = 1 }), - Layout.Box(parameters, { size = 40 }), - }, { dir = "row" }) - else - _boxes = Layout.Box({ - Layout.Box({ - Layout.Box(input, { grow = 1 }), - Layout.Box(instruction, { size = 3 }), - }, { dir = "col", size = "50%" }), - Layout.Box(output, { size = "50%" }), - }, { dir = "row" }) - end - - if not layout then - layout = Layout({ - relative = "editor", - position = "50%", - size = { - width = Config.options.popup_layout.center.width, - height = Config.options.popup_layout.center.height, - }, - }, _boxes) - else - layout:update(_boxes) - end - - layout:mount() - - if opts.show_parameters then - parameters:show() - parameters:mount() - - vim.api.nvim_set_current_win(parameters.winid) - vim.api.nvim_buf_set_option(parameters.bufnr, "modifiable", false) - vim.api.nvim_win_set_option(parameters.winid, "cursorline", true) - else - parameters:hide() - vim.api.nvim_set_current_win(instruction.winid) - end - - return layout -end - -M.edit_with_no_layout = function(layout, parent, input, instruction, output, parameters, opts) - opts = opts or {} - - vim.schedule_wrap(input:mount()) - vim.schedule_wrap(output:mount()) - vim.schedule_wrap(instruction:mount()) - - if opts.show_parameters then - parameters:mount() - - vim.api.nvim_set_current_win(parameters.winid) - vim.api.nvim_buf_set_option(parameters.bufnr, "modifiable", false) - vim.api.nvim_win_set_option(parameters.winid, "cursorline", true) - - parameters:map("n", "d", function() - local row, _ = unpack(vim.api.nvim_win_get_cursor(parameters.winid)) - - local existing_order = {} - for _, key in ipairs(SimpleParameters.params_order) do - if SimpleParameters.params[key] ~= nil then - table.insert(existing_order, key) - end - end - - local key = existing_order[row] - SimpleParameters.update_property(key, row, nil) - SimpleParameters.refresh_panel() - end) - - parameters:map("n", "a", function() - local row, _ = unpack(vim.api.nvim_win_get_cursor(parameters.winid)) - SimpleParameters.select_parameter({ - cb = function(key, value) - SimpleParameters.update_property(key, row + 1, value) - end, - }) - end) - - parameters:map("n", "", function() - local row, _ = unpack(vim.api.nvim_win_get_cursor(parameters.winid)) - - local existing_order = {} - for _, key in ipairs(SimpleParameters.params_order) do - if SimpleParameters.params[key] ~= nil then - table.insert(existing_order, key) - end - end - - local key = existing_order[row] - if key == "model" then - local models = require("ogpt.models") - models.select_model(parent.provider, { - cb = function(display, value) - SimpleParameters.update_property(key, row, value) - end, - }) - elseif key == "provider" then - local provider = require("ogpt.provider") - provider.select_provider({ - cb = function(display, value) - SimpleParameters.update_property(key, row, value) - parent.provider = Config.get_provider(value) - end, - }) - else - local value = SimpleParameters.params[key] - SimpleParameters.open_edit_property_input(key, value, row, function(new_value) - SimpleParameters.update_property(key, row, utils.process_string(new_value)) - end) - end - end, {}) - - SimpleParameters.refresh_panel() - else - parameters:hide() - -- vim.api.nvim_set_current_win(instruction.winid) - end - - return layout -end - -return M diff --git a/lua/ogpt/flows/actions/init.lua b/lua/ogpt/flows/actions/init.lua index 539a6d9..d8c28f2 100644 --- a/lua/ogpt/flows/actions/init.lua +++ b/lua/ogpt/flows/actions/init.lua @@ -2,7 +2,6 @@ local M = {} -- local CompletionAction = require("ogpt.flows.actions.completions") local EditAction = require("ogpt.flows.actions.edits") -local EditSimpleAction = require("ogpt.flows.actions.simple_edit") local PopupAction = require("ogpt.flows.actions.popup") local Config = require("ogpt.config") @@ -10,7 +9,6 @@ local classes_by_type = { chat = PopupAction, -- completion = CompletionAction, edit = EditAction, - simple_edit = EditSimpleAction, popup = PopupAction, } @@ -74,7 +72,7 @@ function M.run_action(opts) opts = vim.tbl_extend("force", {}, action_opts, item) local class = classes_by_type[item.type] - local action = class.new(action_name, opts) + local action = class(action_name, opts) action:run() end diff --git a/lua/ogpt/flows/actions/popup/init.lua b/lua/ogpt/flows/actions/popup/init.lua index 0eaa6d2..7b04701 100644 --- a/lua/ogpt/flows/actions/popup/init.lua +++ b/lua/ogpt/flows/actions/popup/init.lua @@ -1,23 +1,20 @@ -local classes = require("ogpt.common.classes") local BaseAction = require("ogpt.flows.actions.base") +local Spinner = require("ogpt.spinner") +local PopupWindow = require("ogpt.flows.actions.popup.window") local utils = require("ogpt.utils") local Config = require("ogpt.config") -local SimpleWindow = require("ogpt.common.ui.window") -local popup_keymap = require("ogpt.flows.actions.popup.keymaps") -local PopupAction = classes.class(BaseAction) +local PopupAction = BaseAction:extend("PopupAction") local STRATEGY_REPLACE = "replace" local STRATEGY_APPEND = "append" local STRATEGY_PREPEND = "prepend" local STRATEGY_DISPLAY = "display" -local STRATEGY_DISPLAY_WINDOW = "display_window" -local STRATEGY_NEW_DISPLAY_WINDOW = "new_display_window" local STRATEGY_QUICK_FIX = "quick_fix" function PopupAction:init(name, opts) self.name = name or "" - self.super:init(opts) + PopupAction.super.init(self, opts) self.provider = Config.get_provider(opts.provider, self) self.params = Config.get_action_params(self.provider.name, opts.params or {}) self.system = type(opts.system) == "function" and opts.system() or opts.system or "" @@ -26,8 +23,15 @@ function PopupAction:init(name, opts) self.strategy = opts.strategy or STRATEGY_DISPLAY self.ui = opts.ui or {} self.cur_win = vim.api.nvim_get_current_win() + self.edgy = Config.options.popup.edgy + self.popup = PopupWindow(Config.options.popup, Config.options.popup.edgy) + self.spinner = Spinner:new(function(state) end) - self:post_init() + self:update_variables() + + self.popup:on({ "BufUnload" }, function() + self:set_loading(false) + end) end function PopupAction:run() @@ -79,50 +83,6 @@ function PopupAction:run() end end ) - elseif self.strategy == STRATEGY_DISPLAY_WINDOW or self.strategy == STRATEGY_NEW_DISPLAY_WINDOW then - self.popup = SimpleWindow.new("ogpt_popup", { - new_win = false, - buf = { - syntax = "markdown", - }, - events = { - -- { - -- events = { "BufUnload" }, - -- callback = function() - -- opts.stop() - -- end, - -- }, - }, - }) - popup_keymap.apply_map(self.popup, opts) - - self:set_loading(true) - if self.strategy == STRATEGY_NEW_DISPLAY_WINDOW then - self.popup:mount(self.name) - else - self.popup:mount() - end - - params.stream = true - - self.provider.api:chat_completions( - params, - utils.partial(utils.add_partial_completion, { - panel = self.popup, - progress = function(flag) - self:run_spinner(flag) - end, - }), - function() - -- should stop function - if self.stop then - self:set_loading(false) - return true - else - return false - end - end - ) else self:set_loading(true) self.provider.api:chat_completions(params, function(answer, usage) diff --git a/lua/ogpt/common/popup_window.lua b/lua/ogpt/flows/actions/popup/window.lua similarity index 89% rename from lua/ogpt/common/popup_window.lua rename to lua/ogpt/flows/actions/popup/window.lua index 36b9471..5f55a05 100644 --- a/lua/ogpt/common/popup_window.lua +++ b/lua/ogpt/flows/actions/popup/window.lua @@ -1,20 +1,24 @@ -local Popup = require("nui.popup") +local Popup = require("ogpt.common.popup") local Config = require("ogpt.config") local event = require("nui.utils.autocmd").event local Utils = require("ogpt.utils") local PopupWindow = Popup:extend("PopupWindow") -function PopupWindow:init(options) +function PopupWindow:init(options, edgy) options = vim.tbl_deep_extend("keep", options or {}, Config.options.popup) + self.options = options - PopupWindow.super.init(self, options) + PopupWindow.super.init(self, options, self.edgy) end function PopupWindow:update_popup_size(opts) - opts.lines = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) - local ui_opts = self:calculate_size(opts) - self:update_layout(ui_opts) + opts = vim.tbl_extend("force", self.options, opts or {}) + if not self.options.edgy then + opts.lines = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) + local ui_opts = self:calculate_size(opts) + self:update_layout(ui_opts) + end end function PopupWindow:calculate_size(opts) @@ -92,14 +96,16 @@ function PopupWindow:mount(opts) -- accept output and replace self:map("n", Config.options.popup.keymaps.accept, function() - -- local _lines = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) + local _lines = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) + table.insert(_lines, "") + table.insert(_lines, "") vim.api.nvim_buf_set_text( opts.main_bufnr, opts.selection_idx.start_row - 1, opts.selection_idx.start_col - 1, opts.selection_idx.end_row - 1, opts.selection_idx.end_col, - opts.lines + _lines ) vim.cmd("q") end) diff --git a/lua/ogpt/flows/actions/simple_edit/init.lua b/lua/ogpt/flows/actions/simple_edit/init.lua deleted file mode 100644 index c2ff9c2..0000000 --- a/lua/ogpt/flows/actions/simple_edit/init.lua +++ /dev/null @@ -1,339 +0,0 @@ -local classes = require("ogpt.common.classes") -local SimpleWindow = require("ogpt.common.ui.window") -local BaseAction = require("ogpt.flows.actions.base") -local layouts = require("ogpt.flows.actions.edits.layouts") -local utils = require("ogpt.utils") -local Config = require("ogpt.config") -local SimpleParameters = require("ogpt.common.simple_parameters") - -local EditAction = classes.class(BaseAction) - -local STRATEGY_EDIT = "edit" -local STRATEGY_EDIT_CODE = "edit_code" - -function EditAction:init(name, opts) - self.name = name or "" - opts = opts or {} - self.super:init(opts) - self.provider = Config.get_provider(opts.provider, self) - self.params = Config.get_action_params(self.provider.name, opts.params or {}) - self.system = type(opts.system) == "function" and opts.system() or opts.system or "" - self.template = type(opts.template) == "function" and opts.template() or opts.template or "{{input}}" - self.variables = opts.variables or {} - self.strategy = opts.strategy or STRATEGY_EDIT - self.ui = opts.ui or {} - self:post_init() -end - -function EditAction:run() - vim.schedule(function() - if self.strategy == STRATEGY_EDIT_CODE and self.opts.delay then - self:edit_with_instructions({}, { self:get_visual_selection() }, { - template = self.template, - variables = self.variables, - edit_code = true, - filetype = self:get_filetype(), - }) - elseif self.strategy == STRATEGY_EDIT and self.opts.delay then - self:edit_with_instructions({}, { self:get_visual_selection() }, { - template = self.template, - variables = self.variables, - edit_code = false, - }) - elseif self.strategy == STRATEGY_EDIT then - self:edit_with_instructions({}, { self:get_visual_selection() }, { - template = self.template, - variables = self.variables, - -- params = self:get_params(), - }) - elseif self.strategy == STRATEGY_EDIT_CODE then - self:edit_with_instructions({}, { self:get_visual_selection() }, { - template = self.template, - variables = self.variables, - -- params = self:get_params(), - edit_code = true, - filetype = self:get_filetype(), - }) - end - end) -end - -local instructions_input, layout, input_window, output_window, output, timer, filetype, bufnr, extmark_id - -local setup_and_mount = function(lines, output_lines, ...) - -- set input - if lines then - vim.api.nvim_buf_set_lines(input_window.bufnr, 0, -1, false, lines) - end - - -- set output - if output_lines then - vim.api.nvim_buf_set_lines(output_window.bufnr, 0, -1, false, output_lines) - end - - -- -- set input and output settings - -- for _, window in ipairs({ input_window, output_window }) do - -- vim.api.nvim_buf_set_option(window.bufnr, "filetype", "markdown") - -- vim.api.nvim_win_set_option(window.winid, "number", true) - -- end -end - -function EditAction:edit_with_instructions(output_lines, selection, opts, ...) - opts = opts or {} - opts.params = opts.params or self.params - local api_params = opts.params - - bufnr = self:get_bufnr() - - filetype = vim.api.nvim_buf_get_option(bufnr, "filetype") - - local visual_lines, start_row, start_col, end_row, end_col - if selection == nil then - visual_lines, start_row, start_col, end_row, end_col = utils.get_visual_lines(bufnr) - else - visual_lines, start_row, start_col, end_row, end_col = unpack(selection) - end - local parameters_panel = SimpleParameters.get_parameters_panel("edits", api_params, nil, self) - input_window = SimpleWindow.new("ogpt_input", { - buf = { - syntax = vim.api.nvim_buf_get_option(0, "filetype"), - }, - }) - output_window = SimpleWindow.new("ogpt_output") - -- instructions_input = ChatInput(Config.options.popup_input, { - instructions_input = SimpleWindow.new("ogpt_instruction", { - keymaps = { - [""] = function() - local function on_submit(instruction) - -- clear input - vim.api.nvim_buf_set_lines(instructions_input.bufnr, 0, -1, false, { "" }) - vim.api.nvim_buf_set_lines(output_window.bufnr, 0, -1, false, { "" }) - -- show_progress() - self:run_spinner(true) - - local input = table.concat(vim.api.nvim_buf_get_lines(input_window.bufnr, 0, -1, false), "\n") - - -- if instruction is empty, try to get the original instruction from opts - if instruction == "" then - instruction = opts.instruction or "" - end - local messages = self:build_edit_messages(input, instruction, opts) - local params = vim.tbl_extend("keep", { messages = messages }, SimpleParameters.params) - self.provider.api:edits( - params, - utils.partial(utils.add_partial_completion, { - panel = output_window, - on_complete = function(response) - -- on the completion, execute this function to extract out codeblocks - local nlcount = utils.count_newlines_at_end(response) - local output_txt = response - if opts.edit_code then - local code_response = utils.extract_code(response) - -- if the chat is to edit code, it will try to extract out the code from response - output_txt = response - if code_response then - output_txt = utils.match_indentation(response, code_response) - else - vim.notify("no codeblock detected", vim.log.levels.INFO) - end - if response.applied_changes then - vim.notify(response.applied_changes, vim.log.levels.INFO) - end - end - local output_txt_nlfixed = utils.replace_newlines_at_end(output_txt, nlcount) - local _output = utils.split_string_by_line(output_txt_nlfixed) - if output_window.bufnr then - vim.api.nvim_buf_set_lines(output_window.bufnr, 0, -1, false, _output) - end - end, - progress = function(flag) - self:run_spinner(flag) - end, - }) - ) - end - - local instructions = vim.api.nvim_buf_get_lines(vim.api.nvim_get_current_buf(), 0, -1, false) - on_submit = vim.schedule_wrap(on_submit) - on_submit(table.concat(instructions, "\n")) - end, - }, - prompt = Config.options.popup_input.prompt, - default_value = opts.instruction or "", - events = { - { - events = { "BufUnload" }, - callback = function() - -- vim.print("turning off spinner") - self:run_spinner(false) - if timer ~= nil then - timer:stop() - end - end, - }, - }, - }) - - layout = - layouts.edit_with_no_layout(layout, self, input_window, instructions_input, output_window, parameters_panel, { - show_parameters = false, - }) - - -- accept output window - for _, window in ipairs({ input_window, output_window, instructions_input }) do - for _, mode in ipairs({ "n", "i" }) do - window:map(mode, Config.options.edit.keymaps.accept, function() - instructions_input.input_props.on_close() - local lines = vim.api.nvim_buf_get_lines(output_window.bufnr, 0, -1, false) - vim.api.nvim_buf_set_text(bufnr, start_row - 1, start_col - 1, end_row - 1, end_col, lines) - vim.notify("Successfully applied the change!", vim.log.levels.INFO) - end, { noremap = true }) - end - end - - -- use output as input - for _, window in ipairs({ input_window, output_window, instructions_input }) do - for _, mode in ipairs({ "n", "i" }) do - window:map(mode, Config.options.edit.keymaps.use_output_as_input, function() - local lines = vim.api.nvim_buf_get_lines(output_window.bufnr, 0, -1, false) - vim.api.nvim_buf_set_lines(input_window.bufnr, 0, -1, false, lines) - vim.api.nvim_buf_set_lines(output_window.bufnr, 0, -1, false, {}) - end, { noremap = true }) - end - end - - -- close - for _, window in ipairs({ input_window, output_window, instructions_input, parameters_panel }) do - for _, mode in ipairs({ "n", "i" }) do - window:map(mode, Config.options.edit.keymaps.close, function() - self.spinner:stop() - if vim.fn.mode() == "i" then - vim.api.nvim_command("stopinsert") - end - -- vim.cmd("q") - input_window:unmount() - output_window:unmount() - instructions_input:unmount() - parameters_panel:unmount() - end, { noremap = true }) - end - end - - -- toggle parameters - local parameters_open = false - for _, popup in ipairs({ parameters_panel, instructions_input, input_window, output_window }) do - for _, mode in ipairs({ "n", "i" }) do - popup:map(mode, Config.options.edit.keymaps.toggle_parameters, function() - if parameters_open then - layouts.edit_with_no_layout(layout, self, input_window, instructions_input, output_window, parameters_panel, { - show_parameters = false, - }) - else - layouts.edit_with_no_layout(layout, self, input_window, instructions_input, output_window, parameters_panel, { - show_parameters = true, - }) - SimpleParameters.refresh_panel() - end - parameters_open = not parameters_open - -- set input and output settings - -- TODO - -- for _, window in ipairs({ input_window, output_window }) do - -- vim.api.nvim_buf_set_option(window.bufnr, "filetype", filetype) - -- vim.api.nvim_win_set_option(window.winid, "number", true) - -- end - end, {}) - end - end - - -- cycle windows - local active_panel = instructions_input - for _, popup in ipairs({ input_window, output_window, parameters_panel, instructions_input }) do - for _, mode in ipairs({ "n", "i" }) do - if mode == "i" and (popup == input_window or popup == output_window) then - goto continue - end - popup:map(mode, Config.options.edit.keymaps.cycle_windows, function() - if active_panel == instructions_input then - vim.api.nvim_set_current_win(input_window.winid) - active_panel = input_window - vim.api.nvim_command("stopinsert") - elseif active_panel == input_window and mode ~= "i" then - vim.api.nvim_set_current_win(output_window.winid) - active_panel = output_window - vim.api.nvim_command("stopinsert") - elseif active_panel == output_window and mode ~= "i" then - if parameters_open then - vim.api.nvim_set_current_win(parameters_panel.winid) - active_panel = parameters_panel - else - vim.api.nvim_set_current_win(instructions_input.winid) - active_panel = instructions_input - end - elseif active_panel == parameters_panel then - vim.api.nvim_set_current_win(instructions_input.winid) - active_panel = instructions_input - end - end, {}) - ::continue:: - end - end - - -- toggle diff mode - local diff_mode = Config.options.edit.diff - for _, popup in ipairs({ parameters_panel, instructions_input, output_window, input_window }) do - for _, mode in ipairs({ "n", "i" }) do - popup:map(mode, Config.options.edit.keymaps.toggle_diff, function() - diff_mode = not diff_mode - for _, winid in ipairs({ input_window.winid, output_window.winid }) do - vim.api.nvim_set_current_win(winid) - if diff_mode then - vim.api.nvim_command("diffthis") - else - vim.api.nvim_command("diffoff") - end - vim.api.nvim_set_current_win(instructions_input.winid) - end - end, {}) - end - end - - setup_and_mount(visual_lines, output_lines) -end - -function EditAction:build_edit_messages(input, instructions, opts) - local _input = input - if opts.edit_code then - _input = "```" .. (opts.filetype or "") .. "\n" .. input .. "````" - else - _input = "```" .. (opts.filetype or "") .. "\n" .. input .. "````" - end - local variables = vim.tbl_extend("force", {}, { - instruction = instructions, - input = _input, - filetype = opts.filetype, - }, opts.variables) - local system_msg = opts.params.system or "" - local messages = { - { - role = "system", - content = system_msg, - }, - { - role = "user", - content = self:render_template(variables, opts.template), - }, - } - - return messages -end - -function EditAction:render_template(variables, template) - local result = template - for key, value in pairs(variables) do - local escaped_value = utils.escape_pattern(value) - result = string.gsub(result, "{{" .. key .. "}}", escaped_value) - end - return result -end - -return EditAction diff --git a/lua/ogpt/flows/chat/base.lua b/lua/ogpt/flows/chat/base.lua index 7734a82..1eb50b4 100644 --- a/lua/ogpt/flows/chat/base.lua +++ b/lua/ogpt/flows/chat/base.lua @@ -1,6 +1,6 @@ -local classes = require("ogpt.common.classes") +local Object = require("ogpt.common.object") local Layout = require("nui.layout") -local Popup = require("nui.popup") +local Popup = require("ogpt.common.popup") local ChatInput = require("ogpt.input") local Config = require("ogpt.config") @@ -17,7 +17,7 @@ ROLE_ASSISTANT = "assistant" ROLE_SYSTEM = "system" ROLE_USER = "user" -local Chat = classes.class() +local Chat = Object("Chat") function Chat:init(opts) self.input_extmark_id = nil @@ -30,6 +30,7 @@ function Chat:init(opts) self.focused = true -- UI ELEMENTS + self.edgy = Config.options.chat.edgy self.layout = nil self.chat_panel = nil self.chat_input = nil @@ -685,12 +686,12 @@ function Chat:open() self.sessions_panel = Sessions.get_panel(function(session) self:set_session(session) end) - self.chat_window = Popup(Config.options.popup_window) + self.chat_window = Popup(Config.options.output_window, Config.options.chat.edgy) self.system_role_panel = SystemWindow({ on_change = function(text) self:set_system_message(text) end, - }) + }, Config.options.chat.edgy) self.stop = false self.should_stop = function() if self.stop then @@ -700,8 +701,9 @@ function Chat:open() return false end end - self.chat_input = ChatInput(Config.options.popup_input, { - prompt = Config.options.popup_input.prompt, + self.chat_input = ChatInput(Config.options.input_window, { + edgy = Config.options.chat.edgy, + prompt = Config.options.input_window.prompt, on_close = function() self:hide() end, @@ -959,7 +961,7 @@ function Chat:toggle() end function Chat:configure_parameters_panel(session) - self.parameters_panel = Parameters.get_panel(session) + self.parameters_panel = Parameters.get_panel(session, self) self:redraw() end diff --git a/lua/ogpt/flows/chat/init.lua b/lua/ogpt/flows/chat/init.lua index 65928d2..6c14d47 100644 --- a/lua/ogpt/flows/chat/init.lua +++ b/lua/ogpt/flows/chat/init.lua @@ -10,7 +10,7 @@ M.open = function(opts) if M.chat ~= nil and M.chat.active then M.chat:toggle() else - M.chat = Chat.new(opts) + M.chat = Chat(opts) M.chat:open(opts) end end @@ -22,7 +22,7 @@ M.focus = function(opts) M.chat:show(opts) end else - M.chat = Chat.new(opts) + M.chat = Chat(opts) M.chat:open(opts) end end @@ -31,7 +31,7 @@ M.open_with_awesome_prompt = function() Prompts.selectAwesomePrompt({ cb = vim.schedule_wrap(function(act, prompt) -- create new named session - local session = Session.new({ name = act }) + local session = Session({ name = act }) session:save() local chat = Chat:new() diff --git a/lua/ogpt/flows/chat/session.lua b/lua/ogpt/flows/chat/session.lua index 67a0a69..84091ad 100644 --- a/lua/ogpt/flows/chat/session.lua +++ b/lua/ogpt/flows/chat/session.lua @@ -1,9 +1,9 @@ -local classes = require("ogpt.common.classes") +local Object = require("ogpt.common.object") local Path = require("plenary.path") local scan = require("plenary.scandir") local Config = require("ogpt.config") -local Session = classes.class() +local Session = Object("Session") local function get_current_date() return os.date("%Y-%m-%d_%H:%M:%S") @@ -199,9 +199,9 @@ function Session.latest(opts) local sessions = Session.list_sessions() if #sessions > 0 then local session = sessions[1] - return Session.new({ filename = session.filename }) + return Session({ filename = session.filename }) end - return Session.new(opts) + return Session(opts) end return Session diff --git a/lua/ogpt/flows/chat/sessions.lua b/lua/ogpt/flows/chat/sessions.lua index 9af5fac..7952842 100644 --- a/lua/ogpt/flows/chat/sessions.lua +++ b/lua/ogpt/flows/chat/sessions.lua @@ -1,7 +1,7 @@ local M = {} M.vts = {} -local Popup = require("nui.popup") +local Popup = require("ogpt.common.popup") local Config = require("ogpt.config") local Session = require("ogpt.flows.chat.session") local Utils = require("ogpt.utils") @@ -17,7 +17,7 @@ end M.set_session = function() M.active_line = M.current_line local selected = M.sessions[M.current_line] - local session = Session.new({ filename = selected.filename }) + local session = Session({ filename = selected.filename }) M.render_list() M.set_session_cb(session) end @@ -25,7 +25,7 @@ end M.rename_session = function() M.active_line = M.current_line local selected = M.sessions[M.current_line] - local session = Session.new({ filename = selected.filename }) + local session = Session({ filename = selected.filename }) local input_widget = InputWidget("New Name:", function(value) if value ~= nil and value ~= "" then session:rename(value) @@ -39,7 +39,7 @@ end M.delete_session = function() local selected = M.sessions[M.current_line] if M.active_line ~= M.current_line then - local session = Session.new({ filename = selected.filename }) + local session = Session({ filename = selected.filename }) session:delete() M.sessions = Session.list_sessions() if M.active_line > M.current_line then @@ -98,7 +98,7 @@ M.get_panel = function(set_session_cb) M.current_line = 1 M.set_session_cb = set_session_cb - M.panel = Popup(Config.options.chat.sessions_window) + M.panel = Popup(Config.options.chat.sessions_window, Config.options.chat.edgy) M.panel:map("n", Config.options.chat.keymaps.select_session, function() M.set_session() diff --git a/lua/ogpt/flows/chat/system_window.lua b/lua/ogpt/flows/chat/system_window.lua index 4253704..3d9607b 100644 --- a/lua/ogpt/flows/chat/system_window.lua +++ b/lua/ogpt/flows/chat/system_window.lua @@ -1,15 +1,15 @@ -local Popup = require("nui.popup") +local Popup = require("ogpt.common.popup") local Config = require("ogpt.config") local SystemWindow = Popup:extend("SystemWindow") -function SystemWindow:init(options) +function SystemWindow:init(options, edgy) self.working = false self.on_change = options.on_change options = vim.tbl_deep_extend("force", options or {}, Config.options.system_window) - SystemWindow.super.init(self, options) + SystemWindow.super.init(self, options, edgy) end function SystemWindow:toggle_placeholder() diff --git a/lua/ogpt/flows/code_completions/init.lua b/lua/ogpt/flows/code_completions/init.lua index 6c34d9d..96d5936 100644 --- a/lua/ogpt/flows/code_completions/init.lua +++ b/lua/ogpt/flows/code_completions/init.lua @@ -85,7 +85,7 @@ M.complete = function() }, }, function(answer, usage) set_loading(false) - local Popup = require("nui.popup") + local Popup = require("ogpt.common.popup") answer = M.extract_snippet(answer) local lines = Utils.split_string_by_line(answer) diff --git a/lua/ogpt/input.lua b/lua/ogpt/input.lua index b0247f0..6181a0e 100644 --- a/lua/ogpt/input.lua +++ b/lua/ogpt/input.lua @@ -1,4 +1,4 @@ -local Popup = require("nui.popup") +local Popup = require("ogpt.common.popup") local Text = require("nui.text") local defaults = require("nui.utils").defaults local is_type = require("nui.utils").is_type @@ -22,11 +22,11 @@ local function patch_cursor_position(target_cursor, force) end end -local Input = Popup:extend("NuiInput") +local Input = Popup:extend("OgptInput") ---@param popup_options table ---@param options table -function Input:init(popup_options, options) +function Input:init(popup_options, options, edgy) vim.fn.sign_define("multiprompt_sign", { text = " ", texthl = "LineNr", numhl = "LineNr" }) vim.fn.sign_define("singleprompt_sign", { text = " ", texthl = "LineNr", numhl = "LineNr" }) @@ -42,7 +42,7 @@ function Input:init(popup_options, options) popup_options.size.height = 2 - Input.super.init(self, popup_options) + Input.super.init(self, popup_options, options.edgy) self._.default_value = defaults(options.default_value, "") self._.prompt = Text(defaults(options.prompt, "")) @@ -97,7 +97,7 @@ function Input:init(popup_options, options) if options.on_change then props.on_change = function() local lines = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) - local max_lines = Config.options.popup_input.max_visible_lines -- Set the maximum number of lines here + local max_lines = Config.options.input_window.max_visible_lines -- Set the maximum number of lines here if max_lines ~= nil and #lines > max_lines then lines = { unpack(lines, 1, max_lines) } -- Only keep the first max_lines lines end @@ -132,13 +132,13 @@ function Input:mount() end, { once = true }) end - self:map("i", Config.options.popup_input.submit, function() + self:map("i", Config.options.input_window.submit, function() local lines = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) local value = table.concat(lines, "\n") props.on_submit(value) end, { noremap = true }) - self:map("n", Config.options.popup_input.submit_n, function() + self:map("n", Config.options.input_window.submit_n, function() local lines = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) local value = table.concat(lines, "\n") props.on_submit(value) diff --git a/lua/ogpt/models.lua b/lua/ogpt/models.lua index 909e9d4..5d576f6 100644 --- a/lua/ogpt/models.lua +++ b/lua/ogpt/models.lua @@ -114,7 +114,7 @@ function M.select_model(provider, opts) height = 0.5, }, results_title = "Select Ollama Model", - prompt_prefix = Config.options.popup_input.prompt, + prompt_prefix = Config.options.input_window.prompt, selection_caret = Config.options.chat.answer_sign .. " ", prompt_title = "Models", finder = finder(provider), diff --git a/lua/ogpt/parameters.lua b/lua/ogpt/parameters.lua index 486a57f..b553aa4 100644 --- a/lua/ogpt/parameters.lua +++ b/lua/ogpt/parameters.lua @@ -7,7 +7,7 @@ local action_state = require("telescope.actions.state") local M = {} M.vts = {} -local Popup = require("nui.popup") +local Popup = require("ogpt.common.popup") local Config = require("ogpt.config") local namespace_id = vim.api.nvim_create_namespace("OGPTNS") @@ -155,7 +155,7 @@ function M.select_parameter(opts) height = 0.5, }, results_title = "Select Additional Parameter", - prompt_prefix = Config.options.popup_input.prompt, + prompt_prefix = Config.options.input_window.prompt, selection_caret = Config.options.chat.answer_sign .. " ", prompt_title = "Parameter", finder = finder({ @@ -255,9 +255,7 @@ M.get_parameters_panel = function(type, default_params, session, parent) M.params = session.parameters end - M.panel = Popup(Config.options.parameters_window) - -- M.panel = SimpleWindow.new(Config.options.parameters_window) - -- M.panel:mount() + M.panel = Popup(Config.options.parameters_window, parent.edgy) M.refresh_panel() M.panel:map("n", "d", function() @@ -365,7 +363,7 @@ M.open_edit_property_input = function(key, value, row, cb) winhighlight = "Normal:Normal,FloatBorder:Normal", }, }, { - prompt = Config.options.popup_input.prompt .. key .. ": ", + prompt = Config.options.input_window.prompt .. key .. ": ", default_value = "" .. value, on_submit = cb, }) diff --git a/lua/ogpt/prompts.lua b/lua/ogpt/prompts.lua index 5f27df2..7466a0b 100644 --- a/lua/ogpt/prompts.lua +++ b/lua/ogpt/prompts.lua @@ -133,7 +133,7 @@ function M.selectAwesomePrompt(opts) height = 0.5, }, results_title = "OGPT Acts As ...", - prompt_prefix = Config.options.popup_input.prompt, + prompt_prefix = Config.options.input_window.prompt, selection_caret = Config.options.chat.answer_sign .. " ", prompt_title = "Prompt", finder = finder({ url = Config.options.predefined_chat_gpt_prompts }), diff --git a/lua/ogpt/provider/init.lua b/lua/ogpt/provider/init.lua index d769791..f0dc2d8 100644 --- a/lua/ogpt/provider/init.lua +++ b/lua/ogpt/provider/init.lua @@ -75,7 +75,7 @@ function M.select_provider(opts) height = 0.5, }, results_title = "Select Provider", - prompt_prefix = Config.options.popup_input.prompt, + prompt_prefix = Config.options.input_window.prompt, selection_caret = Config.options.chat.answer_sign .. " ", prompt_title = "Providers", finder = finder(), diff --git a/lua/ogpt/utils.lua b/lua/ogpt/utils.lua index 5d43641..017a708 100644 --- a/lua/ogpt/utils.lua +++ b/lua/ogpt/utils.lua @@ -290,7 +290,7 @@ function M.add_partial_completion(opts, text, state) local buffer = panel.bufnr for i, line in ipairs(lines) do - if vim.fn.bufexists(buffer) then + if buffer and vim.fn.bufexists(buffer) then local currentLine = vim.api.nvim_buf_get_lines(buffer, -2, -1, false)[1] if currentLine then vim.api.nvim_buf_set_lines(buffer, -2, -1, false, { currentLine .. line })