From 9130d30167194a651d501fcc8db9018027f39a24 Mon Sep 17 00:00:00 2001 From: Horacio Sanson Date: Mon, 13 Oct 2025 10:47:28 +0900 Subject: [PATCH 1/7] chore: refactor configuration to a separate module. Refactoring the configuration to a separate module to make it easier and cleaner to add additional configurations without poluting the init.lua file. --- lua/opencode-context/config.lua | 17 +++++++++++++++++ lua/opencode-context/init.lua | 15 +++++---------- 2 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 lua/opencode-context/config.lua diff --git a/lua/opencode-context/config.lua b/lua/opencode-context/config.lua new file mode 100644 index 0000000..df5a4d2 --- /dev/null +++ b/lua/opencode-context/config.lua @@ -0,0 +1,17 @@ +local default_config = { + -- Tmux settings + tmux_target = nil, -- Manual override: "session:window.pane" + auto_detect_pane = true, -- Auto-detect opencode pane in current window +} + +local M = vim.deepcopy(default_config) + +M.update = function(opts) + local newconf = vim.tbl_deep_extend("force", default_config, opts or {}) + + for k, v in pairs(newconf) do + M[k] = v + end +end + +return M diff --git a/lua/opencode-context/init.lua b/lua/opencode-context/init.lua index 239cb0d..468ead3 100644 --- a/lua/opencode-context/init.lua +++ b/lua/opencode-context/init.lua @@ -1,11 +1,6 @@ local M = {} local ui = require("opencode-context.ui") - -M.config = { - -- Tmux settings - tmux_target = nil, -- Manual override: "session:window.pane" - auto_detect_pane = true, -- Auto-detect opencode pane in current window -} +local config = require("opencode-context.config") local function get_current_file_path() local bufnr = vim.api.nvim_get_current_buf() @@ -186,11 +181,11 @@ end local function find_opencode_pane() -- If manual target is set, use it - if M.config.tmux_target then - return M.config.tmux_target + if config.tmux_target then + return config.tmux_target end - if not M.config.auto_detect_pane then + if not config.auto_detect_pane then return nil end @@ -340,7 +335,7 @@ function M.toggle_persistent_prompt() end function M.setup(opts) - M.config = vim.tbl_deep_extend("force", M.config, opts or {}) + require("opencode-context.config").update(opts) end return M From a561ff3dc283e5e88842536de97ecd68c0690887 Mon Sep 17 00:00:00 2001 From: Horacio Sanson Date: Mon, 13 Oct 2025 11:45:47 +0900 Subject: [PATCH 2/7] feat: allow configuration of persistent prompt height --- lua/opencode-context/config.lua | 7 +++++++ lua/opencode-context/ui.lua | 27 ++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lua/opencode-context/config.lua b/lua/opencode-context/config.lua index df5a4d2..94bdb95 100644 --- a/lua/opencode-context/config.lua +++ b/lua/opencode-context/config.lua @@ -2,6 +2,13 @@ local default_config = { -- Tmux settings tmux_target = nil, -- Manual override: "session:window.pane" auto_detect_pane = true, -- Auto-detect opencode pane in current window + + -- Prompt window configuration + win = { + persistent = { + height = 2, + }, + }, } local M = vim.deepcopy(default_config) diff --git a/lua/opencode-context/ui.lua b/lua/opencode-context/ui.lua index 3be8990..2947718 100644 --- a/lua/opencode-context/ui.lua +++ b/lua/opencode-context/ui.lua @@ -1,9 +1,34 @@ +local config = require("opencode-context.config") + local M = {} -- UI state local prompt_win = nil local prompt_buf = nil +local safe_size = function(size, min, max) + local safe_size = size + + -- If less or equal to zero, return min allowed + -- size. + if safe_size <= 0 then + return min + end + + -- If size is between 0 and 1, assume is a percentage of + -- max allowed size. + if safe_size < 1 then + safe_size = math.floor(max * safe_size) + end + + return safe_size +end + +local function safe_height(height) + local max_height = math.floor(0.9 * vim.o.lines) + return safe_size(height, 1, max_height) +end + local function create_prompt_buffer() if prompt_buf and vim.api.nvim_buf_is_valid(prompt_buf) then return prompt_buf @@ -65,7 +90,7 @@ function M.show_persistent_prompt(send_callback) -- Get editor dimensions local width = vim.o.columns - local height = 1 -- Increased to accommodate 2-line title + local height = safe_height(config.win.persistent.height) -- Increased to accommodate 2-line title local row = vim.o.lines - height - 3 -- Position above statusline/powerline -- Create floating window From 3d68ea8a10dc92a6bd7927495ffc12d61d3759f6 Mon Sep 17 00:00:00 2001 From: Horacio Sanson Date: Mon, 13 Oct 2025 11:46:49 +0900 Subject: [PATCH 3/7] feat: allow configuration of prompt icon --- lua/opencode-context/config.lua | 1 + lua/opencode-context/ui.lua | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/opencode-context/config.lua b/lua/opencode-context/config.lua index 94bdb95..2840d6f 100644 --- a/lua/opencode-context/config.lua +++ b/lua/opencode-context/config.lua @@ -5,6 +5,7 @@ local default_config = { -- Prompt window configuration win = { + icon = " ", persistent = { height = 2, }, diff --git a/lua/opencode-context/ui.lua b/lua/opencode-context/ui.lua index 2947718..2e0e6c7 100644 --- a/lua/opencode-context/ui.lua +++ b/lua/opencode-context/ui.lua @@ -102,7 +102,7 @@ function M.show_persistent_prompt(send_callback) col = 2, style = "minimal", border = "solid", - title = " OpenCode - use @file, @buffers, @cursor, @selection, @diagnostics, @here", + title = config.win.icon .. " OpenCode - use @file, @buffers, @cursor, @selection, @diagnostics, @here", title_pos = "left", }) From 67e69cfd7ddb4071905229479e42dc24705b50e0 Mon Sep 17 00:00:00 2001 From: Horacio Sanson Date: Mon, 13 Oct 2025 12:46:17 +0900 Subject: [PATCH 4/7] feat: implement input prompt. - Refactor _persistent_ methods to accept a type that allows to show/toggle either a prompt input (floating) or a persistent (bottom) prompt. - Add configuration options to set with/height of the prompts. --- lua/opencode-context/config.lua | 28 ++++--- lua/opencode-context/init.lua | 29 +++---- lua/opencode-context/ui.lua | 143 ++++++++++++++++++++++++++++---- 3 files changed, 156 insertions(+), 44 deletions(-) diff --git a/lua/opencode-context/config.lua b/lua/opencode-context/config.lua index 2840d6f..1584776 100644 --- a/lua/opencode-context/config.lua +++ b/lua/opencode-context/config.lua @@ -1,25 +1,29 @@ local default_config = { -- Tmux settings - tmux_target = nil, -- Manual override: "session:window.pane" - auto_detect_pane = true, -- Auto-detect opencode pane in current window + tmux_target = nil, -- Manual override: "session:window.pane" + auto_detect_pane = true, -- Auto-detect opencode pane in current window -- Prompt window configuration - win = { - icon = " ", - persistent = { - height = 2, - }, - }, + win = { + icon = " ", + input = { + width = 0.60, + height = 0.30, + }, + persistent = { + height = 2, + }, + }, } local M = vim.deepcopy(default_config) M.update = function(opts) - local newconf = vim.tbl_deep_extend("force", default_config, opts or {}) + local newconf = vim.tbl_deep_extend("force", default_config, opts or {}) - for k, v in pairs(newconf) do - M[k] = v - end + for k, v in pairs(newconf) do + M[k] = v + end end return M diff --git a/lua/opencode-context/init.lua b/lua/opencode-context/init.lua index 468ead3..f3151f6 100644 --- a/lua/opencode-context/init.lua +++ b/lua/opencode-context/init.lua @@ -276,19 +276,12 @@ function M.send_prompt() local default_text = "" if mode == "v" or mode == "V" or mode == "\22" then -- \22 is visual block mode default_text = "@selection " + else + default_text = "@cursor " end - vim.ui.input({ - prompt = "Enter prompt for opencode (use @file, @buffers, @cursor, @selection, @diagnostics): ", - default = default_text, - }, function(input) - if not input or input == "" then - return - end - - local processed_prompt = replace_placeholders(input) - send_to_opencode(processed_prompt) - end) + M.show_input_prompt() + M.add_text_to_prompt(default_text) end function M.toggle_mode() @@ -323,15 +316,23 @@ local function create_send_callback() end function M.show_persistent_prompt() - ui.show_persistent_prompt(create_send_callback()) + ui.show_prompt(create_send_callback(), "persistent") end function M.hide_persistent_prompt() - ui.hide_persistent_prompt() + ui.hide_prompt() end function M.toggle_persistent_prompt() - ui.toggle_persistent_prompt(create_send_callback()) + ui.toggle_prompt(create_send_callback(), "persistent") +end + +function M.show_input_prompt() + ui.show_prompt(create_send_callback(), "input") +end + +function M.add_text_to_prompt(text) + return ui.add_text_at_cursor(text) end function M.setup(opts) diff --git a/lua/opencode-context/ui.lua b/lua/opencode-context/ui.lua index 2e0e6c7..07bf0d4 100644 --- a/lua/opencode-context/ui.lua +++ b/lua/opencode-context/ui.lua @@ -9,7 +9,7 @@ local prompt_buf = nil local safe_size = function(size, min, max) local safe_size = size - -- If less or equal to zero, return min allowed + -- If less or equal to zero, return minimum allowed -- size. if safe_size <= 0 then return min @@ -25,8 +25,15 @@ local safe_size = function(size, min, max) end local function safe_height(height) + local min_height = 1 local max_height = math.floor(0.9 * vim.o.lines) - return safe_size(height, 1, max_height) + return safe_size(height, min_height, max_height) +end + +local function safe_width(width) + local min_width = math.floor(0.1 * vim.o.columns) + local max_width = math.floor(0.9 * vim.o.columns) + return safe_size(width, min_width, max_width) end local function create_prompt_buffer() @@ -45,7 +52,7 @@ local function create_prompt_buffer() return prompt_buf end -local function setup_prompt_keymaps(bufnr, send_callback) +local function setup_prompt_keymaps(bufnr, send_callback, type) local opts = { buffer = bufnr, silent = true } -- Enter to send prompt @@ -66,20 +73,31 @@ local function setup_prompt_keymaps(bufnr, send_callback) vim.api.nvim_win_set_cursor(prompt_win, { 1, 0 }) end end + + if type == "input" then + M.hide_prompt() + end end, opts) -- Escape to return to normal mode vim.keymap.set("i", "", function() vim.cmd("stopinsert") + if type == "input" then + if prompt_buf and vim.api.nvim_buf_is_valid(prompt_buf) then + -- Clear prompt buffer + vim.api.nvim_buf_set_lines(prompt_buf, 0, -1, false, { "" }) + end + M.hide_prompt() + end end, opts) -- Close prompt window with 'q' in normal mode vim.keymap.set("n", "q", function() - M.hide_persistent_prompt() + M.hide_prompt() end, opts) end -function M.show_persistent_prompt(send_callback) +function M.show_prompt(send_callback, type) if prompt_win and vim.api.nvim_win_is_valid(prompt_win) then vim.api.nvim_set_current_win(prompt_win) vim.cmd("startinsert") @@ -88,20 +106,31 @@ function M.show_persistent_prompt(send_callback) local bufnr = create_prompt_buffer() - -- Get editor dimensions - local width = vim.o.columns - local height = safe_height(config.win.persistent.height) -- Increased to accommodate 2-line title - local row = vim.o.lines - height - 3 -- Position above statusline/powerline + local relative = "cursor" + local width = safe_width(config.win.input.width) + local height = safe_height(config.win.input.height) + local row = 0 + local column = 0 + local border = "rounded" + + if type == "persistent" then + relative = "editor" + width = safe_width(vim.o.columns) + height = safe_height(config.win.persistent.height) + row = vim.o.lines - height - 3 -- Position above statusline/powerline + column = 2 + border = "solid" + end -- Create floating window prompt_win = vim.api.nvim_open_win(bufnr, true, { - relative = "editor", + relative = relative, width = width, height = height, row = row, - col = 2, + col = column, style = "minimal", - border = "solid", + border = border, title = config.win.icon .. " OpenCode - use @file, @buffers, @cursor, @selection, @diagnostics, @here", title_pos = "left", }) @@ -111,7 +140,7 @@ function M.show_persistent_prompt(send_callback) vim.wo[prompt_win].linebreak = true -- Setup keymaps for this buffer - setup_prompt_keymaps(bufnr, send_callback) + setup_prompt_keymaps(bufnr, send_callback, type) -- Start in insert mode vim.cmd("startinsert") @@ -124,7 +153,7 @@ function M.show_persistent_prompt(send_callback) if prompt_win and vim.api.nvim_win_is_valid(prompt_win) then local current_win = vim.api.nvim_get_current_win() if current_win ~= prompt_win then - M.hide_persistent_prompt() + M.hide_prompt() end end end, 100) @@ -133,18 +162,18 @@ function M.show_persistent_prompt(send_callback) }) end -function M.hide_persistent_prompt() +function M.hide_prompt() if prompt_win and vim.api.nvim_win_is_valid(prompt_win) then vim.api.nvim_win_close(prompt_win, false) prompt_win = nil end end -function M.toggle_persistent_prompt(send_callback) +function M.toggle_prompt(send_callback, type) if prompt_win and vim.api.nvim_win_is_valid(prompt_win) then - M.hide_persistent_prompt() + M.hide_prompt() else - M.show_persistent_prompt(send_callback) + M.show_prompt(send_callback, type) end end @@ -152,4 +181,82 @@ function M.is_prompt_visible() return prompt_win and vim.api.nvim_win_is_valid(prompt_win) end +function M.add_text_at_cursor(text) + if not prompt_buf or not vim.api.nvim_buf_is_valid(prompt_buf) then + return false + end + + if not prompt_win or not vim.api.nvim_win_is_valid(prompt_win) then + return false + end + + if not text or text == "" then + return false + end + + -- Clear prompt buffer + vim.api.nvim_buf_set_lines(prompt_buf, 0, -1, false, { "" }) + + -- Current cursor position + local row = 0 + local col = 0 + + -- Get current buffer content + local lines = vim.api.nvim_buf_get_lines(prompt_buf, 0, -1, false) + + -- Split the text to insert into lines + local text_lines = vim.split(text, "\n", { plain = true }) + + -- Handle single line insertion + if #text_lines == 1 then + local current_line = lines[row + 1] or "" + local before = current_line:sub(1, col) + local after = current_line:sub(col + 1) + lines[row + 1] = before .. text .. after + + -- Update buffer + vim.api.nvim_buf_set_lines(prompt_buf, 0, -1, false, lines) + + -- Move cursor to end of inserted text + vim.api.nvim_win_set_cursor(prompt_win, { row + 1, col + #text }) + else + -- Handle multi-line insertion + local current_line = lines[row + 1] or "" + local before = current_line:sub(1, col) + local after = current_line:sub(col + 1) + + -- Create newlines array + local new_lines = {} + + -- Add lines before insertion point + for i = 1, row do + new_lines[i] = lines[i] + end + + -- Add first line of insertion (with before text) + new_lines[row + 1] = before .. text_lines[1] + + -- Add middle lines of insertion + for i = 2, #text_lines - 1 do + new_lines[row + i] = text_lines[i] + end + -- Add last line of insertion (with after text) + local last_text_line = text_lines[#text_lines] + new_lines[row + #text_lines] = last_text_line .. after + + -- Add remaining lines after insertion point + for i = row + 2, #lines do + new_lines[row + #text_lines + (i - row - 1)] = lines[i] + end + + -- Update buffer + vim.api.nvim_buf_set_lines(prompt_buf, 0, -1, false, new_lines) + + -- Move cursor to end of inserted text + vim.api.nvim_win_set_cursor(prompt_win, { row + #text_lines, #last_text_line }) + end + + return true +end + return M From 21d16654f72e1bfd642e420922e99edcf6b484b2 Mon Sep 17 00:00:00 2001 From: Horacio Sanson Date: Mon, 13 Oct 2025 16:53:12 +0900 Subject: [PATCH 5/7] fix: exite insert mode when sending input prompt --- lua/opencode-context/ui.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/opencode-context/ui.lua b/lua/opencode-context/ui.lua index 07bf0d4..4887dde 100644 --- a/lua/opencode-context/ui.lua +++ b/lua/opencode-context/ui.lua @@ -75,6 +75,7 @@ local function setup_prompt_keymaps(bufnr, send_callback, type) end if type == "input" then + vim.cmd("stopinsert") M.hide_prompt() end end, opts) From 70cd05a3121803073ed5f6d68246184b76944715 Mon Sep 17 00:00:00 2001 From: Horacio Sanson Date: Mon, 13 Oct 2025 17:06:17 +0900 Subject: [PATCH 6/7] chore: add luarc.json to remove undefined vim global warnings --- .luarc.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .luarc.json diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..a1de830 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,5 @@ +{ + "diagnostics": { + "globals": ["vim"] + } +} From bc15a6f6d9406d7e756304dab5e79c5cae787113 Mon Sep 17 00:00:00 2001 From: Horacio Sanson Date: Mon, 13 Oct 2025 17:06:37 +0900 Subject: [PATCH 7/7] chore: fix redefined conifg variable warning --- lua/opencode-context/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/opencode-context/init.lua b/lua/opencode-context/init.lua index f3151f6..75c523d 100644 --- a/lua/opencode-context/init.lua +++ b/lua/opencode-context/init.lua @@ -37,8 +37,8 @@ end -- returns the buffer, relative file path and cursor local function get_cursor() local function is_floating(winid) - local config = vim.api.nvim_win_get_config(winid) - return config.relative ~= "" + local win_cfg = vim.api.nvim_win_get_config(winid) + return win_cfg.relative ~= "" end local current_win = vim.api.nvim_get_current_win()