use std::fmt::Write as _;
use bat::line_range::{LineRange, LineRanges};
use bat::{Input, PrettyPrinter};
use super::text::truncate_width;
use super::{path, terminal_width};
fn preserve_trailing(text: &str, out: String) -> String {
if text.ends_with('\n') {
out
} else {
out.trim_end_matches('\n').to_string()
}
}
pub fn markdown(text: &str) {
super::out(&render_markdown(text));
}
fn render_markdown(text: &str) -> String {
if let Some(rendered) = render_plain_with_bat("markdown.md", text.as_bytes()) {
return preserve_trailing(text, rendered);
}
text.to_string()
}
pub fn code(path: &str, text: &str, first_line: usize) -> String {
preview_block(path, text, first_line)
}
pub fn code_lines(path: &str, lines: &[(usize, &str)]) -> String {
if lines.is_empty() {
return preview_block(path, "", 1);
}
let title = if path.is_empty() { "text" } else { path };
let max_width = terminal_width().saturating_sub(4).max(40);
let mut out = String::new();
let _ = writeln!(out, "{}", truncate_width(&block_title(title), max_width));
let first_line = lines
.iter()
.map(|(line, _)| *line)
.min()
.unwrap_or(1)
.max(1);
let last_line = lines
.iter()
.map(|(line, _)| *line)
.max()
.unwrap_or(first_line)
.max(first_line);
let mut content = String::new();
for line_number in first_line..=last_line {
if let Some((_, text)) = lines.iter().find(|(line, _)| *line == line_number) {
content.push_str(text);
}
content.push('\n');
}
let content = offset_preview_content(&content, first_line);
let ranges = LineRanges::from(
lines
.iter()
.map(|(line, _)| LineRange::new((*line).max(1), (*line).max(1)))
.collect::<Vec<_>>(),
);
let rendered = render_with_bat(title, content.as_bytes(), ranges)
.unwrap_or_else(|| fallback_preview_lines(lines));
out.push_str(rendered.trim_end());
out.trim_end().to_string()
}
pub fn text_block(title: &str, text: &str) -> String {
preview_block(title, text, 1)
}
pub fn block_title(title: &str) -> String {
path(format_args!("── {title}"))
}
fn preview_block(title: &str, text: &str, first_line: usize) -> String {
let title = if title.is_empty() { "text" } else { title };
let max_width = terminal_width().saturating_sub(4).max(40);
let mut out = String::new();
let _ = writeln!(out, "{}", truncate_width(&block_title(title), max_width));
let content = offset_preview_content(text, first_line);
let last_line = first_line.saturating_add(text.lines().count().max(1).saturating_sub(1));
let ranges = LineRanges::from(vec![LineRange::new(first_line.max(1), last_line.max(1))]);
let rendered = render_with_bat(title, content.as_bytes(), ranges)
.unwrap_or_else(|| fallback_preview_text(text));
out.push_str(rendered.trim_end());
out.trim_end().to_string()
}
fn render_with_bat(title: &str, bytes: &[u8], ranges: LineRanges) -> Option<String> {
render_bat(title, bytes, Some(ranges))
}
fn render_plain_with_bat(title: &str, bytes: &[u8]) -> Option<String> {
render_bat(title, bytes, None)
}
fn render_bat(title: &str, bytes: &[u8], ranges: Option<LineRanges>) -> Option<String> {
let mut out = String::new();
let mut printer = PrettyPrinter::new();
printer.input(Input::from_bytes(bytes).name(title));
if let Some(ranges) = ranges {
printer.line_ranges(ranges);
}
printer.print_with_writer(Some(&mut out)).ok()?;
Some(out)
}
fn offset_preview_content(text: &str, first_line: usize) -> String {
let mut content = "\n".repeat(first_line.saturating_sub(1));
content.push_str(text);
if text.is_empty() {
content.push('\n');
}
content
}
fn fallback_preview_text(text: &str) -> String {
text.to_string()
}
fn fallback_preview_lines(lines: &[(usize, &str)]) -> String {
let mut out = String::new();
for (line, text) in lines {
let _ = writeln!(out, "{:>4} │ {}", line, text.trim_end_matches('\n'));
}
out
}
pub fn diff(text: &str) -> String {
if let Some(rendered) = render_plain_with_bat("changes.diff", text.as_bytes()) {
return preserve_trailing(text, rendered);
}
text.to_string()
}