use ratatui::style::Style;
use ratatui::text::Span;
pub const CURSOR_BLOCK: char = '█';
pub fn text_field_spans(
value: &str,
cursor: usize,
visible: bool,
value_style: Style,
cursor_style: Style,
) -> [Span<'static>; 3] {
let (pre, rest) = split_at_char(value, cursor);
let mut rest_chars = rest.chars();
let under = rest_chars.next();
let post: String = rest_chars.collect();
let cell_style = if visible { cursor_style } else { value_style };
[
Span::styled(pre, value_style),
Span::styled(under.unwrap_or(' ').to_string(), cell_style),
Span::styled(post, value_style),
]
}
fn split_at_char(s: &str, cursor: usize) -> (String, String) {
let byte_idx = s
.char_indices()
.nth(cursor)
.map(|(b, _)| b)
.unwrap_or(s.len());
(s[..byte_idx].to_owned(), s[byte_idx..].to_owned())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_at_char_mid_and_past_end() {
assert_eq!(split_at_char("hello", 2), ("he".into(), "llo".into()));
assert_eq!(split_at_char("hi", 5), ("hi".into(), String::new()));
}
#[test]
fn split_at_char_respects_char_boundaries() {
assert_eq!(split_at_char("é!", 1), ("é".into(), "!".into()));
}
#[test]
fn text_field_slot_is_constant_width_across_blink() {
let vis = text_field_spans("note", 2, true, Style::default(), Style::default());
let hid = text_field_spans("note", 2, false, Style::default(), Style::default());
let width = |spans: &[Span<'static>]| -> usize {
spans.iter().map(|s| s.content.chars().count()).sum()
};
assert_eq!(width(&vis), width(&hid));
assert_eq!(vis[1].content.as_ref(), "t");
assert_eq!(hid[1].content.as_ref(), "t");
}
#[test]
fn text_field_cursor_past_end_is_a_space_cell() {
let spans = text_field_spans("hi", 2, true, Style::default(), Style::default());
let text: String = spans.iter().map(|s| s.content.as_ref()).collect();
assert_eq!(text, "hi ");
assert_eq!(spans[1].content.as_ref(), " ");
assert!(spans[2].content.is_empty());
}
}