From 25b3f98e3d200ae7f0f08b10be50552359502494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Wed, 9 Sep 2020 15:48:25 +0900 Subject: [PATCH] draft: tree-sitter highlighting --- Cargo.lock | 13 +++++ helix-core/src/state.rs | 11 ++++ helix-term/Cargo.toml | 2 + helix-term/src/editor.rs | 115 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 0385b9d3d..6334ee8b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -393,8 +393,11 @@ dependencies = [ "argh", "crossterm", "helix-core", + "helix-syntax", "num_cpus", "smol", + "tree-sitter", + "tree-sitter-highlight", "tui", ] @@ -716,6 +719,16 @@ dependencies = [ "regex", ] +[[package]] +name = "tree-sitter-highlight" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e3c800ac0db1562a045a4893cbbd7c484eb93426cae5632f9e5d24dd588cd1" +dependencies = [ + "regex", + "tree-sitter", +] + [[package]] name = "tui" version = "0.10.0" diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs index 67f230090..5f941fb57 100644 --- a/helix-core/src/state.rs +++ b/helix-core/src/state.rs @@ -81,6 +81,17 @@ impl State { // foldable // changeFilter/transactionFilter + // TODO: move that accepts a boundary matcher fn/list, we keep incrementing until we hit + // a boundary + + // TODO: edits, does each keypress trigger a full command? I guess it's adding to the same + // transaction + // There should be three pieces of the state: current transaction, the original doc, "preview" + // of the new state. + // 1. apply the newly generated keypress as a transaction + // 2. compose onto a ongoing transaction + // 3. on insert mode leave, that transaction gets stored into undo history + pub fn move_pos( &self, pos: usize, diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index ce3add5c3..dcb01c1d3 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -24,3 +24,5 @@ crossterm = { version = "0.17", features = ["event-stream"] } smol = "1" num_cpus = "1.13.0" tui = { version = "0.10.0", default-features = false, features = ["crossterm"] } +tree-sitter = "0.16.1" +tree-sitter-highlight = "0.2.0" diff --git a/helix-term/src/editor.rs b/helix-term/src/editor.rs index e019b5baf..1b58c46db 100644 --- a/helix-term/src/editor.rs +++ b/helix-term/src/editor.rs @@ -216,3 +216,118 @@ impl Editor { Ok(()) } } + +// TODO: language configs: +// tabSize, fileExtension etc, mapping to tree sitter parser +// themes: +// map tree sitter highlights to color values +// +// TODO: expand highlight thing so we're able to render only viewport range +// TODO: async: maybe pre-cache scopes as empty so we render all graphemes initially as regular +////text until calc finishes +// TODO: scope matching: biggest union match? [string] & [html, string], [string, html] & [ string, html] +// can do this by sorting our theme matches based on array len (longest first) then stopping at the +// first rule that matches (rule.all(|scope| scopes.contains(scope))) +// +// let visual_x = 0; +// let line = ?; +// for span in spans { +// start(scope) => scopes.push(scope) +// span => +// let text = rope.slice(span.start..span.end); +// let style = calculate_style(scopes); +// for each grapheme in text.graphemes() { +// // if newline += lines, continue +// +// if state.selection.ranges().any(|range| range.contains(char_index)) { +// if exactly on cursor { +// } +// if on primary cursor? { +// } +// modify style temporarily +// } +// +// // if in bounds +// +// // if tab, draw tab width +// // draw(visual_x, line, grapheme, style) +// // increment visual_x by grapheme_width(grapheme) +// // increment char_index by grapheme.len_chars() +// } +// end => scopes.pop() +// } +#[test] +fn test_parser() { + use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter}; + + let source_code = include_str!("./main.rs"); + + let highlight_names: Vec = [ + "attribute", + "constant", + "function.builtin", + "function", + "keyword", + "operator", + "property", + "punctuation", + "punctuation.bracket", + "punctuation.delimiter", + "string", + "string.special", + "tag", + "type", + "type.builtin", + "variable", + "variable.builtin", + "variable.parameter", + ] + .iter() + .cloned() + .map(String::from) + .collect(); + + let language = helix_syntax::get_language(&helix_syntax::LANG::Rust); + // let mut parser = tree_sitter::Parser::new(); + // parser.set_language(language).unwrap(); + // let tree = parser.parse(source_code, None).unwrap(); + + let mut highlighter = Highlighter::new(); + + let mut config = HighlightConfiguration::new( + language, + &std::fs::read_to_string( + "../helix-syntax/languages/tree-sitter-rust/queries/highlights.scm", + ) + .unwrap(), + &std::fs::read_to_string( + "../helix-syntax/languages/tree-sitter-rust/queries/injections.scm", + ) + .unwrap(), + "", // locals.scm + ) + .unwrap(); + + config.configure(&highlight_names); + + let highlights = highlighter + .highlight(&config, source_code.as_bytes(), None, |_| None) + .unwrap(); + + for event in highlights { + match event.unwrap() { + HighlightEvent::Source { start, end } => { + eprintln!("source: {}-{}", start, end); + // iterate over range char by char + } + HighlightEvent::HighlightStart(s) => { + eprintln!("highlight style started: {:?}", highlight_names[s.0]); + // store/push highlight styles + } + HighlightEvent::HighlightEnd => { + eprintln!("highlight style ended"); + // pop highlight styles + } + } + } +}