tree-sitter based syntax highlighting draft

pull/1/head
Blaž Hrastnik 4 years ago
parent 25b3f98e3d
commit b647c7a773

@ -1,6 +1,6 @@
#![allow(unused)]
pub mod commands;
mod graphemes;
pub mod graphemes;
mod selection;
pub mod state;
mod transaction;

@ -11,6 +11,7 @@ use crossterm::{
use helix_core::{state::coords_at_pos, state::Mode, State};
use smol::prelude::*;
use std::collections::HashMap;
use std::io::{self, stdout, Write};
use std::path::PathBuf;
use std::time::Duration;
@ -29,6 +30,8 @@ pub struct Editor {
state: Option<State>,
first_line: u16,
size: (u16, u16),
surface: Surface,
theme: HashMap<&'static str, Style>,
}
impl Editor {
@ -36,12 +39,54 @@ impl Editor {
let backend = CrosstermBackend::new(stdout());
let mut terminal = Terminal::new(backend)?;
let size = terminal::size().unwrap();
let area = Rect::new(0, 0, size.0, size.1);
use tui::style::Color;
let theme = hashmap! {
"attribute" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
"keyword" => Style::default().fg(Color::Rgb(236, 205, 186)), // almond
"punctuation" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
"punctuation.delimiter" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
"operator" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
"property" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
"variable.parameter" => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
// TODO distinguish type from type.builtin?
"type" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
"type.builtin" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
"constructor" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
"function" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
"function.macro" => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
"comment" => Style::default().fg(Color::Rgb(105, 124, 129)), // sirocco
"variable.builtin" => Style::default().fg(Color::Rgb(159, 242, 143)), // mint
"constant" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
"constant.builtin" => Style::default().fg(Color::Rgb(255, 255, 255)), // white
"string" => Style::default().fg(Color::Rgb(204, 204, 204)), // silver
"escape" => Style::default().fg(Color::Rgb(239, 186, 93)), // honey
// used for lifetimes
"label" => Style::default().fg(Color::Rgb(239, 186, 93)), // honey
// TODO: diferentiate number builtin
// TODO: diferentiate doc comment
// TODO: variable as lilac
// TODO: mod/use statements as white
// TODO: mod stuff as chamoise
// TODO: add "(scoped_identifier) @path" for std::mem::
//
// concat (ERROR) @syntax-error and "MISSING ;" selectors for errors
"module" => Style::default().fg(Color::Rgb(255, 0, 0)), // white
"variable" => Style::default().fg(Color::Rgb(255, 0, 0)), // white
"function.builtin" => Style::default().fg(Color::Rgb(255, 0, 0)), // white
};
let mut editor = Editor {
terminal,
state: None,
first_line: 0,
size: terminal::size().unwrap(),
size,
surface: Surface::empty(area),
theme,
};
if let Some(file) = args.files.pop() {
@ -61,69 +106,200 @@ impl Editor {
Some(state) => {
let area = Rect::new(0, 0, self.size.0, self.size.1);
let mut surface = Surface::empty(area);
let lines = state
.doc
.lines_at(self.first_line as usize)
.take(self.size.1 as usize)
.map(|x| x.as_str().unwrap());
let mut stdout = stdout();
for (n, line) in lines.enumerate() {
execute!(
stdout,
SetForegroundColor(Color::DarkCyan),
cursor::MoveTo(0, n as u16),
Print((n + 1).to_string())
);
surface.set_string(2, n as u16, line, Style::default());
// execute!(
// stdout,
// SetForegroundColor(Color::Reset),
// cursor::MoveTo(2, n as u16),
// Print(line)
// );
}
// iterate over selections and render them
let select = Style::default().bg(tui::style::Color::LightBlue);
let text = state.doc.slice(..);
for range in state.selection.ranges() {
// get terminal coords for x,y for each range pos
// TODO: this won't work with multiline
let (y1, x1) = coords_at_pos(&text, range.from());
let (y2, x2) = coords_at_pos(&text, range.to());
let area = Rect::new(
(x1 + 2) as u16,
y1 as u16,
(x2 - x1 + 1) as u16,
(y2 - y1 + 1) as u16,
);
surface.set_style(area, select);
// TODO: don't highlight next char in append mode
//
use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter};
let highlight_names: Vec<String> = [
"attribute",
"constant.builtin",
"constant",
"function.builtin",
"function.macro",
"function",
"keyword",
"operator",
"property",
"punctuation",
"comment",
"escape",
"label",
// "punctuation.bracket",
"punctuation.delimiter",
"string",
"string.special",
"tag",
"type",
"type.builtin",
"constructor",
"variable",
"variable.builtin",
"variable.parameter",
"path",
]
.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);
// TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset,
// pos|)
let source_code = state.doc.to_string();
// TODO: cache highlight results
// TODO: only recalculate when state.doc is actually modified
let highlights = highlighter
.highlight(&config, source_code.as_bytes(), None, |_| None)
.unwrap();
let mut spans = Vec::new();
let offset = 2;
let mut visual_x = 0;
let mut line = 0;
for event in highlights {
match event.unwrap() {
HighlightEvent::HighlightStart(span) => {
// eprintln!("highlight style started: {:?}", highlight_names[span.0]);
spans.push(span);
}
HighlightEvent::HighlightEnd => {
spans.pop();
// eprintln!("highlight style ended");
}
HighlightEvent::Source { start, end } => {
// TODO: filter out spans out of viewport for now..
let start = state.doc.byte_to_char(start);
let end = state.doc.byte_to_char(end);
let text = state.doc.slice(start..end);
use helix_core::graphemes::{grapheme_width, RopeGraphemes};
use tui::style::Color;
let style = match spans.first() {
Some(span) => self
.theme
.get(highlight_names[span.0].as_str())
.map(|style| *style)
.unwrap_or(Style::default().fg(Color::Rgb(0, 0, 255))),
None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
// None => Style::default().fg(Color::Rgb(219, 191, 239)), // lilac
};
// iterate over range char by char
for grapheme in RopeGraphemes::new(&text) {
// TODO: track current char_index
if grapheme == "\n" {
visual_x = 0;
line += 1;
} else {
// Cow will prevent allocations if span contained in a single slice
// which should really be the majority case
let grapheme = std::borrow::Cow::from(grapheme);
let width = grapheme_width(&grapheme) as u16;
surface.set_string(offset + visual_x, line, grapheme, style);
visual_x += width;
}
// if grapheme == "\t"
}
}
}
}
let mode = match state.mode {
Mode::Insert => "INS",
Mode::Normal => "NOR",
};
execute!(
stdout,
SetForegroundColor(Color::Reset),
cursor::MoveTo(0, self.size.1),
Print(mode)
);
//
// let lines = state
// .doc
// .lines_at(self.first_line as usize)
// .take(self.size.1 as usize)
// .map(|x| x.as_str().unwrap());
// for (n, line) in lines.enumerate() {
// execute!(
// stdout,
// SetForegroundColor(Color::DarkCyan),
// cursor::MoveTo(0, n as u16),
// Print((n + 1).to_string())
// );
// surface.set_string(2, n as u16, line, Style::default());
// // execute!(
// // stdout,
// // SetForegroundColor(Color::Reset),
// // cursor::MoveTo(2, n as u16),
// // Print(line)
// // );
// }
// // iterate over selections and render them
// let select = Style::default().bg(tui::style::Color::LightBlue);
// let text = state.doc.slice(..);
// for range in state.selection.ranges() {
// // get terminal coords for x,y for each range pos
// // TODO: this won't work with multiline
// let (y1, x1) = coords_at_pos(&text, range.from());
// let (y2, x2) = coords_at_pos(&text, range.to());
// let area = Rect::new(
// (x1 + 2) as u16,
// y1 as u16,
// (x2 - x1 + 1) as u16,
// (y2 - y1 + 1) as u16,
// );
// surface.set_style(area, select);
// // TODO: don't highlight next char in append mode
// }
// let mode = match state.mode {
// Mode::Insert => "INS",
// Mode::Normal => "NOR",
// };
// execute!(
// stdout,
// SetForegroundColor(Color::Reset),
// cursor::MoveTo(0, self.size.1),
// Print(mode)
// );
use tui::backend::Backend;
// TODO: double buffer and diff here
let empty = Surface::empty(area);
// // TODO: double buffer and diff here
self.terminal
.backend_mut()
.draw(empty.diff(&surface).into_iter());
.draw(self.surface.diff(&surface).into_iter());
// swap the buffer
self.surface = surface;
// set cursor shape
match state.mode {
@ -260,7 +436,7 @@ impl Editor {
fn test_parser() {
use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter};
let source_code = include_str!("./main.rs");
let source_code = include_str!("../test.rs");
let highlight_names: Vec<String> = [
"attribute",

@ -79,23 +79,6 @@ use std::collections::HashMap;
// }
// }
macro_rules! hashmap {
(@single $($x:tt)*) => (());
(@count $($rest:expr),*) => (<[()]>::len(&[$(hashmap!(@single $rest)),*]));
($($key:expr => $value:expr,)+) => { hashmap!($($key => $value),+) };
($($key:expr => $value:expr),*) => {
{
let _cap = hashmap!(@count $($key),*);
let mut _map = ::std::collections::HashMap::with_capacity(_cap);
$(
let _ = _map.insert($key, $value);
)*
_map
}
};
}
type Keymap = HashMap<Key, Command>;
pub fn default() -> Keymap {

@ -0,0 +1,16 @@
macro_rules! hashmap {
(@single $($x:tt)*) => (());
(@count $($rest:expr),*) => (<[()]>::len(&[$(hashmap!(@single $rest)),*]));
($($key:expr => $value:expr,)+) => { hashmap!($($key => $value),+) };
($($key:expr => $value:expr),*) => {
{
let _cap = hashmap!(@count $($key),*);
let mut _map = ::std::collections::HashMap::with_capacity(_cap);
$(
let _ = _map.insert($key, $value);
)*
_map
}
};
}

@ -1,6 +1,6 @@
#![allow(unused)]
// mod editor;
// mod component;
#[macro_use]
mod macros;
mod editor;
mod keymap;

@ -0,0 +1,36 @@
pub struct TextArea {
properties: Properties,
frame: Rect,
}
impl Component for TextArea {
type Message = ();
type Properties = Properties;
fn create(properties: Self::Properties, frame: Rect, _link: ComponentLink<Self>) -> Self {
TextArea { properties, frame }
}
fn change<'a>(&'a mut self, properties: Self::Properties) -> ShouldRender {
let a: &'static str = "ase";
let q = 2u8;
let q = 2 as u16;
Some(0);
true;
self.properties = properties;
ShouldRender::Yes
}
fn resize(&mut self, frame: Rect) -> ShouldRender {
println!("hello world! \" test");
self.frame = frame;
ShouldRender::Yes
}
fn view(&self) -> Layout {
let mut canvas = Canvas::new(self.frame.size);
canvas.clear(self.properties.theme.text);
self.draw_text(&mut canvas);
canvas.into()
}
}
Loading…
Cancel
Save