Skip to content

Commit

Permalink
Extract markdown code block highlighting function
Browse files Browse the repository at this point in the history
  • Loading branch information
sudormrfbin authored and archseer committed Mar 8, 2022
1 parent 5a60989 commit 970a111
Showing 1 changed file with 96 additions and 69 deletions.
165 changes: 96 additions & 69 deletions helix-term/src/ui/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,95 @@ use helix_view::{
Theme,
};

fn styled_multiline_text<'a>(text: String, style: Style) -> Text<'a> {
let spans: Vec<_> = text
.lines()
.map(|line| Span::styled(line.to_string(), style))
.map(Spans::from)
.collect();
Text::from(spans)
}

pub fn highlighted_code_block<'a>(
text: String,
language: &str,
theme: Option<&Theme>,
config_loader: Arc<syntax::Loader>,
) -> Text<'a> {
let mut spans = Vec::new();
let mut lines = Vec::new();

let get_theme = |key: &str| -> Style { theme.map(|t| t.get(key)).unwrap_or_default() };
let text_style = get_theme(Markdown::TEXT_STYLE);
let code_style = get_theme(Markdown::BLOCK_STYLE);

let theme = match theme {
Some(t) => t,
None => return styled_multiline_text(text, code_style),
};

let rope = Rope::from(text.as_ref());
let syntax = config_loader
.language_configuration_for_injection_string(language)
.and_then(|config| config.highlight_config(theme.scopes()))
.map(|config| Syntax::new(&rope, config, Arc::clone(&config_loader)));

let syntax = match syntax {
Some(s) => s,
None => return styled_multiline_text(text, code_style),
};

let mut highlights = Vec::new();

for event in syntax.highlight_iter(rope.slice(..), None, None) {
match event.unwrap() {
HighlightEvent::HighlightStart(span) => {
highlights.push(span);
}
HighlightEvent::HighlightEnd => {
highlights.pop();
}
HighlightEvent::Source { start, end } => {
let style = match highlights.first() {
Some(span) => theme.get(&theme.scopes()[span.0]),
None => text_style,
};

let mut slice = &text[start..end];
// TODO: do we need to handle all unicode line endings
// here, or is just '\n' okay?
while let Some(end) = slice.find('\n') {
// emit span up to newline
let text = &slice[..end];
let text = text.replace('\t', " "); // replace tabs
let span = Span::styled(text, style);
spans.push(span);

// truncate slice to after newline
slice = &slice[end + 1..];

// make a new line
let spans = std::mem::take(&mut spans);
lines.push(Spans::from(spans));
}

// if there's anything left, emit it too
if !slice.is_empty() {
let span = Span::styled(slice.replace('\t', " "), style);
spans.push(span);
}
}
}
}

if !spans.is_empty() {
let spans = std::mem::take(&mut spans);
lines.push(Spans::from(spans));
}

Text::from(lines)
}

pub struct Markdown {
contents: String,

Expand Down Expand Up @@ -101,75 +190,13 @@ impl Markdown {
Event::Text(text) => {
// TODO: temp workaround
if let Some(Tag::CodeBlock(CodeBlockKind::Fenced(language))) = tags.last() {
if let Some(theme) = theme {
let rope = Rope::from(text.as_ref());
let syntax = self
.config_loader
.language_configuration_for_injection_string(language)
.and_then(|config| config.highlight_config(theme.scopes()))
.map(|config| {
Syntax::new(&rope, config, self.config_loader.clone())
});

if let Some(syntax) = syntax {
// if we have a syntax available, highlight_iter and generate spans
let mut highlights = Vec::new();

for event in syntax.highlight_iter(rope.slice(..), None, None) {
match event.unwrap() {
HighlightEvent::HighlightStart(span) => {
highlights.push(span);
}
HighlightEvent::HighlightEnd => {
highlights.pop();
}
HighlightEvent::Source { start, end } => {
let style = match highlights.first() {
Some(span) => theme.get(&theme.scopes()[span.0]),
None => text_style,
};

let mut slice = &text[start..end];
// TODO: do we need to handle all unicode line endings
// here, or is just '\n' okay?
while let Some(end) = slice.find('\n') {
// emit span up to newline
let text = &slice[..end];
let text = text.replace('\t', " "); // replace tabs
let span = Span::styled(text, style);
spans.push(span);

// truncate slice to after newline
slice = &slice[end + 1..];

// make a new line
let spans = std::mem::take(&mut spans);
lines.push(Spans::from(spans));
}

// if there's anything left, emit it too
if !slice.is_empty() {
let span = Span::styled(
slice.replace('\t', " "),
style,
);
spans.push(span);
}
}
}
}
} else {
for line in text.lines() {
let span = Span::styled(line.to_string(), code_style);
lines.push(Spans::from(span));
}
}
} else {
for line in text.lines() {
let span = Span::styled(line.to_string(), code_style);
lines.push(Spans::from(span));
}
}
let tui_text = highlighted_code_block(
text.to_string(),
language,
theme,
Arc::clone(&self.config_loader),
);
lines.extend(tui_text.lines.into_iter());
} else {
let style = if let Some(Tag::Heading(level, ..)) = tags.last() {
match level {
Expand Down

0 comments on commit 970a111

Please sign in to comment.