Files
kiln-ui/src/text.rs
T
randrews eabeaf1bf8 Extract kiln-ui: standalone game-agnostic ratatui widget toolkit
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>
2026-07-10 23:42:48 -05:00

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
}