ui: Syntax highlight code inside markdown popups.

pull/8/head
Blaž Hrastnik 4 years ago
parent 54a7e893b7
commit d8599f3a14

@ -10,18 +10,22 @@ use tui::{
use std::borrow::Cow; use std::borrow::Cow;
use helix_core::Position; use helix_core::Position;
use helix_view::Editor; use helix_view::{Editor, Theme};
pub struct Markdown { pub struct Markdown {
contents: String, contents: String,
} }
// TODO: pre-render and self reference via Pin
// better yet, just use Tendril + subtendril for references
impl Markdown { impl Markdown {
pub fn new(contents: String) -> Self { pub fn new(contents: String) -> Self {
Self { contents } Self { contents }
} }
} }
fn parse(contents: &str) -> tui::text::Text {
fn parse<'a>(contents: &'a str, theme: Option<&Theme>) -> tui::text::Text<'a> {
use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag}; use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag};
use tui::text::{Span, Spans, Text}; use tui::text::{Span, Spans, Text};
@ -68,11 +72,74 @@ fn parse(contents: &str) -> tui::text::Text {
} }
} }
Event::Text(text) => { Event::Text(text) => {
if let Some(Tag::CodeBlock(CodeBlockKind::Fenced(_))) = tags.last() { // TODO: temp workaround
if let Some(Tag::CodeBlock(CodeBlockKind::Fenced(language))) = tags.last() {
if let Some(theme) = theme {
use helix_core::syntax::{self, HighlightEvent, Syntax};
use helix_core::Rope;
let rope = Rope::from(text.as_ref());
let syntax = syntax::LOADER
.language_config_for_scope(&format!("source.{}", language))
.and_then(|config| config.highlight_config(theme.scopes()))
.map(|config| Syntax::new(&rope, config));
if let Some(mut 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, |_| 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].as_str())
}
None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
};
let mut slice = &text[start..end];
while let Some(end) = slice.find('\n') {
// emit span up to newline
let text = &slice[..end];
let span = Span::styled(text.to_owned(), style);
spans.push(span);
// truncate slice to after newline
slice = &slice[end + 1..];
// make a new line
let spans = std::mem::replace(&mut spans, Vec::new());
lines.push(Spans::from(spans));
}
// if there's anything left, emit it too
if !slice.is_empty() {
let span = Span::styled(slice.to_owned(), style);
spans.push(span);
}
}
}
}
} else {
for line in text.lines() { for line in text.lines() {
let mut span = Span::styled(line.to_string(), code_style); let mut span = Span::styled(line.to_string(), code_style);
lines.push(Spans::from(span)); lines.push(Spans::from(span));
} }
}
} else {
for line in text.lines() {
let mut span = Span::styled(line.to_string(), code_style);
lines.push(Spans::from(span));
}
}
} else if let Some(Tag::Heading(_)) = tags.last() { } else if let Some(Tag::Heading(_)) = tags.last() {
let mut span = to_span(text); let mut span = to_span(text);
span.style = heading_style; span.style = heading_style;
@ -113,7 +180,7 @@ impl Component for Markdown {
fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) { fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
use tui::widgets::{Paragraph, Widget, Wrap}; use tui::widgets::{Paragraph, Widget, Wrap};
let text = parse(&self.contents); let text = parse(&self.contents, Some(&cx.editor.theme));
let par = Paragraph::new(text) let par = Paragraph::new(text)
.wrap(Wrap { trim: false }) .wrap(Wrap { trim: false })
@ -124,18 +191,10 @@ impl Component for Markdown {
} }
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
let contents = parse(&self.contents); let contents = parse(&self.contents, None);
let padding = 2; let padding = 2;
let width = std::cmp::min(contents.width() as u16 + padding, viewport.0); let width = std::cmp::min(contents.width() as u16 + padding, viewport.0);
let height = std::cmp::min(contents.height() as u16 + padding, viewport.1); let height = std::cmp::min(contents.height() as u16 + padding, viewport.1);
Some((width, height)) Some((width, height))
} }
} }
#[cfg(test)]
mod test {
use super::*;
#[test]
fn it_works() {}
}

@ -88,34 +88,6 @@ pub struct Theme {
mapping: HashMap<&'static str, Style>, mapping: HashMap<&'static str, Style>,
} }
// let highlight_names: Vec<String> = [
// "attribute",
// "constant.builtin",
// "constant",
// "function.builtin",
// "function.macro",
// "function",
// "keyword",
// "operator",
// "property",
// "punctuation",
// "comment",
// "escape",
// "label",
// // "punctuation.bracket",
// "punctuation.delimiter",
// "string",
// "string.special",
// "tag",
// "type",
// "type.builtin",
// "constructor",
// "variable",
// "variable.builtin",
// "variable.parameter",
// "path",
// ];
impl Default for Theme { impl Default for Theme {
fn default() -> Self { fn default() -> Self {
let mapping = hashmap! { let mapping = hashmap! {

Loading…
Cancel
Save