Incremental parsing: rough draft.

This commit is contained in:
Blaž Hrastnik 2020-09-17 14:57:49 +09:00
parent 31999d6528
commit 088f8a82af
11 changed files with 693 additions and 160 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
target target
helix-term/rustfmt.toml helix-term/rustfmt.toml
helix-syntax/languages/

1
Cargo.lock generated
View file

@ -369,6 +369,7 @@ name = "helix-core"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"helix-syntax",
"ropey", "ropey",
"smallvec", "smallvec",
"tendril", "tendril",

View file

@ -6,5 +6,5 @@ members = [
] ]
# Build helix-syntax in release mode to make the code path faster in development. # Build helix-syntax in release mode to make the code path faster in development.
[profile.dev.package."helix-syntax"] # [profile.dev.package."helix-syntax"]
opt-level = 3 # opt-level = 3

View file

@ -7,6 +7,8 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
helix-syntax = { path = "../helix-syntax" }
ropey = "1.2.0" ropey = "1.2.0"
anyhow = "1.0.31" anyhow = "1.0.31"
smallvec = "1.4.0" smallvec = "1.4.0"

View file

@ -1,6 +1,7 @@
#![allow(unused)] #![allow(unused)]
pub mod commands; pub mod commands;
pub mod graphemes; pub mod graphemes;
mod position;
mod selection; mod selection;
pub mod state; pub mod state;
pub mod syntax; pub mod syntax;
@ -9,8 +10,10 @@ mod transaction;
pub use ropey::{Rope, RopeSlice}; pub use ropey::{Rope, RopeSlice};
pub use tendril::StrTendril as Tendril; pub use tendril::StrTendril as Tendril;
pub use position::Position;
pub use selection::Range as SelectionRange; pub use selection::Range as SelectionRange;
pub use selection::Selection; pub use selection::Selection;
pub use syntax::Syntax;
pub use state::State; pub use state::State;

View file

@ -0,0 +1,57 @@
/// Represents a single point in a text buffer. Zero indexed.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Position {
pub row: usize,
pub col: usize,
}
impl Position {
pub const fn new(row: usize, col: usize) -> Self {
Self { row, col }
}
pub const fn is_zero(self) -> bool {
self.row == 0 && self.col == 0
}
// TODO: generalize
pub fn traverse(self, text: &crate::Tendril) -> Self {
let Self { mut row, mut col } = self;
// TODO: there should be a better way here
for ch in text.chars() {
if ch == '\n' {
row += 1;
col = 0;
} else {
col += 1;
}
}
Self { row, col }
}
}
impl From<(usize, usize)> for Position {
fn from(tuple: (usize, usize)) -> Self {
Position {
row: tuple.0,
col: tuple.1,
}
}
}
impl Into<tree_sitter::Point> for Position {
fn into(self) -> tree_sitter::Point {
tree_sitter::Point::new(self.row, self.col)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_ordering() {
// (0, 5) is less than (1, 0)
assert!(Position::new(0, 5) < Position::new(1, 0));
}
}

View file

@ -1,5 +1,5 @@
use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes}; use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
use crate::{Rope, RopeSlice, Selection, SelectionRange}; use crate::{Position, Rope, RopeSlice, Selection, SelectionRange, Syntax};
use anyhow::Error; use anyhow::Error;
use std::path::PathBuf; use std::path::PathBuf;
@ -17,6 +17,9 @@ pub struct State {
pub(crate) doc: Rope, pub(crate) doc: Rope,
pub(crate) selection: Selection, pub(crate) selection: Selection,
pub(crate) mode: Mode, pub(crate) mode: Mode,
//
pub syntax: Option<Syntax>,
} }
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
@ -40,6 +43,7 @@ impl State {
doc, doc,
selection: Selection::single(0, 0), selection: Selection::single(0, 0),
mode: Mode::Normal, mode: Mode::Normal,
syntax: None,
} }
} }
@ -54,6 +58,29 @@ impl State {
let mut state = Self::new(doc); let mut state = Self::new(doc);
state.path = Some(path); state.path = Some(path);
let language = helix_syntax::get_language(&helix_syntax::LANG::Rust);
let mut highlight_config = crate::syntax::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();
// TODO: config.configure(scopes) is now delayed, is that ok?
// TODO: get_language is called twice
let syntax = Syntax::new(helix_syntax::LANG::Rust, &state.doc, highlight_config);
state.syntax = Some(syntax);
Ok(state) Ok(state)
} }
@ -175,29 +202,29 @@ impl State {
} }
/// Coordinates are a 0-indexed line and column pair. /// Coordinates are a 0-indexed line and column pair.
type Coords = (usize, usize); // line, col pub type Coords = (usize, usize); // line, col
/// Convert a character index to (line, column) coordinates. /// Convert a character index to (line, column) coordinates.
pub fn coords_at_pos(text: &RopeSlice, pos: usize) -> Coords { pub fn coords_at_pos(text: &RopeSlice, pos: usize) -> Position {
let line = text.char_to_line(pos); let line = text.char_to_line(pos);
let line_start = text.line_to_char(line); let line_start = text.line_to_char(line);
let col = text.slice(line_start..pos).len_chars(); let col = text.slice(line_start..pos).len_chars();
(line, col) Position::new(line, col)
} }
/// Convert (line, column) coordinates to a character index. /// Convert (line, column) coordinates to a character index.
pub fn pos_at_coords(text: &RopeSlice, coords: Coords) -> usize { pub fn pos_at_coords(text: &RopeSlice, coords: Position) -> usize {
let (line, col) = coords; let Position { row, col } = coords;
let line_start = text.line_to_char(line); let line_start = text.line_to_char(row);
nth_next_grapheme_boundary(text, line_start, col) nth_next_grapheme_boundary(text, line_start, col)
} }
fn move_vertically(text: &RopeSlice, dir: Direction, pos: usize, count: usize) -> usize { fn move_vertically(text: &RopeSlice, dir: Direction, pos: usize, count: usize) -> usize {
let (line, col) = coords_at_pos(text, pos); let Position { row, col } = coords_at_pos(text, pos);
let new_line = match dir { let new_line = match dir {
Direction::Backward => line.saturating_sub(count), Direction::Backward => row.saturating_sub(count),
Direction::Forward => std::cmp::min(line.saturating_add(count), text.len_lines() - 1), Direction::Forward => std::cmp::min(row.saturating_add(count), text.len_lines() - 1),
}; };
// convert to 0-indexed, subtract another 1 because len_chars() counts \n // convert to 0-indexed, subtract another 1 because len_chars() counts \n
@ -210,7 +237,7 @@ fn move_vertically(text: &RopeSlice, dir: Direction, pos: usize, count: usize) -
col col
}; };
pos_at_coords(text, (new_line, new_col)) pos_at_coords(text, Position::new(new_line, new_col))
} }
#[cfg(test)] #[cfg(test)]
@ -220,32 +247,32 @@ mod test {
#[test] #[test]
fn test_coords_at_pos() { fn test_coords_at_pos() {
let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
assert_eq!(coords_at_pos(&text.slice(..), 0), (0, 0)); assert_eq!(coords_at_pos(&text.slice(..), 0), (0, 0).into());
assert_eq!(coords_at_pos(&text.slice(..), 5), (0, 5)); // position on \n assert_eq!(coords_at_pos(&text.slice(..), 5), (0, 5).into()); // position on \n
assert_eq!(coords_at_pos(&text.slice(..), 6), (1, 0)); // position on w assert_eq!(coords_at_pos(&text.slice(..), 6), (1, 0).into()); // position on w
assert_eq!(coords_at_pos(&text.slice(..), 7), (1, 1)); // position on o assert_eq!(coords_at_pos(&text.slice(..), 7), (1, 1).into()); // position on o
assert_eq!(coords_at_pos(&text.slice(..), 10), (1, 4)); // position on d assert_eq!(coords_at_pos(&text.slice(..), 10), (1, 4).into()); // position on d
} }
#[test] #[test]
fn test_pos_at_coords() { fn test_pos_at_coords() {
let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
assert_eq!(pos_at_coords(&text.slice(..), (0, 0)), 0); assert_eq!(pos_at_coords(&text.slice(..), (0, 0).into()), 0);
assert_eq!(pos_at_coords(&text.slice(..), (0, 5)), 5); // position on \n assert_eq!(pos_at_coords(&text.slice(..), (0, 5).into()), 5); // position on \n
assert_eq!(pos_at_coords(&text.slice(..), (1, 0)), 6); // position on w assert_eq!(pos_at_coords(&text.slice(..), (1, 0).into()), 6); // position on w
assert_eq!(pos_at_coords(&text.slice(..), (1, 1)), 7); // position on o assert_eq!(pos_at_coords(&text.slice(..), (1, 1).into()), 7); // position on o
assert_eq!(pos_at_coords(&text.slice(..), (1, 4)), 10); // position on d assert_eq!(pos_at_coords(&text.slice(..), (1, 4).into()), 10); // position on d
} }
#[test] #[test]
fn test_vertical_move() { fn test_vertical_move() {
let text = Rope::from("abcd\nefg\nwrs"); let text = Rope::from("abcd\nefg\nwrs");
let pos = pos_at_coords(&text.slice(..), (0, 4)); let pos = pos_at_coords(&text.slice(..), (0, 4).into());
let slice = text.slice(..); let slice = text.slice(..);
assert_eq!( assert_eq!(
coords_at_pos(&slice, move_vertically(&slice, Direction::Forward, pos, 1)), coords_at_pos(&slice, move_vertically(&slice, Direction::Forward, pos, 1)),
(1, 2) (1, 2).into()
); );
} }
} }

View file

@ -1,66 +1,397 @@
// pub struct Syntax { use crate::{Change, Rope, RopeSlice, Transaction};
// parser: Parser, pub use helix_syntax::LANG;
// } pub use helix_syntax::{get_language, get_language_name};
//impl Syntax { pub struct Syntax {
// // buffer, grammar, config, grammars, sync_timeout? grammar: Language,
// pub fn new() -> Self { parser: Parser,
// unimplemented!() cursors: Vec<QueryCursor>,
// // make a new root layer
// // track markers of injections
// //
// // track scope_descriptor: a Vec of scopes for item in tree
// //
// // fetch grammar for parser based on language string
// // update root layer
// }
// // fn buffer_changed -> call layer.update(range, new_text) on root layer and then all marker layers config: HighlightConfiguration,
// // call this on transaction.apply() -> buffer_changed(changes) root_layer: LanguageLayer,
// // }
// // fn parse(language, old_tree, ranges)
// //
// // fn tree() -> Tree
// //
// // <!--update_for_injection(grammar)-->
// // Highlighting impl Syntax {
// // fn highlight_iter() -> iterates over all the scopes // buffer, grammar, config, grammars, sync_timeout?
// // on_tokenize pub fn new(language: LANG, source: &Rope, config: HighlightConfiguration) -> Self {
// // on_change_highlighting // fetch grammar for parser based on language string
let grammar = get_language(&language);
let parser = Parser::new();
// // Commenting let root_layer = LanguageLayer::new();
// // comment_strings_for_pos
// // is_commented
// // Indentation // track markers of injections
// // suggested_indent_for_line_at_buffer_row // track scope_descriptor: a Vec of scopes for item in tree
// // suggested_indent_for_buffer_row
// // indent_level_for_line
// // TODO: Folding let mut syntax = Self {
grammar,
parser,
cursors: Vec::new(),
config,
root_layer,
};
// // Syntax APIs // update root layer
// // get_syntax_node_containing_range -> syntax.root_layer.parse(
// // ... &mut syntax.parser,
// // get_syntax_node_at_pos &syntax.config,
// // buffer_range_for_scope_at_pos source,
//} 0,
vec![Range {
start_byte: 0,
end_byte: usize::MAX,
start_point: Point::new(0, 0),
end_point: Point::new(usize::MAX, usize::MAX),
}],
);
syntax
}
pub fn configure(&mut self, scopes: &[String]) {
self.config.configure(scopes)
}
pub fn update(&mut self, source: &Rope, changeset: &ChangeSet) -> Result<(), Error> {
self.root_layer
.update(&mut self.parser, &self.config, source, changeset)
// TODO: deal with injections and update them too
}
// fn buffer_changed -> call layer.update(range, new_text) on root layer and then all marker layers
// call this on transaction.apply() -> buffer_changed(changes)
//
// fn parse(language, old_tree, ranges)
//
fn tree(&self) -> &Tree {
self.root_layer.tree()
}
//
// <!--update_for_injection(grammar)-->
// Highlighting
/// Iterate over the highlighted regions for a given slice of source code.
pub fn highlight_iter<'a>(
&'a mut self,
source: &'a [u8],
cancellation_flag: Option<&'a AtomicUsize>,
mut injection_callback: impl FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a,
) -> Result<impl Iterator<Item = Result<HighlightEvent, Error>> + 'a, Error> {
// The `captures` iterator borrows the `Tree` and the `QueryCursor`, which
// prevents them from being moved. But both of these values are really just
// pointers, so it's actually ok to move them.
let mut cursor = QueryCursor::new(); // reuse a pool
let tree_ref = unsafe { mem::transmute::<_, &'static Tree>(self.tree()) };
let cursor_ref = unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) };
let query_ref = unsafe { mem::transmute::<_, &'static mut Query>(&mut self.config.query) };
let config_ref =
unsafe { mem::transmute::<_, &'static HighlightConfiguration>(&self.config) };
let captures = cursor_ref
.captures(query_ref, tree_ref.root_node(), move |n: Node| {
&source[n.byte_range()]
})
.peekable();
// manually craft the root layer based on the existing tree
let layer = HighlightIterLayer {
highlight_end_stack: Vec::new(),
scope_stack: vec![LocalScope {
inherits: false,
range: 0..usize::MAX,
local_defs: Vec::new(),
}],
cursor,
depth: 0,
_tree: None,
captures,
config: config_ref,
ranges: vec![Range {
start_byte: 0,
end_byte: usize::MAX,
start_point: Point::new(0, 0),
end_point: Point::new(usize::MAX, usize::MAX),
}],
};
let mut result = HighlightIter {
source,
byte_offset: 0,
injection_callback,
cancellation_flag,
highlighter: self,
iter_count: 0,
layers: vec![layer],
next_event: None,
last_highlight_range: None,
};
result.sort_layers();
Ok(result)
}
// on_tokenize
// on_change_highlighting
// Commenting
// comment_strings_for_pos
// is_commented
// Indentation
// suggested_indent_for_line_at_buffer_row
// suggested_indent_for_buffer_row
// indent_level_for_line
// TODO: Folding
// Syntax APIs
// get_syntax_node_containing_range ->
// ...
// get_syntax_node_at_pos
// buffer_range_for_scope_at_pos
}
pub struct LanguageLayer { pub struct LanguageLayer {
// mode // mode
// grammar // grammar
// depth // depth
// tree: Tree, tree: Option<Tree>,
} }
// impl LanguageLayer { use crate::state::{coords_at_pos, Coords};
// // fn highlight_iter() -> same as Mode but for this layer. Mode composits these use crate::transaction::{ChangeSet, Operation};
// // fn buffer_changed use crate::Tendril;
// // fn update(range)
// // fn update_injections() impl LanguageLayer {
// } pub fn new() -> Self {
Self { tree: None }
}
fn tree(&self) -> &Tree {
// TODO: no unwrap
self.tree.as_ref().unwrap()
}
fn parse<'a>(
&mut self,
parser: &mut Parser,
config: &HighlightConfiguration,
source: &Rope,
mut depth: usize,
mut ranges: Vec<Range>,
) -> Result<(), Error> {
if parser.set_included_ranges(&ranges).is_ok() {
parser
.set_language(config.language)
.map_err(|_| Error::InvalidLanguage)?;
// unsafe { syntax.parser.set_cancellation_flag(cancellation_flag) };
let tree = parser
.parse_with(
&mut |byte, _| {
if byte <= source.len_bytes() {
let (chunk, start_byte, _, _) = source.chunk_at_byte(byte);
chunk[byte - start_byte..].as_bytes()
} else {
// out of range
&[]
}
},
self.tree.as_ref(),
)
.ok_or(Error::Cancelled)?;
// unsafe { syntax.parser.set_cancellation_flag(None) };
// let mut cursor = syntax.cursors.pop().unwrap_or_else(QueryCursor::new);
// Process combined injections. (ERB, EJS, etc https://github.com/tree-sitter/tree-sitter/pull/526)
// if let Some(combined_injections_query) = &config.combined_injections_query {
// let mut injections_by_pattern_index =
// vec![(None, Vec::new(), false); combined_injections_query.pattern_count()];
// let matches =
// cursor.matches(combined_injections_query, tree.root_node(), |n: Node| {
// &source[n.byte_range()]
// });
// for mat in matches {
// let entry = &mut injections_by_pattern_index[mat.pattern_index];
// let (language_name, content_node, include_children) =
// injection_for_match(config, combined_injections_query, &mat, source);
// if language_name.is_some() {
// entry.0 = language_name;
// }
// if let Some(content_node) = content_node {
// entry.1.push(content_node);
// }
// entry.2 = include_children;
// }
// for (lang_name, content_nodes, includes_children) in injections_by_pattern_index {
// if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) {
// if let Some(next_config) = (injection_callback)(lang_name) {
// let ranges =
// Self::intersect_ranges(&ranges, &content_nodes, includes_children);
// if !ranges.is_empty() {
// queue.push((next_config, depth + 1, ranges));
// }
// }
// }
// }
// }
self.tree = Some(tree)
}
Ok(())
}
pub(crate) fn generate_edits(
text: &RopeSlice,
changeset: &ChangeSet,
) -> Vec<tree_sitter::InputEdit> {
use Operation::*;
let mut old_pos = 0;
let mut new_pos = 0;
let mut edits = Vec::new();
let mut iter = changeset.changes.iter().peekable();
// TODO; this is a lot easier with Change instead of Operation.
fn point_at_pos(text: &RopeSlice, pos: usize) -> (usize, Point) {
let byte = text.char_to_byte(pos);
let line = text.char_to_line(pos);
let line_start_byte = text.line_to_byte(line);
let col = byte - line_start_byte;
(byte, Point::new(line, col))
}
fn traverse(point: Point, text: &Tendril) -> Point {
let Point {
mut row,
mut column,
} = point;
// TODO: there should be a better way here
for ch in text.bytes() {
if ch == b'\n' {
row += 1;
column = 0;
} else {
column += 1;
}
}
Point { row, column }
}
while let Some(change) = iter.next() {
let len = match change {
Delete(i) | Retain(i) => *i,
Insert(_) => 0,
};
let old_end = old_pos + len;
match change {
Retain(_) => {
new_pos += len;
}
Delete(_) => {
let (start_byte, start_position) = point_at_pos(&text, old_pos);
let (old_end_byte, old_end_position) = point_at_pos(&text, old_end);
// TODO: Position also needs to be byte based...
// let byte = char_to_byte(old_pos)
// let line = char_to_line(old_pos)
// let line_start_byte = line_to_byte()
// Position::new(line, line_start_byte - byte)
// a subsequent ins means a replace, consume it
if let Some(Insert(s)) = iter.peek() {
iter.next();
let ins = s.chars().count();
// replacement
edits.push(tree_sitter::InputEdit {
start_byte, // old_pos to byte
old_end_byte, // old_end to byte
new_end_byte: start_byte + s.len(), // old_pos to byte + s.len()
start_position, // old pos to coords
old_end_position, // old_end to coords
new_end_position: traverse(start_position, s), // old pos + chars, newlines matter too (iter over)
});
new_pos += ins;
} else {
// deletion
edits.push(tree_sitter::InputEdit {
start_byte, // old_pos to byte
old_end_byte, // old_end to byte
new_end_byte: start_byte, // old_pos to byte
start_position, // old pos to coords
old_end_position, // old_end to coords
new_end_position: start_position, // old pos to coords
});
};
}
Insert(s) => {
let (start_byte, start_position) = point_at_pos(&text, old_pos);
let ins = s.chars().count();
// insert
edits.push(tree_sitter::InputEdit {
start_byte, // old_pos to byte
old_end_byte: start_byte, // same
new_end_byte: start_byte + s.len(), // old_pos + s.len()
start_position, // old pos to coords
old_end_position: start_position, // same
new_end_position: traverse(start_position, s), // old pos + chars, newlines matter too (iter over)
});
new_pos += ins;
}
}
old_pos = old_end;
}
edits
}
fn update(
&mut self,
parser: &mut Parser,
config: &HighlightConfiguration,
source: &Rope,
changeset: &ChangeSet,
) -> Result<(), Error> {
if changeset.is_empty() {
return Ok(());
}
let edits = Self::generate_edits(&source.slice(..), changeset);
// Notify the tree about all the changes
for edit in edits {
self.tree.as_mut().unwrap().edit(&edit);
}
self.parse(
parser,
config,
source,
0,
// TODO: what to do about this range on update
vec![Range {
start_byte: 0,
end_byte: usize::MAX,
start_point: Point::new(0, 0),
end_point: Point::new(usize::MAX, usize::MAX),
}],
)
}
// fn highlight_iter() -> same as Mode but for this layer. Mode composits these
// fn buffer_changed
// fn update(range)
// fn update_injections()
}
// -- refactored from tree-sitter-highlight to be able to retain state // -- refactored from tree-sitter-highlight to be able to retain state
// TODO: add seek() to iter // TODO: add seek() to iter
@ -169,7 +500,7 @@ where
{ {
source: &'a [u8], source: &'a [u8],
byte_offset: usize, byte_offset: usize,
highlighter: &'a mut Highlighter, highlighter: &'a mut Syntax,
injection_callback: F, injection_callback: F,
cancellation_flag: Option<&'a AtomicUsize>, cancellation_flag: Option<&'a AtomicUsize>,
layers: Vec<HighlightIterLayer<'a>>, layers: Vec<HighlightIterLayer<'a>>,
@ -179,7 +510,7 @@ where
} }
struct HighlightIterLayer<'a> { struct HighlightIterLayer<'a> {
_tree: Tree, _tree: Option<Tree>,
cursor: QueryCursor, cursor: QueryCursor,
captures: iter::Peekable<QueryCaptures<'a, &'a [u8]>>, captures: iter::Peekable<QueryCaptures<'a, &'a [u8]>>,
config: &'a HighlightConfiguration, config: &'a HighlightConfiguration,
@ -207,43 +538,43 @@ impl Highlighter {
&mut self.parser &mut self.parser
} }
/// Iterate over the highlighted regions for a given slice of source code. // /// Iterate over the highlighted regions for a given slice of source code.
pub fn highlight<'a>( // pub fn highlight<'a>(
&'a mut self, // &'a mut self,
config: &'a HighlightConfiguration, // config: &'a HighlightConfiguration,
source: &'a [u8], // source: &'a [u8],
cancellation_flag: Option<&'a AtomicUsize>, // cancellation_flag: Option<&'a AtomicUsize>,
mut injection_callback: impl FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a, // mut injection_callback: impl FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a,
) -> Result<impl Iterator<Item = Result<HighlightEvent, Error>> + 'a, Error> { // ) -> Result<impl Iterator<Item = Result<HighlightEvent, Error>> + 'a, Error> {
let layers = HighlightIterLayer::new( // let layers = HighlightIterLayer::new(
source, // source,
self, // self,
cancellation_flag, // cancellation_flag,
&mut injection_callback, // &mut injection_callback,
config, // config,
0, // 0,
vec![Range { // vec![Range {
start_byte: 0, // start_byte: 0,
end_byte: usize::MAX, // end_byte: usize::MAX,
start_point: Point::new(0, 0), // start_point: Point::new(0, 0),
end_point: Point::new(usize::MAX, usize::MAX), // end_point: Point::new(usize::MAX, usize::MAX),
}], // }],
)?; // )?;
assert_ne!(layers.len(), 0); // assert_ne!(layers.len(), 0);
let mut result = HighlightIter { // let mut result = HighlightIter {
source, // source,
byte_offset: 0, // byte_offset: 0,
injection_callback, // injection_callback,
cancellation_flag, // cancellation_flag,
highlighter: self, // highlighter: self,
iter_count: 0, // iter_count: 0,
layers, // layers,
next_event: None, // next_event: None,
last_highlight_range: None, // last_highlight_range: None,
}; // };
result.sort_layers(); // result.sort_layers();
Ok(result) // Ok(result)
} // }
} }
impl HighlightConfiguration { impl HighlightConfiguration {
@ -413,7 +744,7 @@ impl<'a> HighlightIterLayer<'a> {
/// added to the returned vector. /// added to the returned vector.
fn new<F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a>( fn new<F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a>(
source: &'a [u8], source: &'a [u8],
highlighter: &mut Highlighter, highlighter: &mut Syntax,
cancellation_flag: Option<&'a AtomicUsize>, cancellation_flag: Option<&'a AtomicUsize>,
injection_callback: &mut F, injection_callback: &mut F,
mut config: &'a HighlightConfiguration, mut config: &'a HighlightConfiguration,
@ -423,6 +754,8 @@ impl<'a> HighlightIterLayer<'a> {
let mut result = Vec::with_capacity(1); let mut result = Vec::with_capacity(1);
let mut queue = Vec::new(); let mut queue = Vec::new();
loop { loop {
// --> Tree parsing part
if highlighter.parser.set_included_ranges(&ranges).is_ok() { if highlighter.parser.set_included_ranges(&ranges).is_ok() {
highlighter highlighter
.parser .parser
@ -474,6 +807,8 @@ impl<'a> HighlightIterLayer<'a> {
} }
} }
// --> Highlighting query part
// The `captures` iterator borrows the `Tree` and the `QueryCursor`, which // The `captures` iterator borrows the `Tree` and the `QueryCursor`, which
// prevents them from being moved. But both of these values are really just // prevents them from being moved. But both of these values are really just
// pointers, so it's actually ok to move them. // pointers, so it's actually ok to move them.
@ -495,7 +830,7 @@ impl<'a> HighlightIterLayer<'a> {
}], }],
cursor, cursor,
depth, depth,
_tree: tree, _tree: Some(tree),
captures, captures,
config, config,
ranges, ranges,
@ -1016,3 +1351,106 @@ fn shrink_and_clear<T>(vec: &mut Vec<T>, capacity: usize) {
} }
vec.clear(); vec.clear();
} }
#[test]
fn test_parser() {
let highlight_names: Vec<String> = [
"attribute",
"constant",
"function.builtin",
"function",
"keyword",
"operator",
"property",
"punctuation",
"punctuation.bracket",
"punctuation.delimiter",
"string",
"string.special",
"tag",
"type",
"type.builtin",
"variable",
"variable.builtin",
"variable.parameter",
]
.iter()
.cloned()
.map(String::from)
.collect();
let language = get_language(&LANG::Rust);
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);
let source = Rope::from_str(
"
struct Stuff {}
fn main() {}
",
);
let syntax = Syntax::new(LANG::Rust, &source, config);
let tree = syntax.root_layer.tree.unwrap();
let root = tree.root_node();
assert_eq!(root.kind(), "source_file");
assert_eq!(
root.to_sexp(),
concat!(
"(source_file ",
"(struct_item name: (type_identifier) body: (field_declaration_list)) ",
"(function_item name: (identifier) parameters: (parameters) body: (block)))"
)
);
let struct_node = root.child(0).unwrap();
assert_eq!(struct_node.kind(), "struct_item");
}
#[test]
fn test_input_edits() {
use crate::State;
use tree_sitter::InputEdit;
let mut state = State::new("hello world!\ntest 123".into());
let transaction = Transaction::change(
&state,
vec![(6, 11, Some("test".into())), (12, 17, None)].into_iter(),
);
let edits = LanguageLayer::generate_edits(&state.doc.slice(..), &transaction.changes);
// transaction.apply(&mut state);
assert_eq!(
edits,
&[
InputEdit {
start_byte: 6,
old_end_byte: 11,
new_end_byte: 10,
start_position: Point { row: 0, column: 6 },
old_end_position: Point { row: 0, column: 11 },
new_end_position: Point { row: 0, column: 10 }
},
InputEdit {
start_byte: 12,
old_end_byte: 17,
new_end_byte: 12,
start_position: Point { row: 0, column: 12 },
old_end_position: Point { row: 1, column: 4 },
new_end_position: Point { row: 0, column: 12 }
}
]
);
}

View file

@ -4,7 +4,7 @@ use crate::{Rope, Selection, SelectionRange, State, Tendril};
pub type Change = (usize, usize, Option<Tendril>); pub type Change = (usize, usize, Option<Tendril>);
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
enum Operation { pub(crate) enum Operation {
/// Move cursor by n characters. /// Move cursor by n characters.
Retain(usize), Retain(usize),
/// Delete n characters. /// Delete n characters.
@ -22,7 +22,7 @@ pub enum Assoc {
// ChangeSpec = Change | ChangeSet | Vec<Change> // ChangeSpec = Change | ChangeSet | Vec<Change>
#[derive(Debug)] #[derive(Debug)]
pub struct ChangeSet { pub struct ChangeSet {
changes: Vec<Operation>, pub(crate) changes: Vec<Operation>,
/// The required document length. Will refuse to apply changes unless it matches. /// The required document length. Will refuse to apply changes unless it matches.
len: usize, len: usize,
} }
@ -307,7 +307,7 @@ impl ChangeSet {
/// a single transaction. /// a single transaction.
pub struct Transaction { pub struct Transaction {
/// Changes made to the buffer. /// Changes made to the buffer.
changes: ChangeSet, pub(crate) changes: ChangeSet,
/// When set, explicitly updates the selection. /// When set, explicitly updates the selection.
selection: Option<Selection>, selection: Option<Selection>,
// effects, annotations // effects, annotations
@ -337,6 +337,14 @@ impl Transaction {
.clone() .clone()
.unwrap_or_else(|| state.selection.clone().map(&self.changes)); .unwrap_or_else(|| state.selection.clone().map(&self.changes));
// TODO: no unwrap
state
.syntax
.as_mut()
.unwrap()
.update(&state.doc, &self.changes)
.unwrap();
true true
} }
@ -359,9 +367,15 @@ impl Transaction {
for (from, to, tendril) in changes { for (from, to, tendril) in changes {
// Retain from last "to" to current "from" // Retain from last "to" to current "from"
acc.push(Operation::Retain(from - last)); acc.push(Operation::Retain(from - last));
let span = to - from;
match tendril { match tendril {
Some(text) => acc.push(Operation::Insert(text)), Some(text) => {
None => acc.push(Operation::Delete(to - from)), if span > 0 {
acc.push(Operation::Delete(span));
}
acc.push(Operation::Insert(text));
}
None => acc.push(Operation::Delete(span)),
} }
last = to; last = to;
} }
@ -466,4 +480,15 @@ mod test {
assert_eq!(cs.map_pos(2, Assoc::Before), 2); assert_eq!(cs.map_pos(2, Assoc::Before), 2);
assert_eq!(cs.map_pos(2, Assoc::After), 2); assert_eq!(cs.map_pos(2, Assoc::After), 2);
} }
#[test]
fn transaction_change() {
let mut state = State::new("hello world!\ntest 123".into());
let transaction = Transaction::change(
&state,
vec![(6, 11, Some("void".into())), (12, 17, None)].into_iter(),
);
transaction.apply(&mut state);
assert_eq!(state.doc, Rope::from_str("hello void! 123"));
}
} }

View file

@ -11,11 +11,11 @@ name = "hx"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
helix-core = { path = "../helix-core" }
helix-syntax = { path = "../helix-syntax" } helix-syntax = { path = "../helix-syntax" }
anyhow = "1" anyhow = "1"
argh = "0.1.3" argh = "0.1.3"
helix-core = { path = "../helix-core" }
crossterm = { version = "0.17", features = ["event-stream"] } crossterm = { version = "0.17", features = ["event-stream"] }
smol = "1" smol = "1"

View file

@ -38,8 +38,6 @@ pub struct Editor {
size: (u16, u16), size: (u16, u16),
surface: Surface, surface: Surface,
theme: Theme, theme: Theme,
highlighter: Highlighter,
highlight_config: HighlightConfiguration,
} }
impl Editor { impl Editor {
@ -49,33 +47,8 @@ impl Editor {
let mut terminal = Terminal::new(backend)?; let mut terminal = Terminal::new(backend)?;
let size = terminal::size().unwrap(); let size = terminal::size().unwrap();
let area = Rect::new(0, 0, size.0, size.1); let area = Rect::new(0, 0, size.0, size.1);
let theme = Theme::default(); let theme = Theme::default();
// let mut parser = tree_sitter::Parser::new();
// parser.set_language(language).unwrap();
// let tree = parser.parse(source_code, None).unwrap();
let language = helix_syntax::get_language(&helix_syntax::LANG::Rust);
let highlighter = Highlighter::new();
let mut highlight_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();
highlight_config.configure(theme.scopes());
let mut editor = Editor { let mut editor = Editor {
terminal, terminal,
state: None, state: None,
@ -84,8 +57,6 @@ impl Editor {
surface: Surface::empty(area), surface: Surface::empty(area),
theme, theme,
// TODO; move to state // TODO; move to state
highlighter,
highlight_config,
}; };
if let Some(file) = args.files.pop() { if let Some(file) = args.files.pop() {
@ -96,12 +67,19 @@ impl Editor {
} }
pub fn open(&mut self, path: PathBuf) -> Result<(), Error> { pub fn open(&mut self, path: PathBuf) -> Result<(), Error> {
self.state = Some(State::load(path)?); let mut state = State::load(path)?;
state
.syntax
.as_mut()
.unwrap()
.configure(self.theme.scopes());
self.state = Some(state);
Ok(()) Ok(())
} }
fn render(&mut self) { fn render(&mut self) {
match &self.state { // TODO: ideally not mut but highlights require it because of cursor cache
match &mut self.state {
Some(state) => { Some(state) => {
let area = Rect::new(0, 0, self.size.0, self.size.1); let area = Rect::new(0, 0, self.size.0, self.size.1);
let mut surface = Surface::empty(area); let mut surface = Surface::empty(area);
@ -112,12 +90,13 @@ impl Editor {
// TODO: cache highlight results // TODO: cache highlight results
// TODO: only recalculate when state.doc is actually modified // TODO: only recalculate when state.doc is actually modified
let highlights = self let highlights: Vec<_> = state
.highlighter .syntax
.highlight(&self.highlight_config, source_code.as_bytes(), None, |_| { .as_mut()
None .unwrap()
}) .highlight_iter(source_code.as_bytes(), None, |_| None)
.unwrap(); .unwrap()
.collect(); // TODO: we collect here to avoid double borrow, fix later
let mut spans = Vec::new(); let mut spans = Vec::new();
@ -235,7 +214,7 @@ impl Editor {
let coords = coords_at_pos(&state.doc().slice(..), pos); let coords = coords_at_pos(&state.doc().slice(..), pos);
execute!( execute!(
stdout, stdout,
cursor::MoveTo((coords.1 + 2) as u16, coords.0 as u16) cursor::MoveTo((coords.col + 2) as u16, coords.row as u16)
); );
} }
None => (), None => (),