on master help sign in
Split selection on regex, fix InputEdit generation.
commit36e7e2133fe1d472600cfd935b8046b8d50146c2 authorBlaž Hrastnik <blaz@mxxn.io> dateTue 29 Sep 2020, 01:01 (6 years ago) parent3020077
6 files changed+146−23
helix-core/src/lib.rs+4−1 helix-core/src/selection.rs+74−2 helix-core/src/syntax.rs+39−9 helix-core/src/transaction.rs+14−11 helix-view/src/commands.rs+11 helix-view/src/keymap.rs+4
helix-core/src/lib.rs+4−1
@@ -2,7 +2,7 @@
22pub mod graphemes;
33pub mod macros;
44mod position;
5−mod selection;
5+pub mod selection;
66pub mod state;
77pub mod syntax;
88mod transaction;
@@ -11,6 +11,9 @@ pub use ropey::{Rope, RopeSlice};
1111
1212pub use tendril::StrTendril as Tendril;
1313
14+#[doc(inline)]
15+pub use {regex, tree_sitter};
16+
1417pub use position::Position;
1518pub use selection::Range;
1619pub use selection::Selection;
helix-core/src/selection.rs+74−2
@@ -2,8 +2,9 @@
22//! single selection range.
33//!
44//! All positioning is done via `char` offsets into the buffer.
5−use crate::{Assoc, ChangeSet};
5+use crate::{Assoc, ChangeSet, Rope, RopeSlice};
66use smallvec::{smallvec, SmallVec};
7+use std::borrow::Cow;
78
89#[inline]
910fn abs_difference(x: usize, y: usize) -> usize {
@@ -22,7 +23,7 @@ pub struct Range {
2223 pub anchor: usize,
2324 /// The head of the range, moved when extending.
2425 pub head: usize,
25−}
26+} // TODO: might be cheaper to store normalized as from/to and an inverted flag
2627
2728impl Range {
2829 pub fn new(anchor: usize, head: usize) -> Self {
@@ -106,6 +107,11 @@ impl Range {
106107 }
107108
108109 // 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+ }
109115}
110116
111117/// A selection consists of one or more selection ranges.
@@ -239,10 +245,50 @@ impl Selection {
239245 self.primary_index,
240246 )
241247 }
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+ }
242252}
243253
244254// TODO: checkSelection -> check if valid for doc length
245255
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+
246292#[cfg(test)]
247293mod test {
248294 use super::*;
@@ -312,4 +358,30 @@ mod test {
312358 assert_eq!(range.contains(6), false);
313359 }
314360
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+ }
315387}
helix-core/src/syntax.rs+39−9
@@ -188,9 +188,19 @@ impl Syntax {
188188 syntax
189189 }
190190
191− pub fn update(&mut self, source: &Rope, changeset: &ChangeSet) -> Result<(), Error> {
192− self.root_layer
193− .update(&mut self.parser, &self.config, source, changeset)
191+ pub fn update(
192+ &mut self,
193+ old_source: &Rope,
194+ source: &Rope,
195+ changeset: &ChangeSet,
196+ ) -> Result<(), Error> {
197+ self.root_layer.update(
198+ &mut self.parser,
199+ &self.config,
200+ old_source,
201+ source,
202+ changeset,
203+ )
194204
195205 // TODO: deal with injections and update them too
196206 }
@@ -385,7 +395,7 @@ impl LanguageLayer {
385395 }
386396
387397 pub(crate) fn generate_edits(
388− text: &RopeSlice,
398+ old_text: &RopeSlice,
389399 changeset: &ChangeSet,
390400 ) -> Vec<tree_sitter::InputEdit> {
391401 use Operation::*;
@@ -399,7 +409,7 @@ impl LanguageLayer {
399409 // TODO; this is a lot easier with Change instead of Operation.
400410
401411 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
403413 let line = text.char_to_line(pos);
404414 let line_start_byte = text.line_to_byte(line);
405415 let col = byte - line_start_byte;
@@ -437,8 +447,8 @@ impl LanguageLayer {
437447 new_pos += len;
438448 }
439449 Delete(_) => {
440− let (start_byte, start_position) = point_at_pos(&text, old_pos);
441− let (old_end_byte, old_end_position) = point_at_pos(&text, old_end);
450+ let (start_byte, start_position) = point_at_pos(&old_text, old_pos);
451+ let (old_end_byte, old_end_position) = point_at_pos(&old_text, old_end);
442452
443453 // TODO: Position also needs to be byte based...
444454 // let byte = char_to_byte(old_pos)
@@ -475,7 +485,7 @@ impl LanguageLayer {
475485 };
476486 }
477487 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);
479489
480490 let ins = s.chars().count();
481491
@@ -501,6 +511,7 @@ impl LanguageLayer {
501511 &mut self,
502512 parser: &mut Parser,
503513 config: &HighlightConfiguration,
514+ old_source: &Rope,
504515 source: &Rope,
505516 changeset: &ChangeSet,
506517 ) -> Result<(), Error> {
@@ -508,7 +519,7 @@ impl LanguageLayer {
508519 return Ok(());
509520 }
510521
511− let edits = Self::generate_edits(&source.slice(..), changeset);
522+ let edits = Self::generate_edits(&old_source.slice(..), changeset);
512523
513524 // Notify the tree about all the changes
514525 for edit in edits {
@@ -1530,4 +1541,23 @@ fn test_input_edits() {
15301541 }
15311542 ]
15321543 );
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+ );
15331563}
helix-core/src/transaction.rs+14−11
@@ -326,9 +326,20 @@ impl Transaction {
326326
327327 /// Returns true if applied successfully.
328328 pub fn apply(&self, state: &mut State) -> bool {
329− // apply changes to the document
330− if !self.changes.apply(&mut state.doc) {
331− return false;
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+
334+ // apply changes to the document
335+ if !self.changes.apply(&mut state.doc) {
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+ }
332343 }
333344
334345 // update the selection: either take the selection specified in the transaction, or map the
@@ -338,14 +349,6 @@ impl Transaction {
338349 .clone()
339350 .unwrap_or_else(|| state.selection.clone().map(&self.changes));
340351
341− // TODO: no unwrap
342− state
343− .syntax
344− .as_mut()
345− .unwrap()
346− .update(&state.doc, &self.changes)
347− .unwrap();
348−
349352 true
350353 }
351354
helix-view/src/commands.rs+11
@@ -1,8 +1,11 @@
11use helix_core::{
22 graphemes,
3+ regex::Regex,
4+ selection,
35 state::{Direction, Granularity, Mode, State},
46 Range, Selection, Tendril, Transaction,
57};
8+use once_cell::sync::Lazy;
69
710use crate::view::View;
811
@@ -144,6 +147,14 @@ pub fn extend_line_down(view: &mut View, count: usize) {
144147 .extend_selection(Direction::Forward, Granularity::Line, count);
145148}
146149
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+
147158pub fn delete_selection(view: &mut View, _count: usize) {
148159 let transaction =
149160 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 {
174174 code: KeyCode::Char('c'),
175175 modifiers: Modifiers::NONE
176176 }] => 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,
177181 vec![Key {
178182 code: KeyCode::Esc,
179183 modifiers: Modifiers::NONE