mirror of https://github.com/helix-editor/helix
Extract markdown code block highlighting function
parent
5a60989efe
commit
970a111aa3
|
@ -17,6 +17,95 @@ use helix_view::{
|
||||||
Theme,
|
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 {
|
pub struct Markdown {
|
||||||
contents: String,
|
contents: String,
|
||||||
|
|
||||||
|
@ -101,75 +190,13 @@ impl Markdown {
|
||||||
Event::Text(text) => {
|
Event::Text(text) => {
|
||||||
// TODO: temp workaround
|
// TODO: temp workaround
|
||||||
if let Some(Tag::CodeBlock(CodeBlockKind::Fenced(language))) = tags.last() {
|
if let Some(Tag::CodeBlock(CodeBlockKind::Fenced(language))) = tags.last() {
|
||||||
if let Some(theme) = theme {
|
let tui_text = highlighted_code_block(
|
||||||
let rope = Rope::from(text.as_ref());
|
text.to_string(),
|
||||||
let syntax = self
|
language,
|
||||||
.config_loader
|
theme,
|
||||||
.language_configuration_for_injection_string(language)
|
Arc::clone(&self.config_loader),
|
||||||
.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);
|
lines.extend(tui_text.lines.into_iter());
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let style = if let Some(Tag::Heading(level, ..)) = tags.last() {
|
let style = if let Some(Tag::Heading(level, ..)) = tags.last() {
|
||||||
match level {
|
match level {
|
||||||
|
|
Loading…
Reference in New Issue