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())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Paint {
Plain,
CursorLine,
Bracket,
Selection,
PrimarySelection,
}
impl Paint {
fn style(self, theme: &ThemeColors) -> Style {
match self {
Paint::Plain => Style::default().fg(theme.fg).bg(theme.bg),
Paint::CursorLine => Style::default().fg(theme.fg).bg(theme.cursor_line_bg),
Paint::Bracket => Style::default()
.fg(theme.bracket_match_fg)
.bg(theme.bracket_match_bg),
Paint::Selection => Style::default()
.fg(theme.selection_fg)
.bg(theme.selection_bg),
Paint::PrimarySelection => Style::default()
.fg(theme.selection_fg)
.bg(theme.selection_primary_bg),
}
}
}
pub struct LineStyle<'a> {
pub line: usize,
pub left_col: usize,
pub width: usize,
pub tab_width: usize,
pub selections: &'a [Selection],
pub primary: Selection,
pub cursor_line: bool,
pub brackets: Option<(Position, Position)>,
pub theme: &'a ThemeColors,
}
pub fn styled_line(text: &str, ctx: &LineStyle) -> Line<'static> {
let (visible, skipped) = window(text, ctx.left_col, ctx.tab_width);
let mut spans: Vec<Span<'static>> = Vec::new();
let mut current = String::new();
let mut current_paint: Option<Paint> = None;
let mut columns = 0usize;
for (offset, grapheme) in visible.graphemes(true).enumerate() {
let position = Position {
line: ctx.line,
col: skipped + offset,
};
let paint = paint_for(position, ctx);
if current_paint != Some(paint) && !current.is_empty() {
let style = current_paint.unwrap_or(Paint::Plain).style(ctx.theme);
spans.push(Span::styled(std::mem::take(&mut current), style));
}
current_paint = Some(paint);
current.push_str(grapheme);
columns += display_width_with_tabs(grapheme, ctx.tab_width).max(1);
}
if !current.is_empty() {
let style = current_paint.unwrap_or(Paint::Plain).style(ctx.theme);
spans.push(Span::styled(current, style));
}
if ctx.cursor_line && columns < ctx.width {
spans.push(Span::styled(
" ".repeat(ctx.width - columns),
Paint::CursorLine.style(ctx.theme),
));
}
Line::from(spans)
}
fn paint_for(position: Position, ctx: &LineStyle) -> Paint {
if ctx.selections.iter().any(|s| s.contains(position)) {
if ctx.primary.contains(position) {
Paint::PrimarySelection
} else {
Paint::Selection
}
} else if ctx
.brackets
.is_some_and(|(open, close)| open == position || close == position)
{
Paint::Bracket
} else if ctx.cursor_line {
Paint::CursorLine
} else {
Paint::Plain
}
}