on master help sign in
tree-sitter based syntax highlighting draft
b647c7aBlaž Hrastnik
helix-core/src/lib.rs+1−1
@@ -1,6 +1,6 @@
1#![allow(unused)]
1#![allow(unused)]
2pub mod commands;
2pub mod commands;
3−mod graphemes;
3+pub mod graphemes;
4mod selection;
4mod selection;
5pub mod state;
5pub mod state;
6mod transaction;
6mod transaction;
helix-term/src/editor.rs+233−57
@@ -11,6 +11,7 @@ use crossterm::{
11
11
12use helix_core::{state::coords_at_pos, state::Mode, State};
12use helix_core::{state::coords_at_pos, state::Mode, State};
13use smol::prelude::*;
13use smol::prelude::*;
14+use std::collections::HashMap;
14use std::io::{self, stdout, Write};
15use std::io::{self, stdout, Write};
15use std::path::PathBuf;
16use std::path::PathBuf;
16use std::time::Duration;
17use std::time::Duration;
@@ -29,6 +30,8 @@ pub struct Editor {
29 state: Option<State>,
30 state: Option<State>,
30 first_line: u16,
31 first_line: u16,
31 size: (u16, u16),
32 size: (u16, u16),
33+ surface: Surface,
34+ theme: HashMap<&'static str, Style>,
32}
35}
33
36
34impl Editor {
37impl Editor {
@@ -36,12 +39,54 @@ impl Editor {
36 let backend = CrosstermBackend::new(stdout());
39 let backend = CrosstermBackend::new(stdout());
37
40
38 let mut terminal = Terminal::new(backend)?;
41 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+ };
39
82
40 let mut editor = Editor {
83 let mut editor = Editor {
41 terminal,
84 terminal,
42 state: None,
85 state: None,
43 first_line: 0,
86 first_line: 0,
44− size: terminal::size().unwrap(),
87+ size,
88+ surface: Surface::empty(area),
89+ theme,
45 };
90 };
46
91
47 if let Some(file) = args.files.pop() {
92 if let Some(file) = args.files.pop() {
@@ -61,69 +106,200 @@ impl Editor {
61 Some(state) => {
106 Some(state) => {
62 let area = Rect::new(0, 0, self.size.0, self.size.1);
107 let area = Rect::new(0, 0, self.size.0, self.size.1);
63 let mut surface = Surface::empty(area);
108 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−
71 let mut stdout = stdout();
109 let mut stdout = stdout();
72
110
73− for (n, line) in lines.enumerate() {
111+ //
74− execute!(
112+ use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter};
75− stdout,
113+
76− SetForegroundColor(Color::DarkCyan),
114+ let highlight_names: Vec<String> = [
77− cursor::MoveTo(0, n as u16),
115+ "attribute",
78− Print((n + 1).to_string())
116+ "constant.builtin",
79− );
117+ "constant",
80−
118+ "function.builtin",
81− surface.set_string(2, n as u16, line, Style::default());
119+ "function.macro",
82− // execute!(
120+ "function",
83− // stdout,
121+ "keyword",
84− // SetForegroundColor(Color::Reset),
122+ "operator",
85− // cursor::MoveTo(2, n as u16),
123+ "property",
86− // Print(line)
124+ "punctuation",
87− // );
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
88− }
216+ };
89−
217+
90− // iterate over selections and render them
218+ // iterate over range char by char
91− let select = Style::default().bg(tui::style::Color::LightBlue);
219+ for grapheme in RopeGraphemes::new(&text) {
92− let text = state.doc.slice(..);
220+ // TODO: track current char_index
93− for range in state.selection.ranges() {
221+
94− // get terminal coords for x,y for each range pos
222+ if grapheme == "\n" {
95− // TODO: this won't work with multiline
223+ visual_x = 0;
96− let (y1, x1) = coords_at_pos(&text, range.from());
224+ line += 1;
97− let (y2, x2) = coords_at_pos(&text, range.to());
225+ } else {
98− let area = Rect::new(
226+ // Cow will prevent allocations if span contained in a single slice
99− (x1 + 2) as u16,
227+ // which should really be the majority case
100− y1 as u16,
228+ let grapheme = std::borrow::Cow::from(grapheme);
101− (x2 - x1 + 1) as u16,
229+ let width = grapheme_width(&grapheme) as u16;
102− (y2 - y1 + 1) as u16,
230+ surface.set_string(offset + visual_x, line, grapheme, style);
103− );
231+
104− surface.set_style(area, select);
232+ visual_x += width;
105−
233+ }
106− // TODO: don't highlight next char in append mode
234+ // if grapheme == "\t"
235+ }
236+ }
237+ }
107 }
238 }
108
239
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+
109− let mode = match state.mode {
284+ // let mode = match state.mode {
110− Mode::Insert => "INS",
285+ // Mode::Insert => "INS",
111− Mode::Normal => "NOR",
286+ // Mode::Normal => "NOR",
112− };
287+ // };
113−
288+
114− execute!(
289+ // execute!(
115− stdout,
290+ // stdout,
116− SetForegroundColor(Color::Reset),
291+ // SetForegroundColor(Color::Reset),
117− cursor::MoveTo(0, self.size.1),
292+ // cursor::MoveTo(0, self.size.1),
118− Print(mode)
293+ // Print(mode)
119− );
294+ // );
120
295
121 use tui::backend::Backend;
296 use tui::backend::Backend;
122− // TODO: double buffer and diff here
297+ // // TODO: double buffer and diff here
123− let empty = Surface::empty(area);
124 self.terminal
298 self.terminal
125 .backend_mut()
299 .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;
127
303
128 // set cursor shape
304 // set cursor shape
129 match state.mode {
305 match state.mode {
@@ -260,7 +436,7 @@ impl Editor {
260fn test_parser() {
436fn test_parser() {
261 use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter};
437 use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter};
262
438
263− let source_code = include_str!("./main.rs");
439+ let source_code = include_str!("../test.rs");
264
440
265 let highlight_names: Vec<String> = [
441 let highlight_names: Vec<String> = [
266 "attribute",
442 "attribute",
helix-term/src/keymap.rs−17
@@ -79,23 +79,6 @@ use std::collections::HashMap;
79// }
79// }
80// }
80// }
81
81
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−
99type Keymap = HashMap<Key, Command>;
82type Keymap = HashMap<Key, Command>;
100
83
101pub fn default() -> Keymap {
84pub 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 @@
1#![allow(unused)]
1#![allow(unused)]
2−// mod editor;
2+#[macro_use]
3−// mod component;
3+mod macros;
4mod editor;
4mod editor;
5mod keymap;
5mod keymap;
6
6
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+}