use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;
use crate::buffer::Buffer;
use crate::render::{RenderedBuffer, StyledRange};
use crate::styling::style_to_ratatui;
pub struct EditorView;
impl EditorView {
pub fn gutter_width(buf_frame: Option<&RenderedBuffer>) -> u16 {
buf_frame
.map(|f| f.gutters.iter().map(|g| g.width).sum())
.unwrap_or(0)
}
pub fn render(buf: &Buffer, area: Rect, buf_frame: Option<&RenderedBuffer>, frame: &mut Frame) {
let gutters = buf_frame.map(|f| f.gutters.as_slice()).unwrap_or(&[]);
let decorators = buf_frame.map(|f| f.decorators.as_slice()).unwrap_or(&[]);
let mut constraints: Vec<Constraint> = gutters
.iter()
.map(|g| Constraint::Length(g.width))
.collect();
constraints.push(Constraint::Min(1));
let cols = Layout::default()
.direction(Direction::Horizontal)
.constraints(constraints)
.split(area);
let content_area = cols[cols.len() - 1];
for (i, gutter) in gutters.iter().enumerate() {
frame.render_widget(Paragraph::new(gutter.rows.clone()), cols[i]);
}
let start = buf.file_pos().row.min(buf.len_lines());
let visible_rows = content_area.height as usize;
let content: Vec<Line<'static>> = buf
.lines_at(start)
.take(visible_rows)
.enumerate()
.map(|(row, l)| {
let lnum = start + row;
let text = l.to_string();
let text = text.trim_end_matches(['\n', '\r']).to_string();
apply_decorators(lnum, text, decorators, content_area.width)
})
.collect();
frame.render_widget(Paragraph::new(content), content_area);
}
pub fn cursor(buf: &Buffer, area: Rect, buf_frame: Option<&RenderedBuffer>) -> (u16, u16) {
let gutter_w = Self::gutter_width(buf_frame);
let cur = buf.cursor_pos();
(area.x + gutter_w + cur.col, area.y + cur.row)
}
}
fn apply_decorators(
lnum: usize,
text: String,
decorators: &[crate::render::DecoratorRanges],
area_width: u16,
) -> Line<'static> {
let mut spans = vec![Span::raw(text)];
for d in decorators {
for r in &d.ranges {
if r.row != lnum {
continue;
}
spans = apply_range(spans, r, area_width);
}
}
Line::from(spans)
}
fn apply_range(spans: Vec<Span<'static>>, r: &StyledRange, area_width: u16) -> Vec<Span<'static>> {
let mut text: String = spans.iter().flat_map(|s| s.content.chars()).collect();
let cur_len = text.chars().count();
let pad_len = if r.pad_to_width {
(area_width as usize).max(cur_len)
} else {
cur_len
};
if pad_len > cur_len {
text.extend(std::iter::repeat_n(' ', pad_len - cur_len));
}
let chars: Vec<char> = text.chars().collect();
let end = if r.pad_to_width && r.len == 0 {
pad_len
} else {
(r.col + r.len).min(chars.len())
};
let start = r.col.min(chars.len());
let end = end.max(start);
let inherited = spans.first().map(|s| s.style).unwrap_or_default();
let highlight = inherited.patch(style_to_ratatui(&r.style));
let before: String = chars[..start].iter().collect();
let middle: String = chars[start..end].iter().collect();
let after: String = chars[end..].iter().collect();
let mut out = Vec::with_capacity(3);
if !before.is_empty() {
out.push(Span::styled(before, inherited));
}
if !middle.is_empty() {
out.push(Span::styled(middle, highlight));
}
if !after.is_empty() {
out.push(Span::styled(after, inherited));
}
if out.is_empty() {
out.push(Span::raw(String::new()));
}
out
}