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>
This commit is contained in:
+71
@@ -0,0 +1,71 @@
|
||||
//! 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
|
||||
}
|
||||
Reference in New Issue
Block a user