summaryrefslogtreecommitdiff
path: root/src/syntax/highlight.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-11-30 18:55:50 +0100
committerLaurenz <laurmaedje@gmail.com>2021-11-30 18:55:50 +0100
commitf3bdc9d3daca2c26c4cc745be48a5ce1c8a10641 (patch)
tree05976cd56e414d47d1e2d0bb79fa83893b6daa15 /src/syntax/highlight.rs
parente915cc4ef04de70297a3e840a363382dd2689f7b (diff)
Syntax highlighting
Diffstat (limited to 'src/syntax/highlight.rs')
-rw-r--r--src/syntax/highlight.rs231
1 files changed, 231 insertions, 0 deletions
diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs
new file mode 100644
index 00000000..22e6cf50
--- /dev/null
+++ b/src/syntax/highlight.rs
@@ -0,0 +1,231 @@
+use std::ops::Range;
+
+use super::{NodeKind, RedRef};
+
+/// Provide highlighting categories for the children of a node that fall into a
+/// range.
+pub fn highlight<F>(node: RedRef, range: Range<usize>, f: &mut F)
+where
+ F: FnMut(Range<usize>, Category),
+{
+ for child in node.children() {
+ let span = child.span();
+ if range.start <= span.end && range.end >= span.start {
+ if let Some(category) = Category::determine(child, node) {
+ f(span.to_range(), category);
+ }
+ highlight(child, range.clone(), f);
+ }
+ }
+}
+
+/// The syntax highlighting category of a node.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum Category {
+ /// Any kind of bracket, parenthesis or brace.
+ Bracket,
+ /// Punctuation in code.
+ Punctuation,
+ /// A line or block comment.
+ Comment,
+ /// Strong text.
+ Strong,
+ /// Emphasized text.
+ Emph,
+ /// Raw text or code.
+ Raw,
+ /// A math formula.
+ Math,
+ /// A section heading.
+ Heading,
+ /// A list or enumeration.
+ List,
+ /// An easily typable shortcut to a unicode codepoint.
+ Shortcut,
+ /// An escape sequence.
+ Escape,
+ /// A keyword.
+ Keyword,
+ /// An operator symbol.
+ Operator,
+ /// The none literal.
+ None,
+ /// The auto literal.
+ Auto,
+ /// A boolean literal.
+ Bool,
+ /// A numeric literal.
+ Number,
+ /// A string literal.
+ String,
+ /// A function.
+ Function,
+ /// A variable.
+ Variable,
+ /// An invalid node.
+ Invalid,
+}
+
+impl Category {
+ /// Determine the highlighting category of a node given its parent.
+ pub fn determine(child: RedRef, parent: RedRef) -> Option<Category> {
+ match child.kind() {
+ NodeKind::LeftBracket => Some(Category::Bracket),
+ NodeKind::RightBracket => Some(Category::Bracket),
+ NodeKind::LeftBrace => Some(Category::Bracket),
+ NodeKind::RightBrace => Some(Category::Bracket),
+ NodeKind::LeftParen => Some(Category::Bracket),
+ NodeKind::RightParen => Some(Category::Bracket),
+ NodeKind::Comma => Some(Category::Punctuation),
+ NodeKind::Semicolon => Some(Category::Punctuation),
+ NodeKind::Colon => Some(Category::Punctuation),
+ NodeKind::LineComment => Some(Category::Comment),
+ NodeKind::BlockComment => Some(Category::Comment),
+ NodeKind::Strong => Some(Category::Strong),
+ NodeKind::Emph => Some(Category::Emph),
+ NodeKind::Raw(_) => Some(Category::Raw),
+ NodeKind::Math(_) => Some(Category::Math),
+ NodeKind::Heading => Some(Category::Heading),
+ NodeKind::Minus => match parent.kind() {
+ NodeKind::List => Some(Category::List),
+ _ => Some(Category::Operator),
+ },
+ NodeKind::EnumNumbering(_) => Some(Category::List),
+ NodeKind::Linebreak => Some(Category::Shortcut),
+ NodeKind::NonBreakingSpace => Some(Category::Shortcut),
+ NodeKind::EnDash => Some(Category::Shortcut),
+ NodeKind::EmDash => Some(Category::Shortcut),
+ NodeKind::Escape(_) => Some(Category::Escape),
+ NodeKind::Let => Some(Category::Keyword),
+ NodeKind::If => Some(Category::Keyword),
+ NodeKind::Else => Some(Category::Keyword),
+ NodeKind::For => Some(Category::Keyword),
+ NodeKind::In => Some(Category::Keyword),
+ NodeKind::While => Some(Category::Keyword),
+ NodeKind::Break => Some(Category::Keyword),
+ NodeKind::Continue => Some(Category::Keyword),
+ NodeKind::Return => Some(Category::Keyword),
+ NodeKind::Import => Some(Category::Keyword),
+ NodeKind::Include => Some(Category::Keyword),
+ NodeKind::From => Some(Category::Keyword),
+ NodeKind::Not => Some(Category::Keyword),
+ NodeKind::And => Some(Category::Keyword),
+ NodeKind::Or => Some(Category::Keyword),
+ NodeKind::With => Some(Category::Keyword),
+ NodeKind::Plus => Some(Category::Operator),
+ NodeKind::Star => Some(Category::Operator),
+ NodeKind::Slash => Some(Category::Operator),
+ NodeKind::PlusEq => Some(Category::Operator),
+ NodeKind::HyphEq => Some(Category::Operator),
+ NodeKind::StarEq => Some(Category::Operator),
+ NodeKind::SlashEq => Some(Category::Operator),
+ NodeKind::Eq => match parent.kind() {
+ NodeKind::Heading => None,
+ _ => Some(Category::Operator),
+ },
+ NodeKind::EqEq => Some(Category::Operator),
+ NodeKind::ExclEq => Some(Category::Operator),
+ NodeKind::Lt => Some(Category::Operator),
+ NodeKind::LtEq => Some(Category::Operator),
+ NodeKind::Gt => Some(Category::Operator),
+ NodeKind::GtEq => Some(Category::Operator),
+ NodeKind::Dots => Some(Category::Operator),
+ NodeKind::Arrow => Some(Category::Operator),
+ NodeKind::None => Some(Category::None),
+ NodeKind::Auto => Some(Category::Auto),
+ NodeKind::Ident(_) => match parent.kind() {
+ NodeKind::Named => None,
+ NodeKind::Closure if child.span().start == parent.span().start => {
+ Some(Category::Function)
+ }
+ NodeKind::WithExpr => Some(Category::Function),
+ NodeKind::Call => Some(Category::Function),
+ _ => Some(Category::Variable),
+ },
+ NodeKind::Bool(_) => Some(Category::Bool),
+ NodeKind::Int(_) => Some(Category::Number),
+ NodeKind::Float(_) => Some(Category::Number),
+ NodeKind::Length(_, _) => Some(Category::Number),
+ NodeKind::Angle(_, _) => Some(Category::Number),
+ NodeKind::Percentage(_) => Some(Category::Number),
+ NodeKind::Fraction(_) => Some(Category::Number),
+ NodeKind::Str(_) => Some(Category::String),
+ NodeKind::Error(_, _) => Some(Category::Invalid),
+ NodeKind::Unknown(_) => Some(Category::Invalid),
+ NodeKind::Markup => None,
+ NodeKind::Space(_) => None,
+ NodeKind::Parbreak => None,
+ NodeKind::Text(_) => None,
+ NodeKind::List => None,
+ NodeKind::Enum => None,
+ NodeKind::Array => None,
+ NodeKind::Dict => None,
+ NodeKind::Named => None,
+ NodeKind::Group => None,
+ NodeKind::Unary => None,
+ NodeKind::Binary => None,
+ NodeKind::Call => None,
+ NodeKind::CallArgs => None,
+ NodeKind::Closure => None,
+ NodeKind::ClosureParams => None,
+ NodeKind::Spread => None,
+ NodeKind::Template => None,
+ NodeKind::Block => None,
+ NodeKind::ForExpr => None,
+ NodeKind::WhileExpr => None,
+ NodeKind::IfExpr => None,
+ NodeKind::LetExpr => None,
+ NodeKind::WithExpr => None,
+ NodeKind::ForPattern => None,
+ NodeKind::ImportExpr => None,
+ NodeKind::ImportItems => None,
+ NodeKind::IncludeExpr => None,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::source::SourceFile;
+
+ #[test]
+ fn test_highlighting() {
+ use Category::*;
+
+ #[track_caller]
+ fn test(src: &str, goal: &[(Range<usize>, Category)]) {
+ let mut vec = vec![];
+ let source = SourceFile::detached(src);
+ source.highlight(0 .. src.len(), |range, category| {
+ vec.push((range, category));
+ });
+ assert_eq!(vec, goal);
+ }
+
+ test("= *AB*", &[
+ (0 .. 6, Heading),
+ (2 .. 3, Strong),
+ (5 .. 6, Strong),
+ ]);
+
+ test("#f(x + 1)", &[
+ (0 .. 2, Function),
+ (2 .. 3, Bracket),
+ (3 .. 4, Variable),
+ (5 .. 6, Operator),
+ (7 .. 8, Number),
+ (8 .. 9, Bracket),
+ ]);
+
+ test("#let f(x) = x", &[
+ (0 .. 4, Keyword),
+ (5 .. 6, Function),
+ (6 .. 7, Bracket),
+ (7 .. 8, Variable),
+ (8 .. 9, Bracket),
+ (10 .. 11, Operator),
+ (12 .. 13, Variable),
+ ]);
+ }
+}