Skip to content

Commit 0abbf53

Browse files
committed
add preserve cursor behavior
1 parent ac4faa2 commit 0abbf53

6 files changed

Lines changed: 722 additions & 121 deletions

File tree

‎docs/configuration.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ require("eca").setup({
149149
expanded_label = "- view diff", -- Label when the diff is expanded
150150
expanded = false, -- When true, tool diffs start expanded
151151
},
152+
preserve_cursor = true, -- When true, cursor stays in place when expanding/collapsing
152153
},
153154

154155
-- Reasoning ("Thinking") block behavior
@@ -260,6 +261,24 @@ require("eca").setup({
260261
})
261262
```
262263

264+
### Tool Call Behavior
265+
266+
```lua
267+
-- Keep cursor in place when expanding/collapsing tool calls
268+
require("eca").setup({
269+
windows = {
270+
chat = {
271+
tool_call = {
272+
preserve_cursor = true, -- Don't move cursor on expand/collapse
273+
diff = {
274+
expanded = true, -- Start with diffs expanded
275+
},
276+
},
277+
},
278+
},
279+
})
280+
```
281+
263282
---
264283

265284
## Migration Guide

‎lua/eca/config.lua‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ M._defaults = {
9090
expanded_label = "- view diff", -- Label when the diff is expanded
9191
expanded = false, -- When true, tool diffs start expanded
9292
},
93+
preserve_cursor = true, -- When true, cursor position is preserved when expanding/collapsing
9394
},
9495
reasoning = {
9596
expanded = false, -- When true, "Thinking" blocks start expanded

‎lua/eca/sidebar.lua‎

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2517,6 +2517,13 @@ function M:_expand_tool_call(call)
25172517
return
25182518
end
25192519

2520+
-- Save cursor position before expansion (if configured)
2521+
local saved_cursor = nil
2522+
local preserve_cursor = Utils.should_preserve_cursor()
2523+
if preserve_cursor and chat.winid and vim.api.nvim_win_is_valid(chat.winid) then
2524+
saved_cursor = vim.api.nvim_win_get_cursor(chat.winid)
2525+
end
2526+
25202527
-- Reasoning ("Thinking") entries behave slightly differently: we never
25212528
-- wrap them in code fences and the streaming handler is responsible for
25222529
-- keeping the body up to date. Here we just insert the current body once.
@@ -2552,7 +2559,15 @@ function M:_expand_tool_call(call)
25522559
call.expanded = true
25532560
self:_update_tool_call_header_line(call)
25542561

2555-
if chat.winid and vim.api.nvim_win_is_valid(chat.winid) then
2562+
-- Restore cursor position or move to last line based on config
2563+
if saved_cursor and chat.winid and vim.api.nvim_win_is_valid(chat.winid) then
2564+
-- Adjust cursor row if it was after the insertion point
2565+
if saved_cursor[1] > call.header_line then
2566+
saved_cursor[1] = saved_cursor[1] + count
2567+
end
2568+
vim.api.nvim_win_set_cursor(chat.winid, saved_cursor)
2569+
elseif not preserve_cursor and chat.winid and vim.api.nvim_win_is_valid(chat.winid) then
2570+
-- Default behavior: move cursor to last line of expanded content
25562571
local last_line = call.header_line + (call._last_arguments_count or 0)
25572572
vim.api.nvim_win_set_cursor(chat.winid, { last_line, 0 })
25582573
vim.api.nvim_win_call(chat.winid, function()
@@ -2588,8 +2603,15 @@ function M:_expand_tool_call(call)
25882603
-- Update header arrow
25892604
self:_update_tool_call_header_line(call)
25902605

2591-
-- Move cursor to show the full arguments block
2592-
if chat.winid and vim.api.nvim_win_is_valid(chat.winid) then
2606+
-- Restore cursor position or move to last line based on config
2607+
if saved_cursor and chat.winid and vim.api.nvim_win_is_valid(chat.winid) then
2608+
-- Adjust cursor row if it was after the insertion point
2609+
if saved_cursor[1] > call.header_line then
2610+
saved_cursor[1] = saved_cursor[1] + count
2611+
end
2612+
vim.api.nvim_win_set_cursor(chat.winid, saved_cursor)
2613+
elseif not preserve_cursor and chat.winid and vim.api.nvim_win_is_valid(chat.winid) then
2614+
-- Default behavior: move cursor to last line of expanded content
25932615
local last_line = call.header_line + count
25942616
vim.api.nvim_win_set_cursor(chat.winid, { last_line, 0 })
25952617
vim.api.nvim_win_call(chat.winid, function()
@@ -2609,6 +2631,13 @@ function M:_collapse_tool_call(call)
26092631
return
26102632
end
26112633

2634+
-- Save cursor position before collapsing (if configured)
2635+
local saved_cursor = nil
2636+
local preserve_cursor = Utils.should_preserve_cursor()
2637+
if preserve_cursor and chat.winid and vim.api.nvim_win_is_valid(chat.winid) then
2638+
saved_cursor = vim.api.nvim_win_get_cursor(chat.winid)
2639+
end
2640+
26122641
local count = call.arguments_lines and #call.arguments_lines or 0
26132642
if count == 0 then
26142643
call.expanded = false
@@ -2633,6 +2662,19 @@ function M:_collapse_tool_call(call)
26332662

26342663
-- Update header arrow
26352664
self:_update_tool_call_header_line(call)
2665+
2666+
-- Restore cursor position, adjusting if it was after the collapsed region
2667+
if saved_cursor and chat.winid and vim.api.nvim_win_is_valid(chat.winid) then
2668+
-- If cursor was inside the collapsed region, move it to the header
2669+
if saved_cursor[1] > call.header_line and saved_cursor[1] <= call.header_line + count then
2670+
saved_cursor[1] = call.header_line
2671+
saved_cursor[2] = 0
2672+
-- If cursor was after the collapsed region, adjust it up
2673+
elseif saved_cursor[1] > call.header_line + count then
2674+
saved_cursor[1] = saved_cursor[1] - count
2675+
end
2676+
vim.api.nvim_win_set_cursor(chat.winid, saved_cursor)
2677+
end
26362678
end
26372679

26382680
-- Expand a tool call's diff, inserting it below the diff label
@@ -2646,6 +2688,13 @@ function M:_expand_tool_call_diff(call)
26462688
return
26472689
end
26482690

2691+
-- Save cursor position before expansion (if configured)
2692+
local saved_cursor = nil
2693+
local preserve_cursor = Utils.should_preserve_cursor()
2694+
if preserve_cursor and chat.winid and vim.api.nvim_win_is_valid(chat.winid) then
2695+
saved_cursor = vim.api.nvim_win_get_cursor(chat.winid)
2696+
end
2697+
26492698
call.diff_lines = call.diff_lines or self:_build_tool_call_diff_lines(call.details)
26502699
local count = call.diff_lines and #call.diff_lines or 0
26512700
if count == 0 then
@@ -2664,6 +2713,15 @@ function M:_expand_tool_call_diff(call)
26642713

26652714
-- Update the diff label to show the collapse indicator
26662715
self:_update_tool_call_diff_label_line(call)
2716+
2717+
-- Restore cursor position (no default cursor movement for diff expansion)
2718+
if saved_cursor and chat.winid and vim.api.nvim_win_is_valid(chat.winid) then
2719+
-- Adjust cursor row if it was after the insertion point
2720+
if saved_cursor[1] > call.label_line then
2721+
saved_cursor[1] = saved_cursor[1] + count
2722+
end
2723+
vim.api.nvim_win_set_cursor(chat.winid, saved_cursor)
2724+
end
26672725
end
26682726

26692727
-- Collapse a tool call's diff, removing it from the buffer
@@ -2677,6 +2735,13 @@ function M:_collapse_tool_call_diff(call)
26772735
return
26782736
end
26792737

2738+
-- Save cursor position before collapsing (if configured)
2739+
local saved_cursor = nil
2740+
local preserve_cursor = Utils.should_preserve_cursor()
2741+
if preserve_cursor and chat.winid and vim.api.nvim_win_is_valid(chat.winid) then
2742+
saved_cursor = vim.api.nvim_win_get_cursor(chat.winid)
2743+
end
2744+
26802745
local count = call.diff_lines and #call.diff_lines or 0
26812746
if count == 0 then
26822747
call.diff_expanded = false
@@ -2696,6 +2761,19 @@ function M:_collapse_tool_call_diff(call)
26962761

26972762
-- Update the diff label to show the expand indicator again
26982763
self:_update_tool_call_diff_label_line(call)
2764+
2765+
-- Restore cursor position, adjusting if it was after the collapsed region
2766+
if saved_cursor and chat.winid and vim.api.nvim_win_is_valid(chat.winid) then
2767+
-- If cursor was inside the collapsed region, move it to the label
2768+
if saved_cursor[1] > call.label_line and saved_cursor[1] <= call.label_line + count then
2769+
saved_cursor[1] = call.label_line
2770+
saved_cursor[2] = 0
2771+
-- If cursor was after the collapsed region, adjust it up
2772+
elseif saved_cursor[1] > call.label_line + count then
2773+
saved_cursor[1] = saved_cursor[1] - count
2774+
end
2775+
vim.api.nvim_win_set_cursor(chat.winid, saved_cursor)
2776+
end
26992777
end
27002778

27012779
-- Toggle tool call details at the current cursor position in the chat window

‎lua/eca/utils.lua‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,15 @@ function M.should_start_diff_expanded()
189189
return diff_cfg.expanded == true
190190
end
191191

192+
---Check if cursor position should be preserved when expanding/collapsing tool calls
193+
---@return boolean
194+
function M.should_preserve_cursor()
195+
local chat_cfg = M.get_chat_config()
196+
local cfg = chat_cfg.tool_call or {}
197+
198+
return cfg.preserve_cursor == true
199+
end
200+
192201
---Get reasoning labels configuration
193202
---@return table
194203
function M.get_reasoning_labels()

0 commit comments

Comments
 (0)