use std::ops::Range;
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Position};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Clear, Paragraph};
use syntect::easy::HighlightLines;
use super::{Editor, Mode, SyntaxHighlighter, line_selection_range, line_text};
pub(super) fn draw(
frame: &mut Frame<'_>,
app: &mut Editor,
syntax: &SyntaxHighlighter,
mode: Mode,
) {
let full_area = frame.area();
frame.render_widget(Clear, full_area);
let area = full_area;
app.last_drawn_height = area.height;
app.last_drawn_top = area.y;
let block = Block::default()
.title(format!(" {}: {} ", mode.title(), app.path.display()))
.borders(Borders::ALL);
let inner = block.inner(area);
frame.render_widget(block, area);
let [text_area, status_area] =
Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(inner);
let digits = app.line_count().max(1).to_string().len();
let prefix_width = digits + 2;
let text_width = text_area.width.saturating_sub(prefix_width as u16).max(1) as usize;
let text_height = text_area.height.max(1) as usize;
app.ensure_cursor_visible(text_height, text_width);
let lines = render_lines(app, syntax, text_height, text_width, prefix_width);
frame.render_widget(Paragraph::new(lines), text_area);
let dirty = if app.dirty { " *" } else { "" };
let status = Line::from(vec![
Span::styled(
format!("{}:{}{} ", app.cursor_line + 1, app.cursor_col + 1, dirty),
Style::default().fg(Color::Cyan),
),
Span::raw(app.status.clone()),
]);
frame.render_widget(Paragraph::new(status), status_area);
if app.cursor_line >= app.scroll_y && app.cursor_line < app.scroll_y + text_height {
let y = text_area.y + (app.cursor_line - app.scroll_y) as u16;
let x =
text_area.x + prefix_width as u16 + app.cursor_col.saturating_sub(app.scroll_x) as u16;
if x < text_area.x + text_area.width && y < text_area.y + text_area.height {
frame.set_cursor_position(Position::new(x, y));
}
}
}
fn render_lines(
app: &Editor,
syntax: &SyntaxHighlighter,
text_height: usize,
text_width: usize,
prefix_width: usize,
) -> Vec<Line<'static>> {
let mut highlighter = HighlightLines::new(syntax.syntax(), &syntax.theme);
let end = (app.scroll_y + text_height).min(app.line_count());
let mut output = Vec::with_capacity(text_height);
let active_region = app.active_region();
for idx in app.scroll_y..end {
let line = line_text(&app.buffer, idx);
let line_region = line_selection_range(app, idx, active_region.as_ref());
let highlighted = highlighter
.highlight_line(&line, &syntax.syntax_set)
.unwrap_or_else(|_| vec![(syntect::highlighting::Style::default(), line.as_str())]);
let mut spans = vec![Span::styled(
format!("{:>width$} ", idx + 1, width = prefix_width - 1),
Style::default().fg(Color::DarkGray),
)];
spans.extend(slice_highlighted_line(
highlighted,
app.scroll_x,
text_width,
line_region,
));
output.push(Line::from(spans));
}
while output.len() < text_height {
output.push(Line::from(vec![Span::styled(
"~",
Style::default().fg(Color::DarkGray),
)]));
}
output
}
fn slice_highlighted_line(
highlighted: Vec<(syntect::highlighting::Style, &str)>,
start: usize,
width: usize,
selection: Option<Range<usize>>,
) -> Vec<Span<'static>> {
let end = start.saturating_add(width);
let mut spans = Vec::new();
let mut pos = 0usize;
for (style, text) in highlighted {
let base_style = syntect_style(style);
for ch in text.chars() {
if pos >= start && pos < end {
let style = if selection
.as_ref()
.is_some_and(|selection| selection.contains(&pos))
{
base_style.bg(Color::DarkGray)
} else {
base_style
};
spans.push(Span::styled(ch.to_string(), style));
}
pos += 1;
}
if pos >= end {
break;
}
}
spans
}
fn syntect_style(style: syntect::highlighting::Style) -> Style {
let fg = style.foreground;
let mut out = Style::default().fg(Color::Rgb(fg.r, fg.g, fg.b));
if style
.font_style
.contains(syntect::highlighting::FontStyle::BOLD)
{
out = out.add_modifier(Modifier::BOLD);
}
if style
.font_style
.contains(syntect::highlighting::FontStyle::ITALIC)
{
out = out.add_modifier(Modifier::ITALIC);
}
if style
.font_style
.contains(syntect::highlighting::FontStyle::UNDERLINE)
{
out = out.add_modifier(Modifier::UNDERLINED);
}
out
}