diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs index 970aff88f..c09b3c332 100644 --- a/helix-core/src/movement.rs +++ b/helix-core/src/movement.rs @@ -1255,15 +1255,24 @@ mod test { #[test] fn test_behaviour_when_moving_to_prev_paragraph_single() { let tests = [ - ("^@", "^@"), - ("^s@tart at\nfirst char\n", "@s^tart at\nfirst char\n"), - ("start at\nlast char^\n@", "@start at\nlast char\n^"), - ("goto\nfirst\n\n^p@aragraph", "@goto\nfirst\n\n^paragraph"), - ("goto\nfirst\n^\n@paragraph", "@goto\nfirst\n\n^paragraph"), - ("goto\nsecond\n\np^a@ragraph", "goto\nsecond\n\n@pa^ragraph"), + ("#[|]#", "#[|]#"), + ("#[s|]#tart at\nfirst char\n", "#[|s]#tart at\nfirst char\n"), + ("start at\nlast char#[\n|]#", "#[|start at\nlast char\n]#"), ( - "here\n\nhave\nmultiple\nparagraph\n\n\n\n\n^@", - "here\n\n@have\nmultiple\nparagraph\n\n\n\n\n^", + "goto\nfirst\n\n#[p|]#aragraph", + "#[|goto\nfirst\n\n]#paragraph", + ), + ( + "goto\nfirst\n#[\n|]#paragraph", + "#[|goto\nfirst\n\n]#paragraph", + ), + ( + "goto\nsecond\n\np#[a|]#ragraph", + "goto\nsecond\n\n#[|pa]#ragraph", + ), + ( + "here\n\nhave\nmultiple\nparagraph\n\n\n\n\n#[|]#", + "here\n\n#[|have\nmultiple\nparagraph\n\n\n\n\n]#", ), ]; @@ -1280,8 +1289,14 @@ mod test { #[test] fn test_behaviour_when_moving_to_prev_paragraph_double() { let tests = [ - ("on^e@\n\ntwo\n\nthree\n\n", "@one^\n\ntwo\n\nthree\n\n"), - ("one\n\ntwo\n\nth^r@ee\n\n", "one\n\n@two\n\nthr^ee\n\n"), + ( + "on#[e|]#\n\ntwo\n\nthree\n\n", + "#[|one]#\n\ntwo\n\nthree\n\n", + ), + ( + "one\n\ntwo\n\nth#[r|]#ee\n\n", + "one\n\n#[|two\n\nthr]#ee\n\n", + ), ]; for (before, expected) in tests { @@ -1297,8 +1312,14 @@ mod test { #[test] fn test_behaviour_when_moving_to_prev_paragraph_extend() { let tests = [ - ("one\n\n@two\n\n^three\n\n", "@one\n\ntwo\n\n^three\n\n"), - ("@one\n\ntwo\n\n^three\n\n", "@one\n\ntwo\n\n^three\n\n"), + ( + "one\n\n#[|two\n\n]#three\n\n", + "#[|one\n\ntwo\n\n]#three\n\n", + ), + ( + "#[|one\n\ntwo\n\n]#three\n\n", + "#[|one\n\ntwo\n\n]#three\n\n", + ), ]; for (before, expected) in tests { @@ -1314,24 +1335,24 @@ mod test { #[test] fn test_behaviour_when_moving_to_next_paragraph_single() { let tests = [ - ("^@", "^@"), - ("^s@tart at\nfirst char\n", "^start at\nfirst char\n@"), - ("start at\nlast char^\n@", "start at\nlast char^\n@"), + ("#[|]#", "#[|]#"), + ("#[s|]#tart at\nfirst char\n", "#[start at\nfirst char\n|]#"), + ("start at\nlast char#[\n|]#", "start at\nlast char#[\n|]#"), ( - "a\nb\n\n^g@oto\nthird\n\nparagraph", - "a\nb\n\n^goto\nthird\n\n@paragraph", + "a\nb\n\n#[g|]#oto\nthird\n\nparagraph", + "a\nb\n\n#[goto\nthird\n\n|]#paragraph", ), ( - "a\nb\n^\n@goto\nthird\n\nparagraph", - "a\nb\n\n^goto\nthird\n\n@paragraph", + "a\nb\n#[\n|]#goto\nthird\n\nparagraph", + "a\nb\n\n#[goto\nthird\n\n|]#paragraph", ), ( - "a\nb^\n@\ngoto\nsecond\n\nparagraph", - "a\nb^\n\n@goto\nsecond\n\nparagraph", + "a\nb#[\n|]#\ngoto\nsecond\n\nparagraph", + "a\nb#[\n\n|]#goto\nsecond\n\nparagraph", ), ( - "here\n\nhave\n^m@ultiple\nparagraph\n\n\n\n\n", - "here\n\nhave\n^multiple\nparagraph\n\n\n\n\n@", + "here\n\nhave\n#[m|]#ultiple\nparagraph\n\n\n\n\n", + "here\n\nhave\n#[multiple\nparagraph\n\n\n\n\n|]#", ), ]; @@ -1348,8 +1369,14 @@ mod test { #[test] fn test_behaviour_when_moving_to_next_paragraph_double() { let tests = [ - ("one\n\ntwo\n\nth^r@ee\n\n", "one\n\ntwo\n\nth^ree\n\n@"), - ("on^e@\n\ntwo\n\nthree\n\n", "on^e\n\ntwo\n\n@three\n\n"), + ( + "one\n\ntwo\n\nth#[r|]#ee\n\n", + "one\n\ntwo\n\nth#[ree\n\n|]#", + ), + ( + "on#[e|]#\n\ntwo\n\nthree\n\n", + "on#[e\n\ntwo\n\n|]#three\n\n", + ), ]; for (before, expected) in tests { @@ -1365,8 +1392,14 @@ mod test { #[test] fn test_behaviour_when_moving_to_next_paragraph_extend() { let tests = [ - ("one\n\n^two\n\n@three\n\n", "one\n\n^two\n\nthree\n\n@"), - ("one\n\n^two\n\nthree\n\n@", "one\n\n^two\n\nthree\n\n@"), + ( + "one\n\n#[two\n\n|]#three\n\n", + "one\n\n#[two\n\nthree\n\n|]#", + ), + ( + "one\n\n#[two\n\nthree\n\n|]#", + "one\n\n#[two\n\nthree\n\n|]#", + ), ]; for (before, expected) in tests { diff --git a/helix-core/src/test.rs b/helix-core/src/test.rs index da4f8facf..964a770a7 100644 --- a/helix-core/src/test.rs +++ b/helix-core/src/test.rs @@ -5,8 +5,10 @@ use std::cmp::Reverse; /// Convert annotated test string to test string and selection. /// -/// `^` for `anchor` and `|` for head (`@` for primary), both must appear -/// or otherwise it will panic. +/// `#[|` for primary selection with head before anchor followed by `]#`. +/// `#(|` for secondary selection with head before anchor followed by `)#`. +/// `#[` for primary selection with head after anchor followed by `|]#`. +/// `#(` for secondary selection with head after anchor followed by `|)#`. /// /// # Examples /// @@ -15,7 +17,7 @@ use std::cmp::Reverse; /// use smallvec::smallvec; /// /// assert_eq!( -/// print("^a@b|c^"), +/// print("#[a|]#b#(|c)#"), /// ("abc".to_owned(), Selection::new(smallvec![Range::new(0, 1), Range::new(3, 2)], 0)) /// ); /// ``` @@ -26,56 +28,101 @@ use std::cmp::Reverse; /// Panics when missing head or anchor. /// Panics when head come after head or anchor come after anchor. pub fn print(s: &str) -> (String, Selection) { - let mut anchor = None; - let mut head = None; let mut primary = None; let mut ranges = SmallVec::new(); - let mut i = 0; - let s = s - .chars() - .filter(|c| { - match c { - '^' if anchor != None => panic!("anchor without head {s:?}"), - '^' if head == None => anchor = Some(i), - '^' => ranges.push(Range::new(i, head.take().unwrap())), - '|' if head != None => panic!("head without anchor {s:?}"), - '|' if anchor == None => head = Some(i), - '|' => ranges.push(Range::new(anchor.take().unwrap(), i)), - '@' if primary != None => panic!("head (primary) already appeared {s:?}"), - '@' if head != None => panic!("head (primary) without anchor {s:?}"), - '@' if anchor == None => { - primary = Some(ranges.len()); - head = Some(i); + let mut iter = s.chars().peekable(); + let mut left = String::with_capacity(s.len()); + 'outer: while let Some(c) = iter.next() { + let start = left.len(); + if c == '#' { + if iter.next_if_eq(&'[').is_some() { + if primary.is_some() { + panic!("primary `#[` already appeared {left:?} {s:?}"); } - '@' => { - primary = Some(ranges.len()); - ranges.push(Range::new(anchor.take().unwrap(), i)); + if iter.next_if_eq(&'|').is_some() { + while let Some(c) = iter.next() { + if c == ']' && iter.next_if_eq(&'#').is_some() { + primary = Some(ranges.len()); + ranges.push(Range::new(left.len(), start)); + continue 'outer; + } else { + left.push(c); + } + } + panic!("missing primary end `]#` {left:?} {s:?}"); + } else { + while let Some(c) = iter.next() { + if c == '|' { + if let Some(cc) = iter.next_if_eq(&']') { + if iter.next_if_eq(&'#').is_some() { + primary = Some(ranges.len()); + ranges.push(Range::new(start, left.len())); + continue 'outer; + } else { + left.push(c); + left.push(cc); + } + } else { + left.push(c); + } + } else { + left.push(c); + } + } + panic!("missing primary end `|]#` {left:?} {s:?}"); } - _ => { - i += 1; - return true; + } else if iter.next_if_eq(&'(').is_some() { + if iter.next_if_eq(&'|').is_some() { + while let Some(c) = iter.next() { + if c == ')' && iter.next_if_eq(&'#').is_some() { + ranges.push(Range::new(left.len(), start)); + continue 'outer; + } else { + left.push(c); + } + } + panic!("missing end `)#` {left:?} {s:?}"); + } else { + while let Some(c) = iter.next() { + if c == '|' { + if let Some(cc) = iter.next_if_eq(&')') { + if iter.next_if_eq(&'#').is_some() { + ranges.push(Range::new(start, left.len())); + continue 'outer; + } else { + left.push(c); + left.push(cc); + } + } else { + left.push(c); + } + } else { + left.push(c); + } + } + panic!("missing end `|)#` {left:?} {s:?}"); } - }; - false - }) - .collect(); - if head.is_some() { - panic!("missing anchor (|) {s:?}"); - } - if anchor.is_some() { - panic!("missing head (^) {s:?}"); + } else { + left.push(c); + } + } else { + left.push(c); + } } let primary = match primary { Some(i) => i, - None => panic!("missing primary (@) {s:?}"), + None => panic!("missing primary `#[|]#` {s:?}"), }; let selection = Selection::new(ranges, primary); - (s, selection) + (left, selection) } /// Convert test string and selection to annotated test string. /// -/// `^` for `anchor` and `|` for head (`@` for primary). +/// `#[|` for primary selection with head before anchor followed by `]#`. +/// `#(|` for secondary selection with head before anchor followed by `)#`. +/// `#[` for primary selection with head after anchor followed by `|]#`. +/// `#(` for secondary selection with head after anchor followed by `|)#`. /// /// # Examples /// @@ -85,28 +132,30 @@ pub fn print(s: &str) -> (String, Selection) { /// /// assert_eq!( /// plain("abc", Selection::new(smallvec![Range::new(0, 1), Range::new(3, 2)], 0)), -/// "^a@b|c^".to_owned() +/// "#[a|]#b#(|c)#".to_owned() /// ); /// ``` pub fn plain(s: &str, selection: Selection) -> String { let primary = selection.primary_index(); - let mut out = String::with_capacity(s.len() + 2 * selection.len()); + let mut out = String::with_capacity(s.len() + 5 * selection.len()); out.push_str(s); let mut insertion: Vec<_> = selection .iter() .enumerate() .flat_map(|(i, range)| { - [ - // sort like this before reversed so anchor < head later - (range.head, if i == primary { '@' } else { '|' }), - (range.anchor, '^'), - ] + // sort like this before reversed so anchor < head later + match (range.anchor < range.head, i == primary) { + (true, true) => [(range.anchor, "#["), (range.head, "|]#")], + (true, false) => [(range.anchor, "#("), (range.head, "|)#")], + (false, true) => [(range.anchor, "]#"), (range.head, "#[|")], + (false, false) => [(range.anchor, ")#"), (range.head, "#(|")], + } }) .collect(); // insert in reverse order insertion.sort_unstable_by_key(|k| Reverse(k.0)); - for (i, c) in insertion { - out.insert(i, c); + for (i, s) in insertion { + out.insert_str(i, s); } out } diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs index fb6b71427..cc1e337c7 100644 --- a/helix-core/src/textobject.rs +++ b/helix-core/src/textobject.rs @@ -357,18 +357,21 @@ mod test { #[test] fn test_textobject_paragraph_inside_single() { let tests = [ - ("^@", "^@"), - ("firs^t@\n\nparagraph\n\n", "^first\n@\nparagraph\n\n"), - ("second\n\npa^r@agraph\n\n", "second\n\n^paragraph\n@\n"), - ("^f@irst char\n\n", "^first char\n@\n"), - ("last char\n^\n@", "last char\n\n^@"), + ("#[|]#", "#[|]#"), + ("firs#[t|]#\n\nparagraph\n\n", "#[first\n|]#\nparagraph\n\n"), ( - "empty to line\n^\n@paragraph boundary\n\n", - "empty to line\n\n^paragraph boundary\n@\n", + "second\n\npa#[r|]#agraph\n\n", + "second\n\n#[paragraph\n|]#\n", ), + ("#[f|]#irst char\n\n", "#[first char\n|]#\n"), + ("last char\n#[\n|]#", "last char\n\n#[|]#"), ( - "line to empty\n\n^p@aragraph boundary\n\n", - "line to empty\n\n^paragraph boundary\n@\n", + "empty to line\n#[\n|]#paragraph boundary\n\n", + "empty to line\n\n#[paragraph boundary\n|]#\n", + ), + ( + "line to empty\n\n#[p|]#aragraph boundary\n\n", + "line to empty\n\n#[paragraph boundary\n|]#\n", ), ]; @@ -386,12 +389,12 @@ mod test { fn test_textobject_paragraph_inside_double() { let tests = [ ( - "last two\n\n^p@aragraph\n\nwithout whitespaces\n\n", - "last two\n\n^paragraph\n\nwithout whitespaces\n@\n", + "last two\n\n#[p|]#aragraph\n\nwithout whitespaces\n\n", + "last two\n\n#[paragraph\n\nwithout whitespaces\n|]#\n", ), ( - "last two\n^\n@paragraph\n\nwithout whitespaces\n\n", - "last two\n\n^paragraph\n\nwithout whitespaces\n@\n", + "last two\n#[\n|]#paragraph\n\nwithout whitespaces\n\n", + "last two\n\n#[paragraph\n\nwithout whitespaces\n|]#\n", ), ]; @@ -408,18 +411,21 @@ mod test { #[test] fn test_textobject_paragraph_around_single() { let tests = [ - ("^@", "^@"), - ("firs^t@\n\nparagraph\n\n", "^first\n\n@paragraph\n\n"), - ("second\n\npa^r@agraph\n\n", "second\n\n^paragraph\n\n@"), - ("^f@irst char\n\n", "^first char\n\n@"), - ("last char\n^\n@", "last char\n\n^@"), + ("#[|]#", "#[|]#"), + ("firs#[t|]#\n\nparagraph\n\n", "#[first\n\n|]#paragraph\n\n"), + ( + "second\n\npa#[r|]#agraph\n\n", + "second\n\n#[paragraph\n\n|]#", + ), + ("#[f|]#irst char\n\n", "#[first char\n\n|]#"), + ("last char\n#[\n|]#", "last char\n\n#[|]#"), ( - "empty to line\n^\n@paragraph boundary\n\n", - "empty to line\n\n^paragraph boundary\n\n@", + "empty to line\n#[\n|]#paragraph boundary\n\n", + "empty to line\n\n#[paragraph boundary\n\n|]#", ), ( - "line to empty\n\n^p@aragraph boundary\n\n", - "line to empty\n\n^paragraph boundary\n\n@", + "line to empty\n\n#[p|]#aragraph boundary\n\n", + "line to empty\n\n#[paragraph boundary\n\n|]#", ), ];