Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Install `askCode.nvim` using your favorite package manager.

```lua
{
"e3oroush/askCode",
"realEbi/askCode",
config = function()
require("askCode").setup({
-- Your configuration here
Expand All @@ -41,7 +41,7 @@ Install `askCode.nvim` using your favorite package manager.

```lua
use {
"e3oroush/askCode",
"realEbi/askCode",
config = function()
require("askCode").setup({
-- Your configuration here
Expand All @@ -60,7 +60,8 @@ require("askCode").setup({
debug = false, -- Enable debug mode (default: false)
quit_key = "q", -- Key to quit floating windows (default: "q")
output_format = "json", -- Output format (default: "json")
window = { -- Floating window configuration
window = { -- Configuration for the display window
type = "float", -- Type of window: "float", "vertical", or "horizontal" (default: "float")
width_ratio = 0.7, -- Window width as ratio of screen width (default: 0.7)
height_ratio = 0.7, -- Window height as ratio of screen height (default: 0.7)
max_width = 240, -- Maximum window width in columns (default: 240)
Expand Down Expand Up @@ -89,6 +90,36 @@ The `:AskCode` command automatically detects whether to start a new conversation

**Note**: Code replacement only works in visual mode with selected text.

### Configuration Management

You can get and set configuration values at runtime using the `:AskCodeConfig` command:

**Get a configuration value:**
```vim
:AskCodeConfig agent
:AskCodeConfig window.type
```

**Set a configuration value:**
```vim
:AskCodeConfig agent amazonq
:AskCodeConfig window.type vertical
:AskCodeConfig window.width_ratio 0.8
:AskCodeConfig debug true
```

The command supports tab completion for all available configuration keys. Values are automatically converted to the appropriate type (booleans, numbers, strings).

**Programmatic access:**
```lua
-- Get config
local agent = require("askCode").get_config("agent")
local all_config = require("askCode").get_config()

-- Set config
require("askCode").set_config("window.type", "horizontal")
```

### Keybinding Examples

You can map the commands to keybindings for easier access:
Expand All @@ -110,7 +141,7 @@ Contributions are welcome! To get started with development:
1. **Clone the repository**:

```sh
git clone https://github.com/e3oroush/askCode.git askCode.nvim
git clone https://github.com/realEbi/askCode.git askCode.nvim
cd askCode.nvim
```

Expand Down
2 changes: 1 addition & 1 deletion lua/askCode/agents/amazonq.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ end
function M.prepare_command(prompt)
-- Escape the prompt to ensure it's safely passed to the shell.
local escaped_prompt = vim.fn.shellescape(prompt)
return string.format("echo %s | q chat --no-interactive 2>&1", escaped_prompt)
return string.format("echo %s | kiro-cli chat --no-interactive 2>&1", escaped_prompt)
end

--- Parses the response from the AmazonQ CLI.
Expand Down
80 changes: 74 additions & 6 deletions lua/askCode/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ M.default = {
quit_key = "q",
output_format = "json",
window = {
type = "float", -- float, vertical, horizontal
width_ratio = 0.7,
height_ratio = 0.7,
max_width = 240,
Expand All @@ -26,14 +27,81 @@ M.default = {
---@return Config
function M.merge_with_default(changes)
changes = changes or {}

-- merge basic settings
---@type Config
local config = vim.tbl_deep_extend("force", M.default, changes)
M.current_config = config
M.current_config = vim.tbl_deep_extend("keep", vim.deepcopy(changes), M.current_config)
return M.current_config
end

M.current_config = M.default
M.current_config = vim.deepcopy(M.default)

--- gets a config value
---@param key? string dot separated key
---@return any
function M.get(key)
if not key then
return M.current_config
end
local parts = vim.split(key, "%.")
local current = M.current_config
for _, part in ipairs(parts) do
if type(current) ~= "table" or current[part] == nil then
return nil
end
current = current[part]
end
return current
end

--- sets a config value
---@param key string dot separated key
---@param value any
---@return any new value
function M.set(key, value)
local parts = vim.split(key, "%.")
if type(value) == "string" then
if value == "true" then
value = true
elseif value == "false" then
value = false
elseif tonumber(value) then
value = tonumber(value)
end
end

local t = {}
local current = t
for i, part in ipairs(parts) do
if i == #parts then
current[part] = value
else
current[part] = {}
current = current[part]
end
end
M.merge_with_default(t)
return M.get(key)
end

--- resets config to default
function M.reset_config()
M.current_config = vim.deepcopy(M.default)
end

--- gets all config keys as dot-separated strings
---@return string[]
function M.get_all_keys()
local keys = {}
local function traverse(tbl, prefix)
for key, value in pairs(tbl) do
local full_key = prefix == "" and key or prefix .. "." .. key
if type(value) == "table" then
traverse(value, full_key)
else
table.insert(keys, full_key)
end
end
end
traverse(M.default, "")
return keys
end

return M
25 changes: 20 additions & 5 deletions lua/askCode/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ function M.setup(cfg)
config.merge_with_default(cfg)
end

--- Gets a config value
---@param key? string dot separated key (e.g., "window.type"). If nil, returns entire config
---@return any
function M.get_config(key)
return config.get(key)
end

--- Sets a config value
---@param key string dot separated key (e.g., "window.type")
---@param value any the value to set
---@return any the new value
function M.set_config(key, value)
return config.set(key, value)
end

--- Constructs a prompt for the AI assistant based on question, mode, and conversation history
--- @param question string The user's question or request
--- @param mode string The current editor mode (visual, normal, etc.)
Expand Down Expand Up @@ -83,7 +98,7 @@ function M.ask(question, mode)
end

local command = agent.prepare_command(full_prompt)
state.win_id, state.buf_id = ui.show_in_float("Loading...", on_close)
state.win_id, state.buf_id = ui.show_window("Loading...", on_close)

local response_lines = {}
local on_stdout = function(_, data)
Expand Down Expand Up @@ -114,7 +129,7 @@ function M.ask(question, mode)
utils.append_file(state.history_file, agent_response)

state.display_content = "AGENT: " .. response
ui.update_float(state.win_id, state.buf_id, state.display_content)
ui.update_window(state.win_id, state.buf_id, state.display_content)
end

runner.run_command(
Expand Down Expand Up @@ -174,7 +189,7 @@ function M.follow_up(question)
local new_display_part = string.format("\n\n---\n\nUSER: %s\n\nAGENT: %s", question, response)
state.display_content = state.display_content .. new_display_part

ui.update_float(state.win_id, state.buf_id, state.display_content, cursor_position)
ui.update_window(state.win_id, state.buf_id, state.display_content, cursor_position)
end

runner.run_command({ "sh", "-c", command }, on_stdout, { on_exit = on_exit, stdout_buffered = true })
Expand Down Expand Up @@ -224,7 +239,7 @@ function M.ask_replace(question, mode)

local command = agent.prepare_command(full_prompt)
state.display_content = "Press Q to apply replacement, q to cancel\n\n"
state.win_id, state.buf_id = ui.show_in_float(state.display_content, on_close, true, on_apply)
state.win_id, state.buf_id = ui.show_window(state.display_content, on_close, true, on_apply)

local response_lines = {}
local on_stdout = function(_, data)
Expand All @@ -245,7 +260,7 @@ function M.ask_replace(question, mode)
end

state.display_content = state.display_content .. response
ui.update_float(state.win_id, state.buf_id, state.display_content, nil, true)
ui.update_window(state.win_id, state.buf_id, state.display_content, nil, true)
end

runner.run_command({ "sh", "-c", command }, on_stdout, { on_exit = on_exit, stdout_buffered = true })
Expand Down
70 changes: 46 additions & 24 deletions lua/askCode/ui.lua
Original file line number Diff line number Diff line change
@@ -1,38 +1,60 @@
local config = require("askCode.config")
local M = {}

--- Creates a floating window to show content.
--- Creates a window to show content.
--- @param content string The content to display.
--- @param on_close function A callback to execute when the window is closed.
--- @param editable boolean|nil Whether the window should be editable (default: false).
--- @param on_apply function|nil Callback for apply action (Q key) - receives edited content.
--- @return number, number The window ID and buffer ID.
function M.show_in_float(content, on_close, editable, on_apply)
function M.show_window(content, on_close, editable, on_apply)
local window_config = config.current_config.window
local window_type = window_config.type or "float"

local width = math.floor(vim.o.columns * window_config.width_ratio)
if width > window_config.max_width then
width = window_config.max_width
end

local height = math.floor(vim.o.lines * window_config.height_ratio)
if height > window_config.max_height then
height = window_config.max_height
end

local win_opts = {
relative = "cursor",
width = width,
height = height,
row = 1,
col = 0,
style = "minimal",
border = "rounded",
}
local buf_id = vim.api.nvim_create_buf(false, true)
vim.api.nvim_set_option_value("filetype", "markdown", { buf = buf_id })

local win_id = vim.api.nvim_open_win(buf_id, true, win_opts)
local win_id
if window_type == "vertical" then
vim.api.nvim_command("vsplit")
win_id = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_buf(win_id, buf_id)
local width = math.floor(vim.o.columns * window_config.width_ratio)
if width > window_config.max_width then
width = window_config.max_width
end
vim.api.nvim_win_set_width(win_id, width)
elseif window_type == "horizontal" then
vim.api.nvim_command("split")
win_id = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_buf(win_id, buf_id)
local height = math.floor(vim.o.lines * window_config.height_ratio)
if height > window_config.max_height then
height = window_config.max_height
end
vim.api.nvim_win_set_height(win_id, height)
else -- float is the default
local width = math.floor(vim.o.columns * window_config.width_ratio)
if width > window_config.max_width then
width = window_config.max_width
end

local height = math.floor(vim.o.lines * window_config.height_ratio)
if height > window_config.max_height then
height = window_config.max_height
end

local win_opts = {
relative = "cursor",
width = width,
height = height,
row = 1,
col = 0,
style = "minimal",
border = "rounded",
}
win_id = vim.api.nvim_open_win(buf_id, true, win_opts)
end

-- Prepare content with instructions if editable
-- Set content
Expand Down Expand Up @@ -77,13 +99,13 @@ function M.show_in_float(content, on_close, editable, on_apply)
return win_id, buf_id
end

--- Updates the content of a floating window.
--- Updates the content of a window.
--- @param win_id number The ID of the window to update.
--- @param buf_id number The ID of the buffer to update.
--- @param content string The new content.
--- @param replacement? boolean if the buffer is for replacement command
--- @param cursor_line number|nil Optional line number to position cursor (defaults to end).
function M.update_float(win_id, buf_id, content, cursor_line, replacement)
function M.update_window(win_id, buf_id, content, cursor_line, replacement)
vim.schedule(function()
if not (buf_id and vim.api.nvim_buf_is_valid(buf_id)) then
return
Expand Down
31 changes: 31 additions & 0 deletions plugin/askCode.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,37 @@ vim.api.nvim_create_user_command("AskCodeReplace", function(opts)
end
end, { range = true, nargs = "?" })

vim.api.nvim_create_user_command("AskCodeConfig", function(opts)
local args = vim.split(opts.args, "%s+")
local key = args[1]
local value = args[2]

if not key or key == "" then
vim.notify("Usage: AskCodeConfig <key> [value]", vim.log.levels.ERROR)
return
end

if value then
local new_value = require("askCode").set_config(key, value)
vim.notify(string.format("Set %s = %s", key, vim.inspect(new_value)), vim.log.levels.INFO)
else
local current_value = require("askCode").get_config(key)
vim.notify(string.format("%s = %s", key, vim.inspect(current_value)), vim.log.levels.INFO)
end
end, {
nargs = "+",
complete = function(arg_lead, cmd_line, cursor_pos)
local args = vim.split(cmd_line, "%s+")
if #args <= 2 then
local keys = require("askCode.config").get_all_keys()
return vim.tbl_filter(function(key)
return vim.startswith(key, arg_lead)
end, keys)
end
return {}
end,
})

vim.keymap.set(
"v",
"<Plug>(AskCodeExplain)",
Expand Down
4 changes: 2 additions & 2 deletions tests/test_amazonq.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ T["prepare_command"] = new_set()

T["prepare_command"]["should format command correctly"] = function()
local command = child.lua_get([[Agent.prepare_command("test prompt")]])
eq(command, "echo 'test prompt' | q chat --no-interactive 2>&1")
eq(command, "echo 'test prompt' | kiro-cli chat --no-interactive 2>&1")
end

T["prepare_command"]["should escape special characters"] = function()
local command = child.lua_get([[Agent.prepare_command("test 'quoted' prompt")]])
eq(command, "echo 'test '\\''quoted'\\'' prompt' | q chat --no-interactive 2>&1")
eq(command, "echo 'test '\\''quoted'\\'' prompt' | kiro-cli chat --no-interactive 2>&1")
end

-- Test parse_response function
Expand Down
Loading