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
20 changes: 15 additions & 5 deletions crates/forge_main/src/zsh/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,11 +417,21 @@ mod tests {
use pretty_assertions::assert_eq;

let fixture = generate_zsh_plugin().unwrap();
let actual = fixture.contains(
" _forge_osc133_emit \"B\"\n _forge_osc133_emit \"C\"\n case \"$user_action\" in",
) && fixture.contains(
" local action_status=$?\n _forge_osc133_emit \"D;$action_status\"\n _forge_osc133_emit \"A\"\n _forge_reset",
);

// Verify OSC 133 B and C markers are emitted before the action dispatch
let actual = fixture.contains(" _forge_osc133_emit \"B\"\n _forge_osc133_emit \"C\"")
// The buffer is redisplayed with cursor at end so user input stays visible
&& fixture.contains("CURSOR=${#BUFFER}\n zle redisplay")
// The case dispatch follows the buffer redisplay
&& fixture.contains(" case \"$user_action\" in")
// _forge_reset pads with newlines before clearing to prevent ZLE from clearing
// conversation output lines (see helpers.zsh:139-157)
&& fixture.contains("for ((_i=1; _i<pad; _i++)); do print; done\n")
&& fixture.contains("BUFFER=\"\"\n CURSOR=0")
// D and A markers follow the action, with _forge_reset at the end
&& fixture.contains(
" local action_status=$?\n _forge_osc133_emit \"D;$action_status\"\n _forge_osc133_emit \"A\"\n _forge_reset",
);
let expected = true;
assert_eq!(actual, expected);
}
Expand Down
3 changes: 0 additions & 3 deletions shell-plugin/lib/dispatcher.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,6 @@ function forge-accept-line() {
# Add the original command to history before transformation
print -s -- "$original_buffer"

# CRITICAL: Move cursor to end so output doesn't overwrite
# Don't clear BUFFER yet - let _forge_reset do that after action completes
# This keeps buffer state consistent if Ctrl+C is pressed
CURSOR=${#BUFFER}
zle redisplay

Expand Down
11 changes: 11 additions & 0 deletions shell-plugin/lib/helpers.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ function _forge_select_model_pair_global() {
}

function _forge_reset() {
# Print newlines equal to the current buffer display line count so that
# ZLE's zrefresh() clears these padding lines instead of conversation
# output. When BUFFER spans multiple display lines (newlines or terminal
# wrap), ZLE tracks the multi-line state internally (olnct). On
# reset-prompt, zrefresh() compares olnct (old multi-line count) against
# nlnct (new count after clearing) and clears the delta lines. By
# printing padding here, the cleared lines are blank ones we inserted
# rather than the forge conversation output that precedes them.
local pad="${BUFFERLINES:-1}" _i
for ((_i=1; _i<pad; _i++)); do print; done

# Clear buffer and reset cursor position
BUFFER=""
CURSOR=0
Expand Down
Loading