diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs index bbb37bf49..60af47e5b 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.as_ref(), &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.as_ref(), &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.as_ref(), &selection); assert_eq!(actual, expected, "\nbefore: `{:?}`", before); } } @@ -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.as_ref(), &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.as_ref(), &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.as_ref(), &selection); assert_eq!(actual, expected, "\nbefore: `{:?}`", before); } } diff --git a/helix-core/src/test.rs b/helix-core/src/test.rs index 1d967f23d..7183302c6 100644 --- a/helix-core/src/test.rs +++ b/helix-core/src/test.rs @@ -1,5 +1,6 @@ //! Test helpers. use crate::{Range, Selection}; +use ropey::Rope; use smallvec::SmallVec; use std::cmp::Reverse; use unicode_segmentation::UnicodeSegmentation; @@ -148,10 +149,12 @@ pub fn print(s: &str) -> (String, Selection) { /// "#[a|]#b#(|c)#".to_owned() /// ); /// ``` -pub fn plain(s: &str, selection: &Selection) -> String { +pub fn plain>(s: R, selection: &Selection) -> String { + let s = s.into(); let primary = selection.primary_index(); - let mut out = String::with_capacity(s.len() + 5 * selection.len()); - out.push_str(s); + let mut out = String::with_capacity(s.len_bytes() + 5 * selection.len()); + out.push_str(&s.to_string()); + let mut insertion: Vec<_> = selection .iter() .enumerate() @@ -164,7 +167,9 @@ pub fn plain(s: &str, selection: &Selection) -> String { (false, false) => [(range.anchor, ")#"), (range.head, "#(|")], } }) + .map(|(char_idx, marker)| (s.char_to_byte(char_idx), marker)) .collect(); + // insert in reverse order insertion.sort_unstable_by_key(|k| Reverse(k.0)); for (i, s) in insertion { @@ -173,7 +178,6 @@ pub fn plain(s: &str, selection: &Selection) -> String { out } -#[allow(clippy::module_inception)] #[cfg(test)] #[allow(clippy::module_inception)] mod test { @@ -289,4 +293,94 @@ mod test { print("hello #[|πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦]# goodbye") ); } + + #[test] + fn plain_single() { + assert_eq!("#[|h]#ello", plain("hello", &Selection::single(1, 0))); + assert_eq!("#[h|]#ello", plain("hello", &Selection::single(0, 1))); + assert_eq!("#[|hell]#o", plain("hello", &Selection::single(4, 0))); + assert_eq!("#[hell|]#o", plain("hello", &Selection::single(0, 4))); + assert_eq!("#[|hello]#", plain("hello", &Selection::single(5, 0))); + assert_eq!("#[hello|]#", plain("hello", &Selection::single(0, 5))); + } + + #[test] + fn plain_multi() { + assert_eq!( + plain( + "hello", + &Selection::new( + SmallVec::from_slice(&[Range::new(1, 0), Range::new(5, 4)]), + 0 + ) + ), + String::from("#[|h]#ell#(|o)#") + ); + assert_eq!( + plain( + "hello", + &Selection::new( + SmallVec::from_slice(&[Range::new(0, 1), Range::new(4, 5)]), + 0 + ) + ), + String::from("#[h|]#ell#(o|)#") + ); + assert_eq!( + plain( + "hello", + &Selection::new( + SmallVec::from_slice(&[Range::new(2, 0), Range::new(5, 3)]), + 0 + ) + ), + String::from("#[|he]#l#(|lo)#") + ); + assert_eq!( + plain( + "hello\r\nhello\r\nhello\r\n", + &Selection::new( + SmallVec::from_slice(&[ + Range::new(7, 5), + Range::new(21, 19), + Range::new(14, 12) + ]), + 0 + ) + ), + String::from("hello#[|\r\n]#hello#(|\r\n)#hello#(|\r\n)#") + ); + } + + #[test] + fn plain_multi_byte_code_point() { + assert_eq!( + plain("β€žβ€œ", &Selection::single(1, 0)), + String::from("#[|β€ž]#β€œ") + ); + assert_eq!( + plain("β€žβ€œ", &Selection::single(2, 1)), + String::from("β€ž#[|β€œ]#") + ); + assert_eq!( + plain("β€žβ€œ", &Selection::single(0, 1)), + String::from("#[β€ž|]#β€œ") + ); + assert_eq!( + plain("β€žβ€œ", &Selection::single(1, 2)), + String::from("β€ž#[β€œ|]#") + ); + assert_eq!( + plain("they said β€žhelloβ€œ", &Selection::single(11, 10)), + String::from("they said #[|β€ž]#helloβ€œ") + ); + } + + #[test] + fn plain_multi_code_point_grapheme() { + assert_eq!( + plain("hello πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦ goodbye", &Selection::single(13, 6)), + String::from("hello #[|πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦]# goodbye") + ); + } } diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs index 6e3f18cf0..bf00a4580 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.as_ref(), &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.as_ref(), &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.as_ref(), &selection); assert_eq!(actual, expected, "\nbefore: `{:?}`", before); } } diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs index de81f758d..77332fa58 100644 --- a/helix-term/tests/test/helpers.rs +++ b/helix-term/tests/test/helpers.rs @@ -65,7 +65,7 @@ pub async fn test_key_sequences( for (i, (in_keys, test_fn)) in inputs.into_iter().enumerate() { let (view, doc) = current_ref!(app.editor); - let state = test::plain(&doc.text().to_string(), doc.selection(view.id)); + let state = test::plain(doc.text().slice(..), doc.selection(view.id)); log::debug!("executing test with document state:\n\n-----\n\n{}", state); @@ -81,7 +81,7 @@ pub async fn test_key_sequences( if !app_exited { let (view, doc) = current_ref!(app.editor); - let state = test::plain(&doc.text().to_string(), doc.selection(view.id)); + let state = test::plain(doc.text().slice(..), doc.selection(view.id)); log::debug!( "finished running test with document state:\n\n-----\n\n{}",