pub(crate) mod diff;
pub(crate) mod highlight;
pub(crate) mod markup;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum RenderInputKind {
Markdown,
Plain,
Code { language: Option<String> },
Diff,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DisplayLine {
pub(crate) spans: Vec<DisplaySpan>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DisplaySpan {
pub(crate) text: String,
pub(crate) role: DisplayRole,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DisplayRole {
Plain,
Heading,
ListMarker,
BlockQuote,
InlineCode,
CodeFence,
CodeLanguageLabel,
CodeBlockGutter,
Keyword,
String,
Comment,
Number,
Function,
Type,
Operator,
Punctuation,
FallbackCode,
DiffInserted,
DiffRemoved,
DiffChanged,
DiffContext,
DiffHunkHeader,
DiffFileHeader,
DiffMetadata,
}
impl DisplayLine {
pub(crate) fn plain<T: Into<String>>(text: T) -> Self {
Self {
spans: vec![DisplaySpan::new(text, DisplayRole::Plain)],
}
}
pub(crate) fn from_span<T: Into<String>>(text: T, role: DisplayRole) -> Self {
Self {
spans: vec![DisplaySpan::new(text, role)],
}
}
pub(crate) fn plain_text(&self) -> String {
self.spans.iter().map(|span| span.text.as_str()).collect()
}
pub(crate) fn is_blank(&self) -> bool {
self.spans.iter().all(|span| span.text.is_empty())
}
}
impl DisplaySpan {
pub(crate) fn new<T: Into<String>>(text: T, role: DisplayRole) -> Self {
Self {
text: text.into(),
role,
}
}
}
pub(crate) fn render(input: &str, kind: RenderInputKind) -> Vec<DisplayLine> {
match kind {
RenderInputKind::Markdown => markup::render_markdown(input),
RenderInputKind::Plain => render_plain(input),
RenderInputKind::Code { language } => highlight::highlight_code(input, language.as_deref()),
RenderInputKind::Diff => diff::render_diff(input),
}
}
pub(crate) fn render_plain(input: &str) -> Vec<DisplayLine> {
if input.is_empty() {
return vec![DisplayLine::plain("")];
}
input.split('\n').map(DisplayLine::plain).collect()
}
pub(crate) fn plain_projection(lines: &[DisplayLine]) -> String {
lines
.iter()
.map(DisplayLine::plain_text)
.collect::<Vec<_>>()
.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_projection_round_trips_visible_text() {
let lines = render("a\nb", RenderInputKind::Plain);
assert_eq!(plain_projection(&lines), "a\nb");
}
#[test]
fn is_blank_detects_empty_and_nonempty() {
assert!(DisplayLine::plain("").is_blank());
assert!(!DisplayLine::plain("text").is_blank());
}
#[test]
fn empty_input_is_one_empty_line() {
assert_eq!(
render("", RenderInputKind::Plain),
vec![DisplayLine::plain("")]
);
}
}