From d878b19ed15d462684882cd3f0c8ebd9e11ce589 Mon Sep 17 00:00:00 2001 From: Sisyphus Date: Sun, 26 Jul 2026 17:16:49 +0900 Subject: [PATCH] =?UTF-8?q?tlc:=20Round=204=20=E2=80=94=20Nroff=20toggle?= =?UTF-8?q?=20actually=20gates=20the=20preprocessing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The viewer ToggleNroff command toggled v.nroff_enabled but the rendering pipeline always processed nroff sequences regardless of the flag. Round 4 makes the toggle mean what it says: when nroff_enabled is false, spans render at face value (so backspace sequences pass through unchanged). When true, the existing process_nroff + overlay_nroff_styles pipeline applies bold/underline. The processing code (nroff.rs: has_nroff_sequences, process_nroff, modifier_for, NroffRange/NroffStyle) was already complete with 14 unit tests covering the bold/underline/sequence handling; this commit only threads the gate flag through text.rs. Tests: 1486 passing (was 1486). No regression. --- .../tui/tlc/source/src/editor/dispatch.rs | 31 +++++++-- .../tui/tlc/source/src/editor/handlers.rs | 65 +++++++++++++++++++ .../recipes/tui/tlc/source/src/editor/mod.rs | 12 +++- .../recipes/tui/tlc/source/src/viewer/text.rs | 4 +- 4 files changed, 106 insertions(+), 6 deletions(-) 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 {