on master help sign in
tree-sitter based syntax highlighting draft
commitb647c7a773fb3323ccc884f4d0d4ce25c3e8aea8 authorBlaž Hrastnik <blaz@mxxn.io> dateThu 10 Sep 2020, 13:55 (6 years ago) parent25b3f98
6 files changed+288−77
helix-core/src/lib.rs+1−1 helix-term/src/editor.rs+233−57 helix-term/src/keymap.rs−17 helix-term/src/macros.rs+16 helix-term/src/main.rs+2−2 helix-term/test.rs+36
helix-core/src/lib.rs+1−1
@@ -1,6 +1,6 @@
11#![allow(unused)]
22pub mod commands;
3−mod graphemes;
3+pub mod graphemes;
44mod selection;
55pub mod state;
66mod transaction;
helix-term/src/editor.rs+233−57
@@ -11,6 +11,7 @@ use crossterm::{
1111
1212use helix_core::{state::coords_at_pos, state::Mode, State};
1313use smol::prelude::*;
14+use std::collections::HashMap;
1415use std::io::{self, stdout, Write};
1516use std::path::PathBuf;
1617use std::time::Duration;
@@ -29,6 +30,8 @@ pub struct Editor {
2930 state: Option<State>,
3031 first_line: u16,
3132 size: (u16, u16),
33+ surface: Surface,
34+ theme: HashMap<&'static str, Style>,
3235}
3336
3437impl Editor {
@@ -36,12 +39,54 @@ impl Editor {
3639 let backend = CrosstermBackend::new(stdout());
3740
3841 let mut terminal = Terminal::new(backend)?;
42+ let size = terminal::size().unwrap();
43+ let area = Rect::new(0, 0, size.0, size.1);
44+
45+ use tui::style::Color;
46+ let theme = hashmap! {
47+ "attribute" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
48+ "keyword" => Style::default().fg(Color::Rgb(236, 205, 186)), // almond
49+ "punctuation" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
50+ "punctuation.delimiter" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
51+ "operator" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
52+ "property" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
53+ "variable.parameter" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
54+ // TODO distinguish type from type.builtin?
55+ "type" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
56+ "type.builtin" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
57+ "constructor" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
58+ "function" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
59+ "function.macro" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
60+ "comment" => Style::default().fg(Color::Rgb(105, 124, 129)), // sirocco
61+ "variable.builtin" => Style::default().fg(Color::Rgb(159, 242, 143)), // mint
62+ "constant" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
63+ "constant.builtin" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
64+ "string" => Style::default().fg(Color::Rgb(204, 204, 204)), // silver
65+ "escape" => Style::default().fg(Color::Rgb(239, 186, 93)), // honey
66+ // used for lifetimes
67+ "label" => Style::default().fg(Color::Rgb(239, 186, 93)), // honey
68+
69+ // TODO: diferentiate number builtin
70+ // TODO: diferentiate doc comment
71+ // TODO: variable as lilac
72+ // TODO: mod/use statements as white
73+ // TODO: mod stuff as chamoise
74+ // TODO: add "(scoped_identifier) @path" for std::mem::
75+ //
76+ // concat (ERROR) @syntax-error and "MISSING ;" selectors for errors
77+
78+ "module" => Style::default().fg(Color::Rgb(255, 0, 0)), // white
79+ "variable" => Style::default().fg(Color::Rgb(255, 0, 0)), // white
80+ "function.builtin" => Style::default().fg(Color::Rgb(255, 0, 0)), // white
81+ };
3982
4083 let mut editor = Editor {
4184 terminal,
4285 state: None,
4386 first_line: 0,
44− size: terminal::size().unwrap(),
87+ size,
88+ surface: Surface::empty(area),
89+ theme,
4590 };
4691
4792 if let Some(file) = args.files.pop() {
@@ -61,69 +106,200 @@ impl Editor {
61106 Some(state) => {
62107 let area = Rect::new(0, 0, self.size.0, self.size.1);
63108 let mut surface = Surface::empty(area);
64−
65− let lines = state
66− .doc
67− .lines_at(self.first_line as usize)
68− .take(self.size.1 as usize)
69− .map(|x| x.as_str().unwrap());
70−
71109 let mut stdout = stdout();
72110
73− for (n, line) in lines.enumerate() {
74− execute!(
75− stdout,
76− SetForegroundColor(Color::DarkCyan),
77− cursor::MoveTo(0, n as u16),
78− Print((n + 1).to_string())
79− );
80−
81− surface.set_string(2, n as u16, line, Style::default());
82− // execute!(
83− // stdout,
84− // SetForegroundColor(Color::Reset),
85− // cursor::MoveTo(2, n as u16),
86− // Print(line)
87− // );
88− }
89−
90− // iterate over selections and render them
91− let select = Style::default().bg(tui::style::Color::LightBlue);
92− let text = state.doc.slice(..);
93− for range in state.selection.ranges() {
94− // get terminal coords for x,y for each range pos
95− // TODO: this won't work with multiline
96− let (y1, x1) = coords_at_pos(&text, range.from());
97− let (y2, x2) = coords_at_pos(&text, range.to());
98− let area = Rect::new(
99− (x1 + 2) as u16,
100− y1 as u16,
101− (x2 - x1 + 1) as u16,
102− (y2 - y1 + 1) as u16,
103− );
104− surface.set_style(area, select);
105−
106− // TODO: don't highlight next char in append mode
111+ //
112+ use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter};
113+
114+ let highlight_names: Vec<String> = [
115+ "attribute",
116+ "constant.builtin",
117+ "constant",
118+ "function.builtin",
119+ "function.macro",
120+ "function",
121+ "keyword",
122+ "operator",
123+ "property",
124+ "punctuation",
125+ "comment",
126+ "escape",
127+ "label",
128+ // "punctuation.bracket",
129+ "punctuation.delimiter",
130+ "string",
131+ "string.special",
132+ "tag",
133+ "type",
134+ "type.builtin",
135+ "constructor",
136+ "variable",
137+ "variable.builtin",
138+ "variable.parameter",
139+ "path",
140+ ]
141+ .iter()
142+ .cloned()
143+ .map(String::from)
144+ .collect();
145+
146+ let language = helix_syntax::get_language(&helix_syntax::LANG::Rust);
147+ // let mut parser = tree_sitter::Parser::new();
148+ // parser.set_language(language).unwrap();
149+ // let tree = parser.parse(source_code, None).unwrap();
150+
151+ let mut highlighter = Highlighter::new();
152+
153+ let mut config = HighlightConfiguration::new(
154+ language,
155+ &std::fs::read_to_string(
156+ "../helix-syntax/languages/tree-sitter-rust/queries/highlights.scm",
157+ )
158+ .unwrap(),
159+ &std::fs::read_to_string(
160+ "../helix-syntax/languages/tree-sitter-rust/queries/injections.scm",
161+ )
162+ .unwrap(),
163+ "", // locals.scm
164+ )
165+ .unwrap();
166+
167+ config.configure(&highlight_names);
168+
169+ // TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset,
170+ // pos|)
171+ let source_code = state.doc.to_string();
172+
173+ // TODO: cache highlight results
174+ // TODO: only recalculate when state.doc is actually modified
175+ let highlights = highlighter
176+ .highlight(&config, source_code.as_bytes(), None, |_| None)
177+ .unwrap();
178+
179+ let mut spans = Vec::new();
180+
181+ let offset = 2;
182+
183+ let mut visual_x = 0;
184+ let mut line = 0;
185+
186+ for event in highlights {
187+ match event.unwrap() {
188+ HighlightEvent::HighlightStart(span) => {
189+ // eprintln!("highlight style started: {:?}", highlight_names[span.0]);
190+ spans.push(span);
191+ }
192+ HighlightEvent::HighlightEnd => {
193+ spans.pop();
194+ // eprintln!("highlight style ended");
195+ }
196+ HighlightEvent::Source { start, end } => {
197+ // TODO: filter out spans out of viewport for now..
198+
199+ let start = state.doc.byte_to_char(start);
200+ let end = state.doc.byte_to_char(end);
201+
202+ let text = state.doc.slice(start..end);
203+
204+ use helix_core::graphemes::{grapheme_width, RopeGraphemes};
205+
206+ use tui::style::Color;
207+ let style = match spans.first() {
208+ Some(span) => self
209+ .theme
210+ .get(highlight_names[span.0].as_str())
211+ .map(|style| *style)
212+ .unwrap_or(Style::default().fg(Color::Rgb(0, 0, 255))),
213+
214+ None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
215+ // None => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
216+ };
217+
218+ // iterate over range char by char
219+ for grapheme in RopeGraphemes::new(&text) {
220+ // TODO: track current char_index
221+
222+ if grapheme == "\n" {
223+ visual_x = 0;
224+ line += 1;
225+ } else {
226+ // Cow will prevent allocations if span contained in a single slice
227+ // which should really be the majority case
228+ let grapheme = std::borrow::Cow::from(grapheme);
229+ let width = grapheme_width(&grapheme) as u16;
230+ surface.set_string(offset + visual_x, line, grapheme, style);
231+
232+ visual_x += width;
233+ }
234+ // if grapheme == "\t"
235+ }
236+ }
237+ }
107238 }
108239
109− let mode = match state.mode {
110− Mode::Insert => "INS",
111− Mode::Normal => "NOR",
112− };
113−
114− execute!(
115− stdout,
116− SetForegroundColor(Color::Reset),
117− cursor::MoveTo(0, self.size.1),
118− Print(mode)
119− );
240+ //
241+
242+ // let lines = state
243+ // .doc
244+ // .lines_at(self.first_line as usize)
245+ // .take(self.size.1 as usize)
246+ // .map(|x| x.as_str().unwrap());
247+
248+ // for (n, line) in lines.enumerate() {
249+ // execute!(
250+ // stdout,
251+ // SetForegroundColor(Color::DarkCyan),
252+ // cursor::MoveTo(0, n as u16),
253+ // Print((n + 1).to_string())
254+ // );
255+
256+ // surface.set_string(2, n as u16, line, Style::default());
257+ // // execute!(
258+ // // stdout,
259+ // // SetForegroundColor(Color::Reset),
260+ // // cursor::MoveTo(2, n as u16),
261+ // // Print(line)
262+ // // );
263+ // }
264+
265+ // // iterate over selections and render them
266+ // let select = Style::default().bg(tui::style::Color::LightBlue);
267+ // let text = state.doc.slice(..);
268+ // for range in state.selection.ranges() {
269+ // // get terminal coords for x,y for each range pos
270+ // // TODO: this won't work with multiline
271+ // let (y1, x1) = coords_at_pos(&text, range.from());
272+ // let (y2, x2) = coords_at_pos(&text, range.to());
273+ // let area = Rect::new(
274+ // (x1 + 2) as u16,
275+ // y1 as u16,
276+ // (x2 - x1 + 1) as u16,
277+ // (y2 - y1 + 1) as u16,
278+ // );
279+ // surface.set_style(area, select);
280+
281+ // // TODO: don't highlight next char in append mode
282+ // }
283+
284+ // let mode = match state.mode {
285+ // Mode::Insert => "INS",
286+ // Mode::Normal => "NOR",
287+ // };
288+
289+ // execute!(
290+ // stdout,
291+ // SetForegroundColor(Color::Reset),
292+ // cursor::MoveTo(0, self.size.1),
293+ // Print(mode)
294+ // );
120295
121296 use tui::backend::Backend;
122− // TODO: double buffer and diff here
123− let empty = Surface::empty(area);
297+ // // TODO: double buffer and diff here
124298 self.terminal
125299 .backend_mut()
126− .draw(empty.diff(&surface).into_iter());
300+ .draw(self.surface.diff(&surface).into_iter());
301+ // swap the buffer
302+ self.surface = surface;
127303
128304 // set cursor shape
129305 match state.mode {
@@ -260,7 +436,7 @@ impl Editor {
260436fn test_parser() {
261437 use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter};
262438
263− let source_code = include_str!("./main.rs");
439+ let source_code = include_str!("../test.rs");
264440
265441 let highlight_names: Vec<String> = [
266442 "attribute",
helix-term/src/keymap.rs−17
@@ -79,23 +79,6 @@ use std::collections::HashMap;
7979// }
8080// }
8181
82−macro_rules! hashmap {
83− (@single $($x:tt)*) => (());
84− (@count $($rest:expr),*) => (<[()]>::len(&[$(hashmap!(@single $rest)),*]));
85−
86− ($($key:expr => $value:expr,)+) => { hashmap!($($key => $value),+) };
87− ($($key:expr => $value:expr),*) => {
88− {
89− let _cap = hashmap!(@count $($key),*);
90− let mut _map = ::std::collections::HashMap::with_capacity(_cap);
91− $(
92− let _ = _map.insert($key, $value);
93− )*
94− _map
95− }
96− };
97−}
98−
9982type Keymap = HashMap<Key, Command>;
10083
10184pub fn default() -> Keymap {
helix-term/src/macros.rsnew file+16
@@ -0,0 +1,16 @@
1+macro_rules! hashmap {
2+ (@single $($x:tt)*) => (());
3+ (@count $($rest:expr),*) => (<[()]>::len(&[$(hashmap!(@single $rest)),*]));
4+
5+ ($($key:expr => $value:expr,)+) => { hashmap!($($key => $value),+) };
6+ ($($key:expr => $value:expr),*) => {
7+ {
8+ let _cap = hashmap!(@count $($key),*);
9+ let mut _map = ::std::collections::HashMap::with_capacity(_cap);
10+ $(
11+ let _ = _map.insert($key, $value);
12+ )*
13+ _map
14+ }
15+ };
16+}
helix-term/src/main.rs+2−2
@@ -1,6 +1,6 @@
11#![allow(unused)]
2−// mod editor;
3−// mod component;
2+#[macro_use]
3+mod macros;
44mod editor;
55mod keymap;
66
helix-term/test.rsnew file+36
@@ -0,0 +1,36 @@
1+pub struct TextArea {
2+ properties: Properties,
3+ frame: Rect,
4+}
5+
6+impl Component for TextArea {
7+ type Message = ();
8+ type Properties = Properties;
9+
10+ fn create(properties: Self::Properties, frame: Rect, _link: ComponentLink<Self>) -> Self {
11+ TextArea { properties, frame }
12+ }
13+
14+ fn change<'a>(&'a mut self, properties: Self::Properties) -> ShouldRender {
15+ let a: &'static str = "ase";
16+ let q = 2u8;
17+ let q = 2 as u16;
18+ Some(0);
19+ true;
20+ self.properties = properties;
21+ ShouldRender::Yes
22+ }
23+
24+ fn resize(&mut self, frame: Rect) -> ShouldRender {
25+ println!("hello world! \" test");
26+ self.frame = frame;
27+ ShouldRender::Yes
28+ }
29+
30+ fn view(&self) -> Layout {
31+ let mut canvas = Canvas::new(self.frame.size);
32+ canvas.clear(self.properties.theme.text);
33+ self.draw_text(&mut canvas);
34+ canvas.into()
35+ }
36+}