use super::{Formatter, HtmlElement};
use crate::decorations::{LineSelection, SteppedLineRange};
use crate::events::HighlightEvent;
use crate::languages::Language;
use derive_builder::Builder;
use std::{
io::{self, Write},
ops::RangeInclusive,
};
#[derive(Clone, Debug)]
pub struct HighlightLines {
pub lines: Vec<RangeInclusive<usize>>,
pub class: String,
}
impl Default for HighlightLines {
fn default() -> Self {
Self {
lines: Vec::new(),
class: "l-highlighted".to_string(),
}
}
}
#[derive(Builder, Clone, Debug)]
#[builder(default)]
pub struct HtmlLinked {
#[builder(setter(custom))]
language: Language,
structure: crate::formatter::html::HtmlStructure,
pre_class: Option<String>,
pre_attrs: crate::formatter::html::HtmlAttrs,
code_attrs: crate::formatter::html::HtmlAttrs,
highlight_lines: Option<HighlightLines>,
#[builder(setter(skip), default)]
stepped_highlight_lines: Vec<SteppedLineRange>,
line_numbers: bool,
header: Option<HtmlElement>,
}
impl HtmlLinkedBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn language(&mut self, language: Language) -> &mut Self {
self.language = Some(language);
self
}
#[deprecated(since = "0.1.0", note = "use `.language(...)` instead")]
pub fn lang(&mut self, language: Language) -> &mut Self {
self.language(language)
}
}
impl HtmlLinked {
pub fn new(
language: Language,
pre_class: Option<String>,
highlight_lines: Option<HighlightLines>,
line_numbers: bool,
header: Option<HtmlElement>,
) -> Self {
Self {
language,
structure: crate::formatter::html::HtmlStructure::Block,
pre_class,
pre_attrs: Vec::new(),
code_attrs: Vec::new(),
highlight_lines,
stepped_highlight_lines: Vec::new(),
line_numbers,
header,
}
}
#[doc(hidden)]
pub fn set_stepped_highlight_lines(&mut self, lines: Vec<SteppedLineRange>) {
self.stepped_highlight_lines = lines;
}
fn line_selection(&self) -> LineSelection {
LineSelection::new(
self.highlight_lines
.as_ref()
.map_or(&[][..], |highlight| &highlight.lines),
&self.stepped_highlight_lines,
)
}
fn get_line_class_suffix(&self, is_highlighted: bool) -> Option<String> {
if !is_highlighted {
return None;
}
self.highlight_lines
.as_ref()
.map(|highlight| format!(" {}", highlight.class))
}
fn span_attrs_from_index(scope_index: usize) -> String {
let scope = crate::highlights::HIGHLIGHT_NAMES
.get(scope_index)
.copied()
.unwrap_or("");
crate::formatter::html::span_linked_attrs(scope)
}
}
impl Default for HtmlLinked {
fn default() -> Self {
Self {
language: Language::PlainText,
structure: crate::formatter::html::HtmlStructure::Block,
pre_class: None,
pre_attrs: Vec::new(),
code_attrs: Vec::new(),
highlight_lines: None,
stepped_highlight_lines: Vec::new(),
line_numbers: false,
header: None,
}
}
}
impl<T> Formatter<T> for HtmlLinked {
fn language(&self) -> Language {
self.language
}
fn render(
&self,
source: &str,
events: &[HighlightEvent<'_, T>],
output: &mut dyn Write,
) -> io::Result<()> {
self.write(source, events, output, None)
}
fn render_budgeted(
&self,
source: &str,
events: &[HighlightEvent<'_, T>],
output: &mut dyn Write,
exhausted: super::BudgetExhausted,
) -> io::Result<()> {
self.write(source, events, output, Some(exhausted))
}
}
impl HtmlLinked {
fn write<T>(
&self,
source: &str,
events: &[HighlightEvent<'_, T>],
output: &mut dyn Write,
exhausted: Option<super::BudgetExhausted>,
) -> io::Result<()> {
if self.structure == crate::formatter::html::HtmlStructure::Inline {
return crate::formatter::html::write_html_spans(
output,
source,
events,
self.language,
&|scope_index, _language| Self::span_attrs_from_index(scope_index),
);
}
if let Some(ref header) = self.header {
write!(output, "{}", header.open_tag)?;
}
crate::formatter::html::write_pre_tag(
output,
self.pre_class.as_deref(),
None,
&crate::formatter::html::budget_attrs(&self.pre_attrs, exhausted),
)?;
crate::formatter::html::write_code_tag(output, self.language, &self.code_attrs)?;
let class_suffix = self.get_line_class_suffix(true);
crate::formatter::html::write_html_lines(
output,
source,
events,
&crate::formatter::html::HtmlLines {
structure: crate::formatter::html::HtmlStructure::Block,
language: self.language,
selection: &self.line_selection(),
numbered: self.line_numbers,
line_number_attrs: None,
highlighted_line_number_attrs: None,
highlighted_class: class_suffix.as_deref(),
highlighted_style: None,
empty_style: None,
},
&|scope_index, _language| Self::span_attrs_from_index(scope_index),
)?;
crate::formatter::html::closing_tags(output)?;
if let Some(ref header) = self.header {
write!(output, "{}", header.close_tag)?;
}
Ok(())
}
}