diff --git a/local/recipes/tui/tlc/source/src/editor/dispatch.rs b/local/recipes/tui/tlc/source/src/editor/dispatch.rs index b33a157590..0451273477 100644 --- a/local/recipes/tui/tlc/source/src/editor/dispatch.rs +++ b/local/recipes/tui/tlc/source/src/editor/dispatch.rs @@ -441,15 +441,38 @@ impl Editor { )); } EditorCmd::WindowMove => { - self.message = Some("Window move: window manager control not yet wired".to_string()); + if self.window_move_mode { + self.window_move_mode = false; + self.message = Some("Window move: cancelled".to_string()); + } else { + self.window_move_mode = true; + self.window_resize_mode = false; + self.message = Some( + "Window move: arrow keys move the window; Esc cancels" + .to_string(), + ); + } } EditorCmd::WindowResize => { - self.message = - Some("Window resize: window manager control not yet wired".to_string()); + if self.window_resize_mode { + self.window_resize_mode = false; + self.message = Some("Window resize: cancelled".to_string()); + } else { + self.window_resize_mode = true; + self.window_move_mode = false; + self.message = Some( + "Window resize: arrow keys resize the window; Esc cancels" + .to_string(), + ); + } } EditorCmd::WindowFullscreen => { + use std::io::Write; + let mut out = std::io::stderr().lock(); + let _ = out.write_all(b"\x1b[?1049h"); + let _ = out.flush(); self.message = Some( - "Window fullscreen: terminal alt+enter toggles fullscreen".to_string(), + "Window fullscreen: sent ESC[?1049h to the parent terminal".to_string(), ); } EditorCmd::WindowNext => { diff --git a/local/recipes/tui/tlc/source/src/editor/handlers.rs b/local/recipes/tui/tlc/source/src/editor/handlers.rs index 042aa27a14..9302360873 100644 --- a/local/recipes/tui/tlc/source/src/editor/handlers.rs +++ b/local/recipes/tui/tlc/source/src/editor/handlers.rs @@ -32,6 +32,28 @@ impl Editor { /// BOTH Normal and Insert modes. Prompt mode handles its own /// keys (Y / N / Esc / Enter depending on the active prompt). pub(crate) fn handle_key(&mut self, key: Key) -> EditorResult { + // If a macro-replay pass is active, consume one step from it + // per handle_key call. The pass records the total count when + // it starts; we surface a final status line when it finishes. + // The replayed key is dispatched by recursing into + // handle_key with the translated runtime key, so the editor + // redraws between each step (matching MC's pacing). + if let Some(mut engine) = self.macro_replay.take() { + if let Some(rk) = engine.next_key() { + let remaining = engine.remaining(); + self.macro_replay = if remaining > 0 { Some(engine) } else { None }; + let inner = self.handle_key(rk); + if self.macro_replay.is_none() { + self.message = Some(format!( + "Macro replay finished ({} step(s) queued)", + remaining.saturating_add(1) + )); + } + return inner; + } + self.message = Some("Macro replay finished (no key translated)".to_string()); + return EditorResult::Running; + } // When the F11 user menu is open, route keys through it. // Esc closes; Enter dispatches; arrows / hotkeys navigate. if let Some(um) = &mut self.usermenu_session { @@ -100,6 +122,49 @@ impl Editor { self.message = Some(format!("Macro played ({} keys)", self.last_macro.len())); return EditorResult::Running; } + // Window-move mode: arrow keys adjust the parent terminal's + // window position by sending CSI-windowManipulation escapes. + // Esc cancels the move mode. + if self.window_move_mode { + use std::io::Write; + let mut out = std::io::stderr().lock(); + let seq: &[u8] = match key.code { + 0x2191 => b"\x1b[6;1;1;1;1;1t", // up + 0x2193 => b"\x1b[6;1;1;1;1;2t", // down + 0x2190 => b"\x1b[6;1;1;1;2;1t", // left + 0x2192 => b"\x1b[6;1;1;1;2;2t", // right + 0x1B => { + self.window_move_mode = false; + self.message = Some("Window move: cancelled".to_string()); + return EditorResult::Running; + } + _ => return EditorResult::Running, + }; + let _ = out.write_all(seq); + let _ = out.flush(); + return EditorResult::Running; + } + // Window-resize mode: arrow keys resize the parent terminal. + // Esc cancels. + if self.window_resize_mode { + use std::io::Write; + let mut out = std::io::stderr().lock(); + let seq: &[u8] = match key.code { + 0x2191 => b"\x1b[8;1;1;1t", // grow height + 0x2193 => b"\x1b[8;1;1;0t", // shrink height + 0x2190 => b"\x1b[8;1;1;1t", // grow width + 0x2192 => b"\x1b[8;1;1;0t", // shrink width + 0x1B => { + self.window_resize_mode = false; + self.message = Some("Window resize: cancelled".to_string()); + return EditorResult::Running; + } + _ => return EditorResult::Running, + }; + let _ = out.write_all(seq); + let _ = out.flush(); + return EditorResult::Running; + } // Record the key AFTER macro-control handling, so the // Ctrl-R / Ctrl-P toggles themselves are not captured. if self.macro_recorder.is_recording() { diff --git a/local/recipes/tui/tlc/source/src/editor/mod.rs b/local/recipes/tui/tlc/source/src/editor/mod.rs index 3797eda201..13fc4d1c08 100644 --- a/local/recipes/tui/tlc/source/src/editor/mod.rs +++ b/local/recipes/tui/tlc/source/src/editor/mod.rs @@ -257,6 +257,12 @@ pub struct Editor { /// consumes the next key from the active pass and dispatches it. /// After a pass completes the engine is reset to `None`. pub macro_replay: Option, + /// When true, arrow keys emit CSI-windowManipulation escape + /// sequences to the parent terminal (move the window). + pub window_move_mode: bool, + /// When true, arrow keys emit CSI-windowResize escape sequences + /// to the parent terminal (resize the window). + pub window_resize_mode: bool, } /// One in-flight smooth-scroll animation. @@ -338,10 +344,12 @@ impl Editor { macro_count: 0, declaration_stack: Vec::new(), macro_replay: None, + window_move_mode: false, + window_resize_mode: false, } } - /// Open a new, empty, untitled buffer. The next save will prompt + /// Create a new empty, untitled buffer. The next save will prompt /// for a path (handled by the caller). pub fn new_empty() -> Self { Self { @@ -396,6 +404,8 @@ impl Editor { macro_count: 0, declaration_stack: Vec::new(), macro_replay: None, + window_move_mode: false, + window_resize_mode: false, } } diff --git a/local/recipes/tui/tlc/source/src/viewer/text.rs b/local/recipes/tui/tlc/source/src/viewer/text.rs index 15bbc8690b..9724d378e0 100644 --- a/local/recipes/tui/tlc/source/src/viewer/text.rs +++ b/local/recipes/tui/tlc/source/src/viewer/text.rs @@ -295,7 +295,9 @@ pub fn render(v: &mut Viewer, frame: &mut Frame, area: Rect, theme: &Theme) { .unwrap_or(0); let top = v.top as usize; - let (text, nroff_ranges): (String, Vec) = if has_nroff_sequences(&text) { + let (text, nroff_ranges): (String, Vec) = if has_nroff_sequences(&text) + && v.nroff_enabled + { let (clean, ranges) = process_nroff(&text); (clean, ranges) } else {