aboutsummaryrefslogtreecommitdiff
path: root/helix-core
diff options
context:
space:
mode:
authorSkyler Hawthorne2022-09-17 13:34:56 +0000
committerMichael Davis2023-03-20 23:34:40 +0000
commit58ea1930544df165a4a1342932e4dd8cb93e14ea (patch)
tree3cdc8910f20963027cf444f1d1a4b3392c399b2f /helix-core
parenta264faa98d348a13318f9ccbb0d979882a112654 (diff)
Allow explicit newlines in test DSL
The current test DSL currently has no way to express being at the end of a line, save for putting an explicit LF or CRLF inside the `#[|]#`. The problem with this approach is that it can add unintended extra new lines if used in conjunction with raw strings, which insert newlines for you. This is a simple attempt to mitigate this problem. If there is an explicit newline character at the end of the selection, and then it is immediately followed by the same newline character at the right end of the selection, this following newline is removed. This way, one can express a cursor at the end of a line explicitly.
Diffstat (limited to 'helix-core')
-rw-r--r--helix-core/Cargo.toml1
-rw-r--r--helix-core/src/movement.rs14
-rw-r--r--helix-core/src/test.rs59
-rw-r--r--helix-core/src/textobject.rs6
4 files changed, 54 insertions, 26 deletions
diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml
index 62ec87b4..8618f586 100644
--- a/helix-core/Cargo.toml
+++ b/helix-core/Cargo.toml
@@ -49,3 +49,4 @@ textwrap = "0.16.0"
[dev-dependencies]
quickcheck = { version = "1", default-features = false }
+indoc = "1.0.6"
diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs
index 8e6b6306..bbb37bf4 100644
--- a/helix-core/src/movement.rs
+++ b/helix-core/src/movement.rs
@@ -1474,7 +1474,7 @@ mod test {
let text = Rope::from(s.as_str());
let selection =
selection.transform(|r| move_prev_paragraph(text.slice(..), r, 1, Movement::Move));
- let actual = crate::test::plain(&s, selection);
+ let actual = crate::test::plain(&s, &selection);
assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
}
}
@@ -1497,7 +1497,7 @@ mod test {
let text = Rope::from(s.as_str());
let selection =
selection.transform(|r| move_prev_paragraph(text.slice(..), r, 2, Movement::Move));
- let actual = crate::test::plain(&s, selection);
+ let actual = crate::test::plain(&s, &selection);
assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
}
}
@@ -1520,7 +1520,7 @@ mod test {
let text = Rope::from(s.as_str());
let selection = selection
.transform(|r| move_prev_paragraph(text.slice(..), r, 1, Movement::Extend));
- let actual = crate::test::plain(&s, selection);
+ let actual = crate::test::plain(&s, &selection);
assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
}
}
@@ -1540,7 +1540,7 @@ mod test {
"a\nb\n\n#[goto\nthird\n\n|]#paragraph",
),
(
- "a\nb#[\n|]#\ngoto\nsecond\n\nparagraph",
+ "a\nb#[\n|]#\n\ngoto\nsecond\n\nparagraph",
"a\nb#[\n\n|]#goto\nsecond\n\nparagraph",
),
(
@@ -1562,7 +1562,7 @@ mod test {
let text = Rope::from(s.as_str());
let selection =
selection.transform(|r| move_next_paragraph(text.slice(..), r, 1, Movement::Move));
- let actual = crate::test::plain(&s, selection);
+ let actual = crate::test::plain(&s, &selection);
assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
}
}
@@ -1585,7 +1585,7 @@ mod test {
let text = Rope::from(s.as_str());
let selection =
selection.transform(|r| move_next_paragraph(text.slice(..), r, 2, Movement::Move));
- let actual = crate::test::plain(&s, selection);
+ let actual = crate::test::plain(&s, &selection);
assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
}
}
@@ -1608,7 +1608,7 @@ mod test {
let text = Rope::from(s.as_str());
let selection = selection
.transform(|r| move_next_paragraph(text.slice(..), r, 1, Movement::Extend));
- let actual = crate::test::plain(&s, selection);
+ let actual = crate::test::plain(&s, &selection);
assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
}
}
diff --git a/helix-core/src/test.rs b/helix-core/src/test.rs
index 17523ed7..1d967f23 100644
--- a/helix-core/src/test.rs
+++ b/helix-core/src/test.rs
@@ -2,6 +2,7 @@
use crate::{Range, Selection};
use smallvec::SmallVec;
use std::cmp::Reverse;
+use unicode_segmentation::UnicodeSegmentation;
/// Convert annotated test string to test string and selection.
///
@@ -10,6 +11,10 @@ use std::cmp::Reverse;
/// `#[` for primary selection with head after anchor followed by `|]#`.
/// `#(` for secondary selection with head after anchor followed by `|)#`.
///
+/// If the selection contains any LF or CRLF sequences, which are immediately
+/// followed by the same grapheme, then the subsequent one is removed. This is
+/// to allow representing having the cursor over the end of the line.
+///
/// # Examples
///
/// ```
@@ -30,23 +35,23 @@ use std::cmp::Reverse;
pub fn print(s: &str) -> (String, Selection) {
let mut primary_idx = None;
let mut ranges = SmallVec::new();
- let mut iter = s.chars().peekable();
+ let mut iter = UnicodeSegmentation::graphemes(s, true).peekable();
let mut left = String::with_capacity(s.len());
'outer: while let Some(c) = iter.next() {
let start = left.chars().count();
- if c != '#' {
- left.push(c);
+ if c != "#" {
+ left.push_str(c);
continue;
}
let (is_primary, close_pair) = match iter.next() {
- Some('[') => (true, ']'),
- Some('(') => (false, ')'),
+ Some("[") => (true, "]"),
+ Some("(") => (false, ")"),
Some(ch) => {
left.push('#');
- left.push(ch);
+ left.push_str(ch);
continue;
}
None => break,
@@ -56,24 +61,45 @@ pub fn print(s: &str) -> (String, Selection) {
panic!("primary `#[` already appeared {:?} {:?}", left, s);
}
- let head_at_beg = iter.next_if_eq(&'|').is_some();
+ let head_at_beg = iter.next_if_eq(&"|").is_some();
+ let last_grapheme = |s: &str| {
+ UnicodeSegmentation::graphemes(s, true)
+ .last()
+ .map(String::from)
+ };
while let Some(c) = iter.next() {
- if !(c == close_pair && iter.peek() == Some(&'#')) {
- left.push(c);
+ let next = iter.peek();
+ let mut prev = last_grapheme(left.as_str());
+
+ if !(c == close_pair && next == Some(&"#")) {
+ left.push_str(c);
continue;
}
if !head_at_beg {
- let prev = left.pop().unwrap();
- if prev != '|' {
- left.push(prev);
- left.push(c);
- continue;
+ match &prev {
+ Some(p) if p != "|" => {
+ left.push_str(c);
+ continue;
+ }
+ Some(p) if p == "|" => {
+ left.pop().unwrap(); // pop the |
+ prev = last_grapheme(left.as_str());
+ }
+ _ => (),
}
}
iter.next(); // skip "#"
+ let next = iter.peek();
+
+ // skip explicit line end inside selection
+ if (prev == Some(String::from("\r\n")) || prev == Some(String::from("\n")))
+ && next.map(|n| String::from(*n)) == prev
+ {
+ iter.next();
+ }
if is_primary {
primary_idx = Some(ranges.len());
@@ -118,11 +144,11 @@ pub fn print(s: &str) -> (String, Selection) {
/// use smallvec::smallvec;
///
/// assert_eq!(
-/// plain("abc", Selection::new(smallvec![Range::new(0, 1), Range::new(3, 2)], 0)),
+/// plain("abc", &Selection::new(smallvec![Range::new(0, 1), Range::new(3, 2)], 0)),
/// "#[a|]#b#(|c)#".to_owned()
/// );
/// ```
-pub fn plain(s: &str, selection: Selection) -> String {
+pub fn plain(s: &str, selection: &Selection) -> String {
let primary = selection.primary_index();
let mut out = String::with_capacity(s.len() + 5 * selection.len());
out.push_str(s);
@@ -147,6 +173,7 @@ pub fn plain(s: &str, selection: Selection) -> String {
out
}
+#[allow(clippy::module_inception)]
#[cfg(test)]
#[allow(clippy::module_inception)]
mod test {
diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs
index 972a80e7..6e3f18cf 100644
--- a/helix-core/src/textobject.rs
+++ b/helix-core/src/textobject.rs
@@ -437,7 +437,7 @@ mod test {
let text = Rope::from(s.as_str());
let selection = selection
.transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Inside, 1));
- let actual = crate::test::plain(&s, selection);
+ let actual = crate::test::plain(&s, &selection);
assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
}
}
@@ -460,7 +460,7 @@ mod test {
let text = Rope::from(s.as_str());
let selection = selection
.transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Inside, 2));
- let actual = crate::test::plain(&s, selection);
+ let actual = crate::test::plain(&s, &selection);
assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
}
}
@@ -491,7 +491,7 @@ mod test {
let text = Rope::from(s.as_str());
let selection = selection
.transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Around, 1));
- let actual = crate::test::plain(&s, selection);
+ let actual = crate::test::plain(&s, &selection);
assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
}
}