//! Small char/width/key helpers shared by the text widgets //! ([`code_editor`](crate::code_editor), [`text_field`](crate::text_field)). use ratatui::crossterm::event::{KeyEvent, KeyModifiers}; use unicode_width::UnicodeWidthChar; /// Columns a tab advances to (tab stop). pub(crate) const TAB_WIDTH: usize = 4; /// Byte offset of character index `col` within `s` (clamped to `s.len()`). pub(crate) fn byte_of(s: &str, col: usize) -> usize { s.char_indices().nth(col).map_or(s.len(), |(b, _)| b) } /// Display width of `c` at running display column `at` (tabs advance to the next /// [`TAB_WIDTH`] stop; wide characters are two cells; zero-width control chars are 0). pub(crate) fn char_cells(c: char, at: usize) -> usize { if c == '\t' { TAB_WIDTH - (at % TAB_WIDTH) } else { UnicodeWidthChar::width(c).unwrap_or(0) } } /// Display width (terminal cells) of the first `col` characters of `s`. pub(crate) fn display_width(s: &str, col: usize) -> usize { let mut w = 0; for c in s.chars().take(col) { w += char_cells(c, w); } w } /// Character index after moving one word forward from `col` in `line`: skip the current /// run of non-whitespace, then the following whitespace (lands on the next word's start). pub(crate) fn next_word(line: &str, col: usize) -> usize { let chars: Vec = line.chars().collect(); let n = chars.len(); let mut i = col; while i < n && !chars[i].is_whitespace() { i += 1; } while i < n && chars[i].is_whitespace() { i += 1; } i } /// Character index after moving one word backward from `col` in `line`: skip whitespace, /// then the preceding word (lands on that word's start). pub(crate) fn prev_word(line: &str, col: usize) -> usize { let chars: Vec = line.chars().collect(); let mut i = col; while i > 0 && chars[i - 1].is_whitespace() { i -= 1; } while i > 0 && !chars[i - 1].is_whitespace() { i -= 1; } i } /// Rewrites a `⌘`-modified key to its `ctrl` equivalent (terminals report Super for Cmd /// under the kitty protocol; edtui/our bindings key off ctrl). pub(crate) fn super_to_ctrl(mut key: KeyEvent) -> KeyEvent { if key.modifiers.contains(KeyModifiers::SUPER) { key.modifiers.remove(KeyModifiers::SUPER); key.modifiers.insert(KeyModifiers::CONTROL); } key }