@@ -11,6 +11,7 @@ use crossterm::{
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),
34+ theme: HashMap<&'static str, Style>,
34impl Editor {
37impl Editor {
@@ -36,12 +39,54 @@ impl Editor {
36 let backend = CrosstermBackend::new(stdout());
39 let backend = CrosstermBackend::new(stdout());
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);
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
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::
76+ // concat (ERROR) @syntax-error and "MISSING ;" selectors for errors
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
40 let mut editor = Editor {
83 let mut editor = Editor {
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),
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);
67− .lines_at(self.first_line as usize)
68− .take(self.size.1 as usize)
69− .map(|x| x.as_str().unwrap());
71 let mut stdout = stdout();
109 let mut stdout = stdout();
73− for (n, line) in lines.enumerate() {
111+ //
74− execute!(
112+ use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter};
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",
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",
128+ // "punctuation.bracket",
129+ "punctuation.delimiter",
138+ "variable.parameter",
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();
151+ let mut highlighter = Highlighter::new();
153+ let mut config = HighlightConfiguration::new(
155+ &std::fs::read_to_string(
156+ "../helix-syntax/languages/tree-sitter-rust/queries/highlights.scm",
159+ &std::fs::read_to_string(
160+ "../helix-syntax/languages/tree-sitter-rust/queries/injections.scm",
167+ config.configure(&highlight_names);
169+ // TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset,
171+ let source_code = state.doc.to_string();
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)
179+ let mut spans = Vec::new();
183+ let mut visual_x = 0;
186+ for event in highlights {
187+ match event.unwrap() {
188+ HighlightEvent::HighlightStart(span) => {
189+ // eprintln!("highlight style started: {:?}", highlight_names[span.0]);
192+ HighlightEvent::HighlightEnd => {
194+ // eprintln!("highlight style ended");
196+ HighlightEvent::Source { start, end } => {
197+ // TODO: filter out spans out of viewport for now..
199+ let start = state.doc.byte_to_char(start);
200+ let end = state.doc.byte_to_char(end);
202+ let text = state.doc.slice(start..end);
204+ use helix_core::graphemes::{grapheme_width, RopeGraphemes};
206+ use tui::style::Color;
207+ let style = match spans.first() {
210+ .get(highlight_names[span.0].as_str())
211+ .map(|style| *style)
212+ .unwrap_or(Style::default().fg(Color::Rgb(0, 0, 255))),
214+ None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
215+ // None => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
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);
104− surface.set_style(area, select);
232+ visual_x += width;
106− // TODO: don't highlight next char in append mode
234+ // if grapheme == "\t"
242+ // let lines = state
244+ // .lines_at(self.first_line as usize)
245+ // .take(self.size.1 as usize)
246+ // .map(|x| x.as_str().unwrap());
248+ // for (n, line) in lines.enumerate() {
251+ // SetForegroundColor(Color::DarkCyan),
252+ // cursor::MoveTo(0, n as u16),
253+ // Print((n + 1).to_string())
256+ // surface.set_string(2, n as u16, line, Style::default());
259+ // // SetForegroundColor(Color::Reset),
260+ // // cursor::MoveTo(2, n as u16),
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(
276+ // (x2 - x1 + 1) as u16,
277+ // (y2 - y1 + 1) as u16,
279+ // surface.set_style(area, select);
281+ // // TODO: don't highlight next char in append mode
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",
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)
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());
302+ self.surface = surface;
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};
263− let source_code = include_str!("./main.rs");
439+ let source_code = include_str!("../test.rs");
265 let highlight_names: Vec<String> = [
441 let highlight_names: Vec<String> = [
266 "attribute",
442 "attribute",