diff --git a/src/terminal/view.rs b/src/terminal/view.rs index e9739d3d..5d220805 100644 --- a/src/terminal/view.rs +++ b/src/terminal/view.rs @@ -2256,6 +2256,7 @@ impl TerminalView { let key = ks.key.as_str(); self.cursor_visible = true; self.jump_to_prompt(); + self.adopt_typeahead(); let aliased; let ks = if m.control && !m.platform && !m.alt && matches!(key, "p" | "n") { @@ -3945,16 +3946,22 @@ impl TerminalView { } } - /// Take the record into the editor without paying the wipe yet. + /// Take the record into the editor without paying the wipe yet. Every door + /// into the editor opens with this. /// /// `at_prompt` comes back on the `D` mark, a whole prompt draw ahead of the /// `B` that arms `zle_reading`, and this editor is live for that whole /// window. Everything it offers rewrites the line — history recall and the /// ghost suggestion replace it wholesale, ⌃U empties it, completion filters /// on it — so the line has to be whole *before* those run, not stitched - /// back together at submit time in front of whatever replaced it. The `^U` - /// stays owed until `flush_typeahead`, which keeps it where it has always - /// been on the wire: immediately before the line. + /// back together at submit time in front of whatever replaced it. Folding + /// it in that late made `↑` then Enter run the recalled entry with the gap + /// text glued to its front, and ⌃U then Enter bring back the text ⌃U had + /// just cleared. + /// + /// The `^U` stays owed until `flush_typeahead`, which keeps it where it has + /// always been on the wire: immediately before the line. Sending it here + /// instead would put it out before the shell's own editor is reading. fn adopt_typeahead(&mut self) { if let Some(seed) = self.typeahead.adopt() { self.cmd.prepend_str(&seed); @@ -4072,12 +4079,12 @@ impl TerminalView { // The shell is still holding the recorded text on its own line, and the // ^U that erases it has not gone out yet: `at_prompt` comes back on the // `D` mark, before the prompt is even drawn, while the wipe waits for - // `B`. `adopt_typeahead` has normally already folded the seed into the - // line by now, and this pays the wipe it left owed; on the frame where - // it has not, the drain here still puts the seed back the way every - // other drain does. Dropping it submitted only what was typed after the - // handover, and an empty command when that was nothing, which is the - // blank line #433 reports. + // `B`. The key that got here has folded the seed into the line already, + // so this is usually just paying the wipe that left owed; where nothing + // has, the drain still puts the seed back the way every other drain + // does. Dropping it submitted only what was typed after the handover, + // and an empty command when that was nothing, which is the blank line + // #433 reports. self.flush_typeahead(); let line = self.cmd.text(); if !line.trim().is_empty() { @@ -4836,6 +4843,7 @@ impl TerminalView { return; } if self.input_active() { + self.adopt_typeahead(); self.cmd.insert_str(text); self.history_nav = None; self.editor_goal_col = None; @@ -6360,8 +6368,6 @@ impl Render for TerminalView { } if self.terminal.zle_reading() { self.flush_typeahead(); - } else { - self.adopt_typeahead(); } } let entity = cx.entity(); @@ -14054,17 +14060,6 @@ mod prompt_handover_tests { .unwrap(), "this is the D-to-B window: the shell is not reading its line yet" ); - settle( - cx, - window, - "the editor adopts the line the shell is holding", - |view| view.cmd.text() == text, - ); - assert_eq!( - drain(daemon), - Vec::::new(), - "adopting the line owes the wipe, it does not send it early" - ); } #[gpui::test] @@ -14107,6 +14102,42 @@ mod prompt_handover_tests { ); } + /// The editor takes the held line over the moment it is touched, before it + /// edits anything — and takes it over without putting the wipe on the wire, + /// which is still the shell's line editor's to receive when it starts + /// reading. + #[gpui::test] + fn the_editor_takes_the_held_line_over_before_it_edits_it(cx: &mut TestAppContext) { + let (window, mut daemon) = harness(cx); + typed_into_the_gap_then_handed_back(cx, &window, &mut daemon, "echo"); + + // `home` moves the caret and nothing else: any editor key is enough. + press(&window, cx, "home"); + cx.run_until_parked(); + window + .update(cx, |view, _, _| { + assert_eq!( + view.cmd.text(), + "echo", + "the line the shell is sitting on is the editor's line now" + ); + }) + .unwrap(); + assert_eq!( + drain(&mut daemon), + Vec::::new(), + "taking the line over owes the wipe, it does not send it early" + ); + + press(&window, cx, "enter"); + cx.run_until_parked(); + assert_eq!( + drain(&mut daemon), + b"\x15echo\r".to_vec(), + "the owed wipe is paid on submit, still in front of the line" + ); + } + /// The half of the window the seed alone does not cover: the editor is /// live, so the user can *replace* the line before submitting it. Recalling /// history and pressing Enter has to run the entry recalled — not that