use std::io::{Result, Write};
use anstyle::Style;
use pulldown_cmark::{CodeBlockKind, HeadingLevel};
use syntect::highlighting::HighlightState;
use syntect::parsing::{ParseState, ScopeStack};
use textwrap::core::{display_width, Word};
use textwrap::WordSeparator;
use crate::references::*;
use crate::render::data::{CurrentLine, LinkReferenceDefinition};
use crate::render::highlighting::HIGHLIGHTER;
use crate::render::state::*;
use crate::terminal::capabilities::{MarkCapability, StyleCapability, TerminalCapabilities};
use crate::terminal::TerminalSize;
use crate::theme::CombineStyle;
use crate::Theme;
use crate::{Environment, Settings};
pub fn write_indent<W: Write>(writer: &mut W, level: u16) -> Result<()> {
write!(writer, "{}", " ".repeat(level as usize))
}
pub fn write_styled<W: Write, S: AsRef<str>>(
writer: &mut W,
capabilities: &TerminalCapabilities,
style: &Style,
text: S,
) -> Result<()> {
match capabilities.style {
None => write!(writer, "{}", text.as_ref()),
Some(StyleCapability::Ansi(ansi)) => ansi.write_styled(writer, style, text),
}
}
fn write_remaining_lines<W: Write>(
writer: &mut W,
capabilities: &TerminalCapabilities,
style: &Style,
indent: usize,
mut buffer: String,
next_lines: &[&[Word]],
last_line: &[Word],
) -> Result<CurrentLine> {
writeln!(writer)?;
write_indent(writer, indent as u16)?;
for line in next_lines {
match line.split_last() {
None => {
}
Some((last, heads)) => {
for word in heads {
buffer.push_str(word.word);
buffer.push_str(word.whitespace);
}
buffer.push_str(last.word);
write_styled(writer, capabilities, style, &buffer)?;
writeln!(writer)?;
write_indent(writer, indent as u16)?;
buffer.clear();
}
};
}
match last_line.split_last() {
None => {
Ok(CurrentLine::empty())
}
Some((last, heads)) => {
for word in heads {
buffer.push_str(word.word);
buffer.push_str(word.whitespace);
}
buffer.push_str(last.word);
write_styled(writer, capabilities, style, &buffer)?;
Ok(CurrentLine {
length: textwrap::core::display_width(&buffer),
trailing_space: Some(last.whitespace.to_owned()),
})
}
}
}
pub fn write_styled_and_wrapped<W: Write, S: AsRef<str>>(
writer: &mut W,
capabilities: &TerminalCapabilities,
style: &Style,
max_width: usize,
indent: usize,
current_line: CurrentLine,
text: S,
) -> Result<CurrentLine> {
let words = WordSeparator::UnicodeBreakProperties
.find_words(text.as_ref())
.collect::<Vec<_>>();
match words.first() {
None => Ok(current_line),
Some(first_word) => {
let current_width = current_line.length
+ indent
+ current_line
.trailing_space
.as_ref()
.map_or(0, |s| display_width(s.as_ref()));
if 0 < current_line.length && max_width < current_width + display_width(first_word) {
writeln!(writer)?;
write_indent(writer, indent as u16)?;
return write_styled_and_wrapped(
writer,
capabilities,
style,
max_width,
indent,
CurrentLine::empty(),
text,
);
}
let widths = [
(max_width - current_width.min(max_width)) as f64,
(max_width - indent) as f64,
];
let lines = textwrap::wrap_algorithms::wrap_first_fit(&words, &widths);
match lines.split_first() {
None => {
Ok(current_line)
}
Some((first_line, tails)) => {
let mut buffer = String::with_capacity(max_width);
let new_current_line = match first_line.split_last() {
None => {
current_line
}
Some((last, heads)) => {
if let Some(s) = current_line.trailing_space {
buffer.push_str(&s);
}
for word in heads {
buffer.push_str(word.word);
buffer.push_str(word.whitespace);
}
buffer.push_str(last.word);
let length =
current_line.length + textwrap::core::display_width(&buffer);
write_styled(writer, capabilities, style, &buffer)?;
buffer.clear();
CurrentLine {
length,
trailing_space: Some(last.whitespace.to_owned()),
}
}
};
match tails.split_last() {
None => {
Ok(new_current_line)
}
Some((last_line, next_lines)) => write_remaining_lines(
writer,
capabilities,
style,
indent,
buffer,
next_lines,
last_line,
),
}
}
}
}
}
}
pub fn write_mark<W: Write>(writer: &mut W, capabilities: &TerminalCapabilities) -> Result<()> {
if let Some(mark) = capabilities.marks {
match mark {
MarkCapability::ITerm2(marks) => marks.set_mark(writer),
}
} else {
Ok(())
}
}
pub fn write_rule<W: Write>(
writer: &mut W,
capabilities: &TerminalCapabilities,
theme: &Theme,
length: usize,
) -> std::io::Result<()> {
let rule = "\u{2550}".repeat(length);
write_styled(
writer,
capabilities,
&Style::new().fg_color(Some(theme.rule_color)),
rule,
)
}
pub fn write_code_block_border<W: Write>(
writer: &mut W,
theme: &Theme,
capabilities: &TerminalCapabilities,
terminal_size: &TerminalSize,
) -> std::io::Result<()> {
let separator = "\u{2500}".repeat(terminal_size.columns.min(20));
write_styled(
writer,
capabilities,
&Style::new().fg_color(Some(theme.code_block_border_color)),
separator,
)?;
writeln!(writer)
}
pub fn write_link_refs<W: Write>(
writer: &mut W,
environment: &Environment,
capabilities: &TerminalCapabilities,
links: Vec<LinkReferenceDefinition>,
) -> Result<()> {
if !links.is_empty() {
writeln!(writer)?;
for link in links {
write_styled(
writer,
capabilities,
&link.style,
&format!("[{}]: ", link.index),
)?;
if let Some(url) = environment.resolve_reference(&link.target) {
use crate::terminal::capabilities::LinkCapability::*;
match &capabilities.links {
Some(Osc8(links)) => {
links.set_link_url(writer, url, &environment.hostname)?;
write_styled(writer, capabilities, &link.style, link.target)?;
links.clear_link(writer)?;
}
None => write_styled(writer, capabilities, &link.style, link.target)?,
};
} else {
write_styled(writer, capabilities, &link.style, link.target)?;
}
if !link.title.is_empty() {
write_styled(
writer,
capabilities,
&link.style,
format!(" {}", link.title),
)?;
}
writeln!(writer)?;
}
};
Ok(())
}
pub fn write_start_code_block<W: Write>(
writer: &mut W,
settings: &Settings,
indent: u16,
style: Style,
block_kind: CodeBlockKind<'_>,
) -> Result<StackedState> {
write_indent(writer, indent)?;
write_code_block_border(
writer,
&settings.theme,
&settings.terminal_capabilities,
&settings.terminal_size,
)?;
write_indent(writer, indent)?;
match (&settings.terminal_capabilities.style, block_kind) {
(Some(StyleCapability::Ansi(ansi)), CodeBlockKind::Fenced(name)) if !name.is_empty() => {
match settings.syntax_set.find_syntax_by_token(&name) {
None => Ok(LiteralBlockAttrs {
indent,
style: settings.theme.code_style.on_top_of(&style),
}
.into()),
Some(syntax) => {
let parse_state = ParseState::new(syntax);
let highlight_state = HighlightState::new(&HIGHLIGHTER, ScopeStack::new());
Ok(HighlightBlockAttrs {
ansi: *ansi,
indent,
highlight_state,
parse_state,
}
.into())
}
}
}
(_, _) => Ok(LiteralBlockAttrs {
indent,
style: settings.theme.code_style.on_top_of(&style),
}
.into()),
}
}
pub fn write_start_heading<W: Write>(
writer: &mut W,
capabilities: &TerminalCapabilities,
style: Style,
level: HeadingLevel,
) -> Result<StackedState> {
write_styled(
writer,
capabilities,
&style,
"\u{2504}".repeat(level as usize),
)?;
Ok(StackedState::Inline(
InlineState::InlineBlock,
InlineAttrs { style, indent: 0 },
))
}