use ratatui::layout::Rect;
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use crate::components::text::line_width;
use crate::geometry::Size;
use crate::highlight::CodeHighlighter;
use crate::style::Theme;
use crate::surface::Surface;
use crate::view::{RenderCtx, View};
const RAIL: &str = "▏ ";
pub(crate) fn code_block_lines(
lang: &str,
body: &[&str],
theme: &Theme,
highlighter: CodeHighlighter,
show_label: bool,
) -> Vec<Line<'static>> {
let code = &theme.code;
let rail_style = Style::default().fg(code.label).bg(code.background);
let plain = Style::default().fg(code.text).bg(code.background);
let mut out = Vec::new();
let label = lang.trim();
if show_label && !label.is_empty() {
out.push(Line::from(vec![Span::styled(
format!("{RAIL}{label}"),
Style::default()
.fg(code.label)
.add_modifier(Modifier::ITALIC),
)]));
}
let highlighted = highlighter.highlight(label, body, theme);
for (row, source) in body.iter().enumerate() {
let mut spans = vec![Span::styled(RAIL.to_string(), rail_style)];
match highlighted.as_ref().and_then(|lines| lines.get(row)) {
Some(cells) if !cells.is_empty() => {
for cell in cells {
spans.push(Span::styled(
cell.content.to_string(),
cell.style.bg(code.background),
));
}
}
_ => spans.push(Span::styled((*source).to_string(), plain)),
}
out.push(Line::from(spans));
}
out
}
pub struct CodeBlock<'a> {
lang: String,
body: Vec<String>,
highlighter: CodeHighlighter<'a>,
show_label: bool,
}
impl<'a> CodeBlock<'a> {
pub fn new(lang: impl Into<String>, source: impl AsRef<str>) -> Self {
Self {
lang: lang.into(),
body: source.as_ref().split('\n').map(str::to_owned).collect(),
highlighter: CodeHighlighter::Plain,
show_label: true,
}
}
pub fn highlighter(mut self, highlighter: &'a dyn crate::highlight::Highlighter) -> Self {
self.highlighter = CodeHighlighter::With(highlighter);
self
}
pub fn label(mut self, show: bool) -> Self {
self.show_label = show;
self
}
fn lines(&self, theme: &Theme) -> Vec<Line<'static>> {
let body: Vec<&str> = self.body.iter().map(String::as_str).collect();
code_block_lines(&self.lang, &body, theme, self.highlighter, self.show_label)
}
}
impl View for CodeBlock<'_> {
fn measure(&self, available: Size) -> Size {
let theme = Theme::default();
let lines = self.lines(&theme);
let width = lines
.iter()
.map(|l| line_width(l))
.max()
.unwrap_or(0)
.min(available.width);
Size::new(width, lines.len() as u16)
}
fn render(&self, area: Rect, surface: &mut Surface, ctx: &RenderCtx) {
let lines = self.lines(ctx.theme);
for (row, line) in lines.iter().enumerate() {
let y = area.y.saturating_add(row as u16);
if y >= area.bottom() {
break;
}
let mut x = area.x;
for span in &line.spans {
if x >= area.right() {
break;
}
x = surface.set_string(x, y, span.content.as_ref(), span.style);
}
}
}
}