@@ -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",