diff options
Diffstat (limited to 'src/ide/highlight.rs')
| -rw-r--r-- | src/ide/highlight.rs | 430 |
1 files changed, 0 insertions, 430 deletions
diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs deleted file mode 100644 index 2db636e3..00000000 --- a/src/ide/highlight.rs +++ /dev/null @@ -1,430 +0,0 @@ -use crate::syntax::{ast, LinkedNode, SyntaxKind, SyntaxNode}; - -/// A syntax highlighting tag. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Tag { - /// A line or block comment. - Comment, - /// Punctuation in code. - Punctuation, - /// An escape sequence or shorthand. - Escape, - /// Strong markup. - Strong, - /// Emphasized markup. - Emph, - /// A hyperlink. - Link, - /// Raw text. - Raw, - /// A label. - Label, - /// A reference to a label. - Ref, - /// A section heading. - Heading, - /// A marker of a list, enumeration, or term list. - ListMarker, - /// A term in a term list. - ListTerm, - /// The delimiters of an equation. - MathDelimiter, - /// An operator with special meaning in an equation. - MathOperator, - /// A keyword. - Keyword, - /// An operator in code. - Operator, - /// A numeric literal. - Number, - /// A string literal. - String, - /// A function or method name. - Function, - /// An interpolated variable in markup or math. - Interpolated, - /// A syntax error. - Error, -} - -impl Tag { - /// Return the recommended TextMate grammar scope for the given highlighting - /// tag. - pub fn tm_scope(&self) -> &'static str { - match self { - Self::Comment => "comment.typst", - Self::Punctuation => "punctuation.typst", - Self::Escape => "constant.character.escape.typst", - Self::Strong => "markup.bold.typst", - Self::Emph => "markup.italic.typst", - Self::Link => "markup.underline.link.typst", - Self::Raw => "markup.raw.typst", - Self::MathDelimiter => "punctuation.definition.math.typst", - Self::MathOperator => "keyword.operator.math.typst", - Self::Heading => "markup.heading.typst", - Self::ListMarker => "punctuation.definition.list.typst", - Self::ListTerm => "markup.list.term.typst", - Self::Label => "entity.name.label.typst", - Self::Ref => "markup.other.reference.typst", - Self::Keyword => "keyword.typst", - Self::Operator => "keyword.operator.typst", - Self::Number => "constant.numeric.typst", - Self::String => "string.quoted.double.typst", - Self::Function => "entity.name.function.typst", - Self::Interpolated => "meta.interpolation.typst", - Self::Error => "invalid.typst", - } - } - - /// The recommended CSS class for the highlighting tag. - pub fn css_class(self) -> &'static str { - match self { - Self::Comment => "typ-comment", - Self::Punctuation => "typ-punct", - Self::Escape => "typ-escape", - Self::Strong => "typ-strong", - Self::Emph => "typ-emph", - Self::Link => "typ-link", - Self::Raw => "typ-raw", - Self::Label => "typ-label", - Self::Ref => "typ-ref", - Self::Heading => "typ-heading", - Self::ListMarker => "typ-marker", - Self::ListTerm => "typ-term", - Self::MathDelimiter => "typ-math-delim", - Self::MathOperator => "typ-math-op", - Self::Keyword => "typ-key", - Self::Operator => "typ-op", - Self::Number => "typ-num", - Self::String => "typ-str", - Self::Function => "typ-func", - Self::Interpolated => "typ-pol", - Self::Error => "typ-error", - } - } -} - -/// Determine the highlight tag of a linked syntax node. -/// -/// Returns `None` if the node should not be highlighted. -pub fn highlight(node: &LinkedNode) -> Option<Tag> { - match node.kind() { - SyntaxKind::Markup - if node.parent_kind() == Some(SyntaxKind::TermItem) - && node.next_sibling_kind() == Some(SyntaxKind::Colon) => - { - Some(Tag::ListTerm) - } - SyntaxKind::Markup => None, - SyntaxKind::Text => None, - SyntaxKind::Space => None, - SyntaxKind::Linebreak => Some(Tag::Escape), - SyntaxKind::Parbreak => None, - SyntaxKind::Escape => Some(Tag::Escape), - SyntaxKind::Shorthand => Some(Tag::Escape), - SyntaxKind::SmartQuote => None, - SyntaxKind::Strong => Some(Tag::Strong), - SyntaxKind::Emph => Some(Tag::Emph), - SyntaxKind::Raw => Some(Tag::Raw), - SyntaxKind::Link => Some(Tag::Link), - SyntaxKind::Label => Some(Tag::Label), - SyntaxKind::Ref => Some(Tag::Ref), - SyntaxKind::RefMarker => None, - SyntaxKind::Heading => Some(Tag::Heading), - SyntaxKind::HeadingMarker => None, - SyntaxKind::ListItem => None, - SyntaxKind::ListMarker => Some(Tag::ListMarker), - SyntaxKind::EnumItem => None, - SyntaxKind::EnumMarker => Some(Tag::ListMarker), - SyntaxKind::TermItem => None, - SyntaxKind::TermMarker => Some(Tag::ListMarker), - SyntaxKind::Equation => None, - - SyntaxKind::Math => None, - SyntaxKind::MathIdent => highlight_ident(node), - SyntaxKind::MathAlignPoint => Some(Tag::MathOperator), - SyntaxKind::MathDelimited => None, - SyntaxKind::MathAttach => None, - SyntaxKind::MathFrac => None, - SyntaxKind::MathRoot => None, - - SyntaxKind::Hashtag => highlight_hashtag(node), - SyntaxKind::LeftBrace => Some(Tag::Punctuation), - SyntaxKind::RightBrace => Some(Tag::Punctuation), - SyntaxKind::LeftBracket => Some(Tag::Punctuation), - SyntaxKind::RightBracket => Some(Tag::Punctuation), - SyntaxKind::LeftParen => Some(Tag::Punctuation), - SyntaxKind::RightParen => Some(Tag::Punctuation), - SyntaxKind::Comma => Some(Tag::Punctuation), - SyntaxKind::Semicolon => Some(Tag::Punctuation), - SyntaxKind::Colon => Some(Tag::Punctuation), - SyntaxKind::Star => match node.parent_kind() { - Some(SyntaxKind::Strong) => None, - _ => Some(Tag::Operator), - }, - SyntaxKind::Underscore => match node.parent_kind() { - Some(SyntaxKind::MathAttach) => Some(Tag::MathOperator), - _ => None, - }, - SyntaxKind::Dollar => Some(Tag::MathDelimiter), - SyntaxKind::Plus => Some(Tag::Operator), - SyntaxKind::Minus => Some(Tag::Operator), - SyntaxKind::Slash => Some(match node.parent_kind() { - Some(SyntaxKind::MathFrac) => Tag::MathOperator, - _ => Tag::Operator, - }), - SyntaxKind::Hat => Some(Tag::MathOperator), - SyntaxKind::Dot => Some(Tag::Punctuation), - SyntaxKind::Eq => match node.parent_kind() { - Some(SyntaxKind::Heading) => None, - _ => Some(Tag::Operator), - }, - SyntaxKind::EqEq => Some(Tag::Operator), - SyntaxKind::ExclEq => Some(Tag::Operator), - SyntaxKind::Lt => Some(Tag::Operator), - SyntaxKind::LtEq => Some(Tag::Operator), - SyntaxKind::Gt => Some(Tag::Operator), - SyntaxKind::GtEq => Some(Tag::Operator), - SyntaxKind::PlusEq => Some(Tag::Operator), - SyntaxKind::HyphEq => Some(Tag::Operator), - SyntaxKind::StarEq => Some(Tag::Operator), - SyntaxKind::SlashEq => Some(Tag::Operator), - SyntaxKind::Dots => Some(Tag::Operator), - SyntaxKind::Arrow => Some(Tag::Operator), - SyntaxKind::Root => Some(Tag::MathOperator), - - SyntaxKind::Not => Some(Tag::Keyword), - SyntaxKind::And => Some(Tag::Keyword), - SyntaxKind::Or => Some(Tag::Keyword), - SyntaxKind::None => Some(Tag::Keyword), - SyntaxKind::Auto => Some(Tag::Keyword), - SyntaxKind::Let => Some(Tag::Keyword), - SyntaxKind::Set => Some(Tag::Keyword), - SyntaxKind::Show => Some(Tag::Keyword), - SyntaxKind::If => Some(Tag::Keyword), - SyntaxKind::Else => Some(Tag::Keyword), - SyntaxKind::For => Some(Tag::Keyword), - SyntaxKind::In => Some(Tag::Keyword), - SyntaxKind::While => Some(Tag::Keyword), - SyntaxKind::Break => Some(Tag::Keyword), - SyntaxKind::Continue => Some(Tag::Keyword), - SyntaxKind::Return => Some(Tag::Keyword), - SyntaxKind::Import => Some(Tag::Keyword), - SyntaxKind::Include => Some(Tag::Keyword), - SyntaxKind::As => Some(Tag::Keyword), - - SyntaxKind::Code => None, - SyntaxKind::Ident => highlight_ident(node), - SyntaxKind::Bool => Some(Tag::Keyword), - SyntaxKind::Int => Some(Tag::Number), - SyntaxKind::Float => Some(Tag::Number), - SyntaxKind::Numeric => Some(Tag::Number), - SyntaxKind::Str => Some(Tag::String), - SyntaxKind::CodeBlock => None, - SyntaxKind::ContentBlock => None, - SyntaxKind::Parenthesized => None, - SyntaxKind::Array => None, - SyntaxKind::Dict => None, - SyntaxKind::Named => None, - SyntaxKind::Keyed => None, - SyntaxKind::Unary => None, - SyntaxKind::Binary => None, - SyntaxKind::FieldAccess => None, - SyntaxKind::FuncCall => None, - SyntaxKind::Args => None, - SyntaxKind::Spread => None, - SyntaxKind::Closure => None, - SyntaxKind::Params => None, - SyntaxKind::LetBinding => None, - SyntaxKind::SetRule => None, - SyntaxKind::ShowRule => None, - SyntaxKind::Conditional => None, - SyntaxKind::WhileLoop => None, - SyntaxKind::ForLoop => None, - SyntaxKind::ModuleImport => None, - SyntaxKind::ImportItems => None, - SyntaxKind::ModuleInclude => None, - SyntaxKind::LoopBreak => None, - SyntaxKind::LoopContinue => None, - SyntaxKind::FuncReturn => None, - SyntaxKind::Destructuring => None, - SyntaxKind::DestructAssignment => None, - - SyntaxKind::LineComment => Some(Tag::Comment), - SyntaxKind::BlockComment => Some(Tag::Comment), - SyntaxKind::Error => Some(Tag::Error), - SyntaxKind::Eof => None, - } -} - -/// Highlight an identifier based on context. -fn highlight_ident(node: &LinkedNode) -> Option<Tag> { - // Are we directly before an argument list? - let next_leaf = node.next_leaf(); - if let Some(next) = &next_leaf { - if node.range().end == next.offset() - && ((next.kind() == SyntaxKind::LeftParen - && matches!( - next.parent_kind(), - Some(SyntaxKind::Args | SyntaxKind::Params) - )) - || (next.kind() == SyntaxKind::LeftBracket - && next.parent_kind() == Some(SyntaxKind::ContentBlock))) - { - return Some(Tag::Function); - } - } - - // Are we in math? - if node.kind() == SyntaxKind::MathIdent { - return Some(Tag::Interpolated); - } - - // Find the first non-field access ancestor. - let mut ancestor = node; - while ancestor.parent_kind() == Some(SyntaxKind::FieldAccess) { - ancestor = ancestor.parent()?; - } - - // Are we directly before or behind a show rule colon? - if ancestor.parent_kind() == Some(SyntaxKind::ShowRule) - && (next_leaf.map(|leaf| leaf.kind()) == Some(SyntaxKind::Colon) - || node.prev_leaf().map(|leaf| leaf.kind()) == Some(SyntaxKind::Colon)) - { - return Some(Tag::Function); - } - - // Are we (or an ancestor field access) directly after a hashtag. - if ancestor.prev_leaf().map(|leaf| leaf.kind()) == Some(SyntaxKind::Hashtag) { - return Some(Tag::Interpolated); - } - - // Are we behind a dot, that is behind another identifier? - let prev = node.prev_leaf()?; - if prev.kind() == SyntaxKind::Dot { - let prev_prev = prev.prev_leaf()?; - if is_ident(&prev_prev) { - return highlight_ident(&prev_prev); - } - } - - None -} - -/// Highlight a hashtag based on context. -fn highlight_hashtag(node: &LinkedNode) -> Option<Tag> { - let next = node.next_sibling()?; - let expr = next.cast::<ast::Expr>()?; - if !expr.hashtag() { - return None; - } - highlight(&next.leftmost_leaf()?) -} - -/// Whether the node is one of the two identifier nodes. -fn is_ident(node: &LinkedNode) -> bool { - matches!(node.kind(), SyntaxKind::Ident | SyntaxKind::MathIdent) -} - -/// Highlight a node to an HTML `code` element. -/// -/// This uses these [CSS classes for categories](Tag::css_class). -pub fn highlight_html(root: &SyntaxNode) -> String { - let mut buf = String::from("<code>"); - let node = LinkedNode::new(root); - highlight_html_impl(&mut buf, &node); - buf.push_str("</code>"); - buf -} - -/// Highlight one source node, emitting HTML. -fn highlight_html_impl(html: &mut String, node: &LinkedNode) { - let mut span = false; - if let Some(tag) = highlight(node) { - if tag != Tag::Error { - span = true; - html.push_str("<span class=\""); - html.push_str(tag.css_class()); - html.push_str("\">"); - } - } - - let text = node.text(); - if !text.is_empty() { - for c in text.chars() { - match c { - '<' => html.push_str("<"), - '>' => html.push_str(">"), - '&' => html.push_str("&"), - '\'' => html.push_str("'"), - '"' => html.push_str("""), - _ => html.push(c), - } - } - } else { - for child in node.children() { - highlight_html_impl(html, &child); - } - } - - if span { - html.push_str("</span>"); - } -} - -#[cfg(test)] -mod tests { - use std::ops::Range; - - use super::*; - use crate::syntax::Source; - - #[test] - fn test_highlighting() { - use Tag::*; - - #[track_caller] - fn test(text: &str, goal: &[(Range<usize>, Tag)]) { - let mut vec = vec![]; - let source = Source::detached(text); - highlight_tree(&mut vec, &LinkedNode::new(source.root())); - assert_eq!(vec, goal); - } - - fn highlight_tree(tags: &mut Vec<(Range<usize>, Tag)>, node: &LinkedNode) { - if let Some(tag) = highlight(node) { - tags.push((node.range(), tag)); - } - - for child in node.children() { - highlight_tree(tags, &child); - } - } - - test("= *AB*", &[(0..6, Heading), (2..6, Strong)]); - - test( - "#f(x + 1)", - &[ - (0..1, Function), - (1..2, Function), - (2..3, Punctuation), - (5..6, Operator), - (7..8, Number), - (8..9, Punctuation), - ], - ); - - test( - "#let f(x) = x", - &[ - (0..1, Keyword), - (1..4, Keyword), - (5..6, Function), - (6..7, Punctuation), - (8..9, Punctuation), - (10..11, Operator), - ], - ); - } -} |
