From 7d83999f0619119b8ffd997494be0104d0611f9d Mon Sep 17 00:00:00 2001 From: reuben olinsky Date: Wed, 10 Jun 2026 23:19:45 -0700 Subject: [PATCH] fix: re-use prompt rows after a quiet buffer editor The buffer-editor success path re-initialized the prompt position from scratch; with the cursor still sitting after the old buffer text (column > 0), select_prompt_row would start a new prompt one row down, leaving a stale duplicate prompt row above. Capture the painter's suspended state before spawning the editor and re-initialize with it: an editor that wrote nothing to the tty leaves the cursor inside the previous prompt rows and re-uses them (in-place repaint, as before this branch); an editor that printed output moves the cursor out of range and gets a fresh prompt below that output. Found by the PTY e2e suite: editor_replaces_buffer regressed on this branch while editor_that_prints_to_tty_does_not_corrupt_repaint regressed on main. Co-Authored-By: Claude Fable 5 --- src/engine.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index e055a16b..1bab9178 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1985,6 +1985,12 @@ impl Reedline { let mut file = File::create(temp_file)?; write!(file, "{}", self.editor.get_buffer())?; } + // Capture the prompt's screen range so that an editor + // which leaves the cursor untouched (e.g. a GUI editor, + // or one that wrote nothing to the tty) re-uses the + // existing prompt rows instead of starting a new prompt + // a row below the old one. + let suspended_state = self.painter.state_before_suspension(); { let mut child = command.spawn()?; // The child owns the tty now; invalidate eagerly so @@ -1995,12 +2001,15 @@ impl Reedline { } // On the success path, re-initialize position and size - // from scratch (covers a resize-during-editor with no - // SIGWINCH). On query failure, the eager invalidate - // above is our floor — losing the size refresh is - // acceptable; losing the user's edited buffer below - // is not. - let _ = self.painter.initialize_prompt_position(None); + // (covers a resize-during-editor with no SIGWINCH). If + // the editor moved the cursor out of the prompt's rows + // (it printed output), a fresh prompt starts below that + // output. On query failure, the eager invalidate above + // is our floor — losing the size refresh is acceptable; + // losing the user's edited buffer below is not. + let _ = self + .painter + .initialize_prompt_position(Some(&suspended_state)); let res = std::fs::read_to_string(temp_file)?; let res = res.trim_end().to_string();