diff --git a/local/recipes/tui/tlc/source/src/editor/mod.rs b/local/recipes/tui/tlc/source/src/editor/mod.rs index 80990d538d..250537f87a 100644 --- a/local/recipes/tui/tlc/source/src/editor/mod.rs +++ b/local/recipes/tui/tlc/source/src/editor/mod.rs @@ -164,6 +164,9 @@ pub struct Editor { /// offset) for the flash highlight, or `None` when the cursor is /// not on a bracket. bracket_flash: Option<(usize, usize)>, + /// Timestamp of the last mode change, or `None`. Used to flash + /// the mode tag in the status bar for ~300ms. + mode_flash: Option, /// In-buffer search engine — pattern, history, last match. The /// history backs the Alt-/ popup in the Find/Replace prompts. search: SearchState, @@ -277,6 +280,7 @@ impl Editor { #[cfg(feature = "syntect")] last_render_top: 0, bracket_flash: None, + mode_flash: None, search: SearchState::new(), history_popup_selected: None, cursor_shape: CursorShape::default(), @@ -326,6 +330,7 @@ impl Editor { #[cfg(feature = "syntect")] last_render_top: 0, bracket_flash: None, + mode_flash: None, search: SearchState::new(), history_popup_selected: None, cursor_shape: CursorShape::default(), @@ -821,6 +826,36 @@ impl Editor { history.get(idx).cloned() } + /// Trigger the mode-change flash. Call this whenever the + /// `mode` field is set to a new value. + pub fn trigger_mode_flash(&mut self) { + self.mode_flash = Some(std::time::Instant::now()); + } + + /// `Some(frac)` while the mode flash is active, where `frac` + /// is `1.0` just after the change and `0.0` fully expired. + /// `None` when no flash is active. + pub fn mode_flash_progress(&self) -> Option { + let start = self.mode_flash?; + const FLASH_MS: f32 = 300.0; + let elapsed_ms = start.elapsed().as_secs_f32() * 1000.0; + if elapsed_ms >= FLASH_MS { + return None; + } + let remaining = FLASH_MS - elapsed_ms; + Some((remaining / FLASH_MS).clamp(0.0, 1.0)) + } + + /// Set the editor mode and trigger the mode-change flash. + /// Use this instead of writing `self.mode = ...` directly so + /// the status bar flash stays in sync. + pub fn set_mode(&mut self, mode: Mode) { + if self.mode != mode { + self.mode = mode; + self.trigger_mode_flash(); + } + } + fn update_bracket_flash(&mut self) { let pos = self.cursor.position(); let text = self.buffer.as_string(); @@ -3698,5 +3733,19 @@ mod tests { let misspelled = e.find_next_misspelled(0); assert!(misspelled.is_none()); } + + #[test] + fn mode_flash_triggers_on_set_mode_and_expires() { + let mut e = make_empty(); + assert!(e.mode_flash_progress().is_none()); + e.set_mode(Mode::Normal); + assert!(e.mode_flash_progress().is_some()); + // Setting the same mode should NOT trigger a flash. + e.set_mode(Mode::Normal); + let frac = e.mode_flash_progress().unwrap(); + // No new timestamp was recorded, so the fraction is + // still the same (or slightly less, since time passed). + assert!(frac > 0.0 && frac <= 1.0); + } }