on master help sign in
Split selection on regex, fix InputEdit generation.
36e7e21Blaž Hrastnik
helix-core/src/lib.rs+4−1
@@ -2,7 +2,7 @@
2pub mod graphemes;
2pub mod graphemes;
3pub mod macros;
3pub mod macros;
4mod position;
4mod position;
5−mod selection;
5+pub mod selection;
6pub mod state;
6pub mod state;
7pub mod syntax;
7pub mod syntax;
8mod transaction;
8mod transaction;
@@ -11,6 +11,9 @@ pub use ropey::{Rope, RopeSlice};
11
11
12pub use tendril::StrTendril as Tendril;
12pub use tendril::StrTendril as Tendril;
13
13
14+#[doc(inline)]
15+pub use {regex, tree_sitter};
16+
14pub use position::Position;
17pub use position::Position;
15pub use selection::Range;
18pub use selection::Range;
16pub use selection::Selection;
19pub use selection::Selection;
helix-core/src/selection.rs+74−2
@@ -2,8 +2,9 @@
2//! single selection range.
2//! single selection range.
3//!
3//!
4//! All positioning is done via `char` offsets into the buffer.
4//! All positioning is done via `char` offsets into the buffer.
5−use crate::{Assoc, ChangeSet};
5+use crate::{Assoc, ChangeSet, Rope, RopeSlice};
6use smallvec::{smallvec, SmallVec};
6use smallvec::{smallvec, SmallVec};
7+use std::borrow::Cow;
7
8
8#[inline]
9#[inline]
9fn abs_difference(x: usize, y: usize) -> usize {
10fn abs_difference(x: usize, y: usize) -> usize {
@@ -22,7 +23,7 @@ pub struct Range {
22 pub anchor: usize,
23 pub anchor: usize,
23 /// The head of the range, moved when extending.
24 /// The head of the range, moved when extending.
24 pub head: usize,
25 pub head: usize,
25−}
26+} // TODO: might be cheaper to store normalized as from/to and an inverted flag
26
27
27impl Range {
28impl Range {
28 pub fn new(anchor: usize, head: usize) -> Self {
29 pub fn new(anchor: usize, head: usize) -> Self {
@@ -106,6 +107,11 @@ impl Range {
106 }
107 }
107
108
108 // groupAt
109 // groupAt
110+
111+ #[inline]
112+ pub fn fragment<'a>(&'a self, text: &'a RopeSlice) -> Cow<'a, str> {
113+ Cow::from(text.slice(self.from()..self.to()))
114+ }
109}
115}
110
116
111/// A selection consists of one or more selection ranges.
117/// A selection consists of one or more selection ranges.
@@ -239,10 +245,50 @@ impl Selection {
239 self.primary_index,
245 self.primary_index,
240 )
246 )
241 }
247 }
248+
249+ pub fn fragments<'a>(&'a self, text: &'a RopeSlice) -> impl Iterator<Item = Cow<str>> + 'a {
250+ self.ranges.iter().map(move |range| range.fragment(text))
251+ }
242}
252}
243
253
244// TODO: checkSelection -> check if valid for doc length
254// TODO: checkSelection -> check if valid for doc length
245
255
256+// TODO: support to split on capture #N instead of whole match
257+pub fn split_on_matches(
258+ text: &RopeSlice,
259+ selections: &Selection,
260+ regex: &crate::regex::Regex,
261+) -> Selection {
262+ let mut result = SmallVec::with_capacity(selections.ranges().len());
263+
264+ for sel in selections.ranges() {
265+ // TODO: can't avoid occasional allocations since Regex can't operate on chunks yet
266+ let fragment = sel.fragment(&text);
267+
268+ let mut sel_start = sel.from();
269+ let sel_end = sel.to();
270+
271+ let mut start_byte = text.char_to_byte(sel_start);
272+
273+ let mut start = sel_start;
274+
275+ for mat in regex.find_iter(&fragment) {
276+ // TODO: retain range direction
277+
278+ let end = text.byte_to_char(start_byte + mat.start());
279+ result.push(Range::new(start, end - 1));
280+ start = text.byte_to_char(start_byte + mat.end());
281+ }
282+
283+ if start <= sel_end {
284+ result.push(Range::new(start, sel_end));
285+ }
286+ }
287+
288+ // TODO: figure out a new primary index
289+ Selection::new(result, 0)
290+}
291+
246#[cfg(test)]
292#[cfg(test)]
247mod test {
293mod test {
248 use super::*;
294 use super::*;
@@ -312,4 +358,30 @@ mod test {
312 assert_eq!(range.contains(6), false);
358 assert_eq!(range.contains(6), false);
313 }
359 }
314
360
361+ #[test]
362+ fn test_split_on_matches() {
363+ use crate::regex::Regex;
364+
365+ let text = Rope::from("abcd efg wrs xyz 123 456");
366+
367+ let selections = Selection::new(smallvec![Range::new(0, 8), Range::new(10, 19),], 0);
368+
369+ let result = split_on_matches(&text.slice(..), &selections, &Regex::new(r"\s+").unwrap());
370+
371+ assert_eq!(
372+ result.ranges(),
373+ &[
374+ Range::new(0, 4),
375+ Range::new(5, 8),
376+ Range::new(10, 12),
377+ Range::new(15, 18),
378+ Range::new(19, 19),
379+ ]
380+ );
381+
382+ assert_eq!(
383+ result.fragments(&text.slice(..)).collect::<Vec<_>>(),
384+ &["abcd", "efg", "rs", "xyz", ""]
385+ );
386+ }
315}
387}
helix-core/src/syntax.rs+39−9
@@ -188,9 +188,19 @@ impl Syntax {
188 syntax
188 syntax
189 }
189 }
190
190
191+ pub fn update(
192+ &mut self,
193+ old_source: &Rope,
194+ source: &Rope,
191− pub fn update(&mut self, source: &Rope, changeset: &ChangeSet) -> Result<(), Error> {
195+ changeset: &ChangeSet,
196+ ) -> Result<(), Error> {
192− self.root_layer
197+ self.root_layer.update(
193− .update(&mut self.parser, &self.config, source, changeset)
198+ &mut self.parser,
199+ &self.config,
200+ old_source,
201+ source,
202+ changeset,
203+ )
194
204
195 // TODO: deal with injections and update them too
205 // TODO: deal with injections and update them too
196 }
206 }
@@ -385,7 +395,7 @@ impl LanguageLayer {
385 }
395 }
386
396
387 pub(crate) fn generate_edits(
397 pub(crate) fn generate_edits(
388− text: &RopeSlice,
398+ old_text: &RopeSlice,
389 changeset: &ChangeSet,
399 changeset: &ChangeSet,
390 ) -> Vec<tree_sitter::InputEdit> {
400 ) -> Vec<tree_sitter::InputEdit> {
391 use Operation::*;
401 use Operation::*;
@@ -399,7 +409,7 @@ impl LanguageLayer {
399 // TODO; this is a lot easier with Change instead of Operation.
409 // TODO; this is a lot easier with Change instead of Operation.
400
410
401 fn point_at_pos(text: &RopeSlice, pos: usize) -> (usize, Point) {
411 fn point_at_pos(text: &RopeSlice, pos: usize) -> (usize, Point) {
402− let byte = text.char_to_byte(pos);
412+ let byte = text.char_to_byte(pos); // <- attempted to index past end
403 let line = text.char_to_line(pos);
413 let line = text.char_to_line(pos);
404 let line_start_byte = text.line_to_byte(line);
414 let line_start_byte = text.line_to_byte(line);
405 let col = byte - line_start_byte;
415 let col = byte - line_start_byte;
@@ -437,8 +447,8 @@ impl LanguageLayer {
437 new_pos += len;
447 new_pos += len;
438 }
448 }
439 Delete(_) => {
449 Delete(_) => {
440− let (start_byte, start_position) = point_at_pos(&text, old_pos);
450+ let (start_byte, start_position) = point_at_pos(&old_text, old_pos);
441− let (old_end_byte, old_end_position) = point_at_pos(&text, old_end);
451+ let (old_end_byte, old_end_position) = point_at_pos(&old_text, old_end);
442
452
443 // TODO: Position also needs to be byte based...
453 // TODO: Position also needs to be byte based...
444 // let byte = char_to_byte(old_pos)
454 // let byte = char_to_byte(old_pos)
@@ -475,7 +485,7 @@ impl LanguageLayer {
475 };
485 };
476 }
486 }
477 Insert(s) => {
487 Insert(s) => {
478− let (start_byte, start_position) = point_at_pos(&text, old_pos);
488+ let (start_byte, start_position) = point_at_pos(&old_text, old_pos);
479
489
480 let ins = s.chars().count();
490 let ins = s.chars().count();
481
491
@@ -501,6 +511,7 @@ impl LanguageLayer {
501 &mut self,
511 &mut self,
502 parser: &mut Parser,
512 parser: &mut Parser,
503 config: &HighlightConfiguration,
513 config: &HighlightConfiguration,
514+ old_source: &Rope,
504 source: &Rope,
515 source: &Rope,
505 changeset: &ChangeSet,
516 changeset: &ChangeSet,
506 ) -> Result<(), Error> {
517 ) -> Result<(), Error> {
@@ -508,7 +519,7 @@ impl LanguageLayer {
508 return Ok(());
519 return Ok(());
509 }
520 }
510
521
511− let edits = Self::generate_edits(&source.slice(..), changeset);
522+ let edits = Self::generate_edits(&old_source.slice(..), changeset);
512
523
513 // Notify the tree about all the changes
524 // Notify the tree about all the changes
514 for edit in edits {
525 for edit in edits {
@@ -1530,4 +1541,23 @@ fn test_input_edits() {
1530 }
1541 }
1531 ]
1542 ]
1532 );
1543 );
1544+
1545+ // Testing with the official example from tree-sitter
1546+ let mut state = State::new("fn test() {}".into());
1547+ let transaction = Transaction::change(&state, vec![(8, 8, Some("a: u32".into()))].into_iter());
1548+ let edits = LanguageLayer::generate_edits(&state.doc.slice(..), &transaction.changes);
1549+ transaction.apply(&mut state);
1550+
1551+ assert_eq!(state.doc(), "fn test(a: u32) {}");
1552+ assert_eq!(
1553+ edits,
1554+ &[InputEdit {
1555+ start_byte: 8,
1556+ old_end_byte: 8,
1557+ new_end_byte: 14,
1558+ start_position: Point { row: 0, column: 8 },
1559+ old_end_position: Point { row: 0, column: 8 },
1560+ new_end_position: Point { row: 0, column: 14 }
1561+ }]
1562+ );
1533}
1563}
helix-core/src/transaction.rs+14−11
@@ -326,9 +326,20 @@ impl Transaction {
326
326
327 /// Returns true if applied successfully.
327 /// Returns true if applied successfully.
328 pub fn apply(&self, state: &mut State) -> bool {
328 pub fn apply(&self, state: &mut State) -> bool {
329+ if !self.changes.is_empty() {
330+ // TODO: also avoid mapping the selection if not necessary
331+
332+ let old_doc = state.doc().clone();
333+
329− // apply changes to the document
334+ // apply changes to the document
330− if !self.changes.apply(&mut state.doc) {
335+ if !self.changes.apply(&mut state.doc) {
331− return false;
336+ return false;
337+ }
338+
339+ if let Some(syntax) = &mut state.syntax {
340+ // TODO: no unwrap
341+ syntax.update(&old_doc, &state.doc, &self.changes).unwrap();
342+ }
332 }
343 }
333
344
334 // update the selection: either take the selection specified in the transaction, or map the
345 // update the selection: either take the selection specified in the transaction, or map the
@@ -338,14 +349,6 @@ impl Transaction {
338 .clone()
349 .clone()
339 .unwrap_or_else(|| state.selection.clone().map(&self.changes));
350 .unwrap_or_else(|| state.selection.clone().map(&self.changes));
340
351
341− // TODO: no unwrap
342− state
343− .syntax
344− .as_mut()
345− .unwrap()
346− .update(&state.doc, &self.changes)
347− .unwrap();
348−
349 true
352 true
350 }
353 }
351
354
helix-view/src/commands.rs+11
@@ -1,8 +1,11 @@
1use helix_core::{
1use helix_core::{
2 graphemes,
2 graphemes,
3+ regex::Regex,
4+ selection,
3 state::{Direction, Granularity, Mode, State},
5 state::{Direction, Granularity, Mode, State},
4 Range, Selection, Tendril, Transaction,
6 Range, Selection, Tendril, Transaction,
5};
7};
8+use once_cell::sync::Lazy;
6
9
7use crate::view::View;
10use crate::view::View;
8
11
@@ -144,6 +147,14 @@ pub fn extend_line_down(view: &mut View, count: usize) {
144 .extend_selection(Direction::Forward, Granularity::Line, count);
147 .extend_selection(Direction::Forward, Granularity::Line, count);
145}
148}
146
149
150+pub fn split_selection_on_newline(view: &mut View, _count: usize) {
151+ let text = &view.state.doc.slice(..);
152+ // only compile the regex once
153+ static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\n").unwrap());
154+ // TODO: use a transaction
155+ view.state.selection = selection::split_on_matches(text, view.state.selection(), &REGEX)
156+}
157+
147pub fn delete_selection(view: &mut View, _count: usize) {
158pub fn delete_selection(view: &mut View, _count: usize) {
148 let transaction =
159 let transaction =
149 Transaction::change_by_selection(&view.state, |range| (range.from(), range.to(), None));
160 Transaction::change_by_selection(&view.state, |range| (range.from(), range.to(), None));
helix-view/src/keymap.rs+4
@@ -174,6 +174,10 @@ pub fn default() -> Keymaps {
174 code: KeyCode::Char('c'),
174 code: KeyCode::Char('c'),
175 modifiers: Modifiers::NONE
175 modifiers: Modifiers::NONE
176 }] => commands::change_selection as Command,
176 }] => commands::change_selection as Command,
177+ vec![Key {
178+ code: KeyCode::Char('s'),
179+ modifiers: Modifiers::NONE
180+ }] => commands::split_selection_on_newline as Command,
177 vec![Key {
181 vec![Key {
178 code: KeyCode::Esc,
182 code: KeyCode::Esc,
179 modifiers: Modifiers::NONE
183 modifiers: Modifiers::NONE