use ratatui::style::Style;
use ratatui::text::{Line, Span};
use typ_buffer::{Position, Selection, display_width_with_tabs};
use typ_core::ThemeColors;
use unicode_segmentation::UnicodeSegmentation;
pub fn window(text: &str, left_col: usize, tab_width: usize) -> (&str, usize) {
if left_col == 0 {
return (text, 0);
}
let mut column = 0usize;
for (skipped, (byte, grapheme)) in text.grapheme_indices(true).enumerate() {
if column >= left_col {
return (&text[byte..], skipped);
}
column += if grapheme == "\t" {
tab_width - (column % tab_width)
} else {
display_width_with_tabs(grapheme, tab_width).max(1)
};
}
("", text.graphemes(true).count())
}
pub fn styled_line(
text: &str,
line_index: usize,
left_col: usize,
tab_width: usize,
selections: &[Selection],
theme: &ThemeColors,
) -> Line<'static> {
let plain = Style::default().fg(theme.fg).bg(theme.bg);
let selected = Style::default()
.fg(theme.selection_fg)
.bg(theme.selection_bg);
let (visible, skipped) = window(text, left_col, tab_width);
let mut spans: Vec<Span<'static>> = Vec::new();
let mut current = String::new();
let mut current_selected: Option<bool> = None;
for (offset, grapheme) in visible.graphemes(true).enumerate() {
let position = Position {
line: line_index,
col: skipped + offset,
};
let is_selected = selections.iter().any(|s| s.contains(position));
if current_selected != Some(is_selected) && !current.is_empty() {
let style = if current_selected == Some(true) {
selected
} else {
plain
};
spans.push(Span::styled(std::mem::take(&mut current), style));
}
current_selected = Some(is_selected);
current.push_str(grapheme);
}
if !current.is_empty() {
let style = if current_selected == Some(true) {
selected
} else {
plain
};
spans.push(Span::styled(current, style));
}
Line::from(spans)
}