eabeaf1bf8
Widgets (CodeEditor, TextField, Dialog) plus clipboard/text internals, extracted from the kiln workspace. No game-engine dependency. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
72 lines
2.3 KiB
Rust
72 lines
2.3 KiB
Rust
//! 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<char> = 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<char> = 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
|
|
}
|