Use new marked ranges format whenever we don't need overlapping ranges

Max Brunsfeld created

Change summary

crates/editor/src/display_map.rs    |  57 ++++----
crates/editor/src/editor.rs         |  58 ++++----
crates/editor/src/movement.rs       | 148 ++++++++++----------
crates/editor/src/test.rs           |  14 +-
crates/util/src/test/marked_text.rs | 209 ++++++++++++++----------------
crates/vim/src/normal.rs            |  14 +-
6 files changed, 241 insertions(+), 259 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -577,7 +577,7 @@ pub mod tests {
     use smol::stream::StreamExt;
     use std::{env, sync::Arc};
     use theme::SyntaxTheme;
-    use util::test::{parse_marked_text, sample_text};
+    use util::test::{marked_text_ranges, sample_text};
     use Bias::*;
 
     #[gpui::test(iterations = 100)]
@@ -1170,8 +1170,7 @@ pub mod tests {
         );
         language.set_theme(&theme);
 
-        let (text, highlighted_ranges) =
-            parse_marked_text(r#"constˇ «a»: B = "c «d»""#, false).unwrap();
+        let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»: B = "c «d»""#, false);
 
         let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
         buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
@@ -1247,28 +1246,28 @@ pub mod tests {
         }
 
         use Bias::{Left, Right};
-        assert("||α", false, Left, cx);
-        assert("||α", true, Left, cx);
-        assert("||α", false, Right, cx);
-        assert("|α|", true, Right, cx);
-        assert("||✋", false, Left, cx);
-        assert("||✋", true, Left, cx);
-        assert("||✋", false, Right, cx);
-        assert("|✋|", true, Right, cx);
-        assert("||🍐", false, Left, cx);
-        assert("||🍐", true, Left, cx);
-        assert("||🍐", false, Right, cx);
-        assert("|🍐|", true, Right, cx);
-        assert("||\t", false, Left, cx);
-        assert("||\t", true, Left, cx);
-        assert("||\t", false, Right, cx);
-        assert("|\t|", true, Right, cx);
-        assert(" ||\t", false, Left, cx);
-        assert(" ||\t", true, Left, cx);
-        assert(" ||\t", false, Right, cx);
-        assert(" |\t|", true, Right, cx);
-        assert("   ||\t", false, Left, cx);
-        assert("   ||\t", false, Right, cx);
+        assert("ˇˇα", false, Left, cx);
+        assert("ˇˇα", true, Left, cx);
+        assert("ˇˇα", false, Right, cx);
+        assert("ˇαˇ", true, Right, cx);
+        assert("ˇˇ✋", false, Left, cx);
+        assert("ˇˇ✋", true, Left, cx);
+        assert("ˇˇ✋", false, Right, cx);
+        assert("ˇ✋ˇ", true, Right, cx);
+        assert("ˇˇ🍐", false, Left, cx);
+        assert("ˇˇ🍐", true, Left, cx);
+        assert("ˇˇ🍐", false, Right, cx);
+        assert("ˇ🍐ˇ", true, Right, cx);
+        assert("ˇˇ\t", false, Left, cx);
+        assert("ˇˇ\t", true, Left, cx);
+        assert("ˇˇ\t", false, Right, cx);
+        assert("ˇ\tˇ", true, Right, cx);
+        assert(" ˇˇ\t", false, Left, cx);
+        assert(" ˇˇ\t", true, Left, cx);
+        assert(" ˇˇ\t", false, Right, cx);
+        assert(" ˇ\tˇ", true, Right, cx);
+        assert("   ˇˇ\t", false, Left, cx);
+        assert("   ˇˇ\t", false, Right, cx);
     }
 
     #[gpui::test]
@@ -1284,10 +1283,10 @@ pub mod tests {
             );
         }
 
-        assert("||", cx);
-        assert("|a|", cx);
-        assert("a|b|", cx);
-        assert("a|α|", cx);
+        assert("ˇˇ", cx);
+        assert("ˇaˇ", cx);
+        assert("aˇbˇ", cx);
+        assert("aˇαˇ", cx);
     }
 
     #[gpui::test]

crates/editor/src/editor.rs 🔗

@@ -6644,10 +6644,7 @@ mod tests {
     use unindent::Unindent;
     use util::{
         assert_set_eq,
-        test::{
-            marked_text_ranges, marked_text_ranges_by, parse_marked_text, sample_text,
-            TextRangeMarker,
-        },
+        test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
     };
     use workspace::{FollowableItem, ItemHandle, NavigationEntry, Pane};
 
@@ -7045,7 +7042,7 @@ mod tests {
 
     #[gpui::test]
     fn test_clone(cx: &mut gpui::MutableAppContext) {
-        let (text, selection_ranges) = parse_marked_text(
+        let (text, selection_ranges) = marked_text_ranges(
             indoc! {"
                 one
                 two
@@ -7054,8 +7051,7 @@ mod tests {
                 fiveˇ
             "},
             true,
-        )
-        .unwrap();
+        );
         cx.set_global(Settings::test(cx));
         let buffer = MultiBuffer::build_simple(&text, cx);
 
@@ -9901,15 +9897,14 @@ mod tests {
     async fn test_snippets(cx: &mut gpui::TestAppContext) {
         cx.update(|cx| cx.set_global(Settings::test(cx)));
 
-        let (text, insertion_ranges) = parse_marked_text(
+        let (text, insertion_ranges) = marked_text_ranges(
             indoc! {"
                 a.ˇ b
                 a.ˇ b
                 a.ˇ b
             "},
             false,
-        )
-        .unwrap();
+        );
 
         let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
         let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
@@ -9921,9 +9916,8 @@ mod tests {
                 .insert_snippet(&insertion_ranges, snippet, cx)
                 .unwrap();
 
-            fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text_ranges: &str) {
-                let (expected_text, selection_ranges) =
-                    parse_marked_text(marked_text_ranges, false).unwrap();
+            fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
+                let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
                 assert_eq!(editor.text(cx), expected_text);
                 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
             }
@@ -10318,6 +10312,7 @@ mod tests {
             three sˇ
             additional edit
         "});
+        //
         handle_completion_request(
             &mut cx,
             indoc! {"
@@ -10443,7 +10438,7 @@ mod tests {
             edit: Option<(&'static str, &'static str)>,
         ) {
             let edit = edit.map(|(marked_string, new_text)| {
-                let (_, marked_ranges) = parse_marked_text(marked_string, false).unwrap();
+                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
                 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
                 vec![lsp::TextEdit::new(replace_range, new_text.to_string())]
             });
@@ -10595,13 +10590,21 @@ mod tests {
     #[gpui::test]
     fn test_editing_overlapping_excerpts(cx: &mut gpui::MutableAppContext) {
         cx.set_global(Settings::test(cx));
-        let (initial_text, excerpt_ranges) = marked_text_ranges(indoc! {"
+        let markers = vec![('[', ']').into(), ('(', ')').into()];
+        let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
+            indoc! {"
                 [aaaa
                 (bbbb]
-                cccc)"});
-        let excerpt_ranges = excerpt_ranges.into_iter().map(|context| ExcerptRange {
-            context,
-            primary: None,
+                cccc)",
+            },
+            markers.clone(),
+        );
+        let excerpt_ranges = markers.into_iter().map(|marker| {
+            let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
+            ExcerptRange {
+                context,
+                primary: None,
+            }
         });
         let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
         let multibuffer = cx.add_model(|cx| {
@@ -10612,7 +10615,7 @@ mod tests {
 
         let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
         view.update(cx, |view, cx| {
-            let (expected_text, selection_ranges) = parse_marked_text(
+            let (expected_text, selection_ranges) = marked_text_ranges(
                 indoc! {"
                     aaaa
                     bˇbbb
@@ -10620,14 +10623,13 @@ mod tests {
                     cccc"
                 },
                 true,
-            )
-            .unwrap();
+            );
             assert_eq!(view.text(cx), expected_text);
             view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
 
             view.handle_input("X", cx);
 
-            let (expected_text, expected_selections) = parse_marked_text(
+            let (expected_text, expected_selections) = marked_text_ranges(
                 indoc! {"
                     aaaa
                     bXˇbbXb
@@ -10635,13 +10637,12 @@ mod tests {
                     cccc"
                 },
                 false,
-            )
-            .unwrap();
+            );
             assert_eq!(view.text(cx), expected_text);
             assert_eq!(view.selections.ranges(cx), expected_selections);
 
             view.newline(&Newline, cx);
-            let (expected_text, expected_selections) = parse_marked_text(
+            let (expected_text, expected_selections) = marked_text_ranges(
                 indoc! {"
                     aaaa
                     bX
@@ -10653,8 +10654,7 @@ mod tests {
                     cccc"
                 },
                 false,
-            )
-            .unwrap();
+            );
             assert_eq!(view.text(cx), expected_text);
             assert_eq!(view.selections.ranges(cx), expected_selections);
         });
@@ -11132,7 +11132,7 @@ mod tests {
     }
 
     fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
-        let (text, ranges) = parse_marked_text(marked_text, true).unwrap();
+        let (text, ranges) = marked_text_ranges(marked_text, true);
         assert_eq!(view.text(cx), text);
         assert_eq!(
             view.selections.ranges(cx),

crates/editor/src/movement.rs 🔗

@@ -287,20 +287,20 @@ mod tests {
             );
         }
 
-        assert("\n|   |lorem", cx);
-        assert("|\n|   lorem", cx);
-        assert("    |lorem|", cx);
-        assert("|    |lorem", cx);
-        assert("    |lor|em", cx);
-        assert("\nlorem\n|   |ipsum", cx);
-        assert("\n\n|\n|", cx);
-        assert("    |lorem  |ipsum", cx);
-        assert("lorem|-|ipsum", cx);
-        assert("lorem|-#$@|ipsum", cx);
-        assert("|lorem_|ipsum", cx);
-        assert(" |defγ|", cx);
-        assert(" |bcΔ|", cx);
-        assert(" ab|——|cd", cx);
+        assert("\nˇ   ˇlorem", cx);
+        assert("ˇ\nˇ   lorem", cx);
+        assert("    ˇloremˇ", cx);
+        assert("ˇ    ˇlorem", cx);
+        assert("    ˇlorˇem", cx);
+        assert("\nlorem\nˇ   ˇipsum", cx);
+        assert("\n\nˇ\nˇ", cx);
+        assert("    ˇlorem  ˇipsum", cx);
+        assert("loremˇ-ˇipsum", cx);
+        assert("loremˇ-#$@ˇipsum", cx);
+        assert("ˇlorem_ˇipsum", cx);
+        assert(" ˇdefγˇ", cx);
+        assert(" ˇbcΔˇ", cx);
+        assert(" abˇ——ˇcd", cx);
     }
 
     #[gpui::test]
@@ -315,26 +315,26 @@ mod tests {
         }
 
         // Subword boundaries are respected
-        assert("lorem_|ip|sum", cx);
-        assert("lorem_|ipsum|", cx);
-        assert("|lorem_|ipsum", cx);
-        assert("lorem_|ipsum_|dolor", cx);
-        assert("lorem|Ip|sum", cx);
-        assert("lorem|Ipsum|", cx);
+        assert("lorem_ˇipˇsum", cx);
+        assert("lorem_ˇipsumˇ", cx);
+        assert("ˇlorem_ˇipsum", cx);
+        assert("lorem_ˇipsum_ˇdolor", cx);
+        assert("loremˇIpˇsum", cx);
+        assert("loremˇIpsumˇ", cx);
 
         // Word boundaries are still respected
-        assert("\n|   |lorem", cx);
-        assert("    |lorem|", cx);
-        assert("    |lor|em", cx);
-        assert("\nlorem\n|   |ipsum", cx);
-        assert("\n\n|\n|", cx);
-        assert("    |lorem  |ipsum", cx);
-        assert("lorem|-|ipsum", cx);
-        assert("lorem|-#$@|ipsum", cx);
-        assert(" |defγ|", cx);
-        assert(" bc|Δ|", cx);
-        assert(" |bcδ|", cx);
-        assert(" ab|——|cd", cx);
+        assert("\nˇ   ˇlorem", cx);
+        assert("    ˇloremˇ", cx);
+        assert("    ˇlorˇem", cx);
+        assert("\nlorem\nˇ   ˇipsum", cx);
+        assert("\n\nˇ\nˇ", cx);
+        assert("    ˇlorem  ˇipsum", cx);
+        assert("loremˇ-ˇipsum", cx);
+        assert("loremˇ-#$@ˇipsum", cx);
+        assert(" ˇdefγˇ", cx);
+        assert(" bcˇΔˇ", cx);
+        assert(" ˇbcδˇ", cx);
+        assert(" abˇ——ˇcd", cx);
     }
 
     #[gpui::test]
@@ -352,14 +352,14 @@ mod tests {
             );
         }
 
-        assert("abc|def\ngh\nij|k", cx, |left, right| {
+        assert("abcˇdef\ngh\nijˇk", cx, |left, right| {
             left == 'c' && right == 'd'
         });
-        assert("abcdef\n|gh\nij|k", cx, |left, right| {
+        assert("abcdef\nˇgh\nijˇk", cx, |left, right| {
             left == '\n' && right == 'g'
         });
         let mut line_count = 0;
-        assert("abcdef\n|gh\nij|k", cx, |left, _| {
+        assert("abcdef\nˇgh\nijˇk", cx, |left, _| {
             if left == '\n' {
                 line_count += 1;
                 line_count == 2
@@ -380,17 +380,17 @@ mod tests {
             );
         }
 
-        assert("\n|   lorem|", cx);
-        assert("    |lorem|", cx);
-        assert("    lor|em|", cx);
-        assert("    lorem|    |\nipsum\n", cx);
-        assert("\n|\n|\n\n", cx);
-        assert("lorem|    ipsum|   ", cx);
-        assert("lorem|-|ipsum", cx);
-        assert("lorem|#$@-|ipsum", cx);
-        assert("lorem|_ipsum|", cx);
-        assert(" |bcΔ|", cx);
-        assert(" ab|——|cd", cx);
+        assert("\nˇ   loremˇ", cx);
+        assert("    ˇloremˇ", cx);
+        assert("    lorˇemˇ", cx);
+        assert("    loremˇ    ˇ\nipsum\n", cx);
+        assert("\nˇ\nˇ\n\n", cx);
+        assert("loremˇ    ipsumˇ   ", cx);
+        assert("loremˇ-ˇipsum", cx);
+        assert("loremˇ#$@-ˇipsum", cx);
+        assert("loremˇ_ipsumˇ", cx);
+        assert(" ˇbcΔˇ", cx);
+        assert(" abˇ——ˇcd", cx);
     }
 
     #[gpui::test]
@@ -405,25 +405,25 @@ mod tests {
         }
 
         // Subword boundaries are respected
-        assert("lo|rem|_ipsum", cx);
-        assert("|lorem|_ipsum", cx);
-        assert("lorem|_ipsum|", cx);
-        assert("lorem|_ipsum|_dolor", cx);
-        assert("lo|rem|Ipsum", cx);
-        assert("lorem|Ipsum|Dolor", cx);
+        assert("loˇremˇ_ipsum", cx);
+        assert("ˇloremˇ_ipsum", cx);
+        assert("loremˇ_ipsumˇ", cx);
+        assert("loremˇ_ipsumˇ_dolor", cx);
+        assert("loˇremˇIpsum", cx);
+        assert("loremˇIpsumˇDolor", cx);
 
         // Word boundaries are still respected
-        assert("\n|   lorem|", cx);
-        assert("    |lorem|", cx);
-        assert("    lor|em|", cx);
-        assert("    lorem|    |\nipsum\n", cx);
-        assert("\n|\n|\n\n", cx);
-        assert("lorem|    ipsum|   ", cx);
-        assert("lorem|-|ipsum", cx);
-        assert("lorem|#$@-|ipsum", cx);
-        assert("lorem|_ipsum|", cx);
-        assert(" |bc|Δ", cx);
-        assert(" ab|——|cd", cx);
+        assert("\nˇ   loremˇ", cx);
+        assert("    ˇloremˇ", cx);
+        assert("    lorˇemˇ", cx);
+        assert("    loremˇ    ˇ\nipsum\n", cx);
+        assert("\nˇ\nˇ\n\n", cx);
+        assert("loremˇ    ipsumˇ   ", cx);
+        assert("loremˇ-ˇipsum", cx);
+        assert("loremˇ#$@-ˇipsum", cx);
+        assert("loremˇ_ipsumˇ", cx);
+        assert(" ˇbcˇΔ", cx);
+        assert(" abˇ——ˇcd", cx);
     }
 
     #[gpui::test]
@@ -441,14 +441,14 @@ mod tests {
             );
         }
 
-        assert("abc|def\ngh\nij|k", cx, |left, right| {
+        assert("abcˇdef\ngh\nijˇk", cx, |left, right| {
             left == 'j' && right == 'k'
         });
-        assert("ab|cdef\ngh\n|ijk", cx, |left, right| {
+        assert("abˇcdef\ngh\nˇijk", cx, |left, right| {
             left == '\n' && right == 'i'
         });
         let mut line_count = 0;
-        assert("abc|def\ngh\n|ijk", cx, |left, _| {
+        assert("abcˇdef\ngh\nˇijk", cx, |left, _| {
             if left == '\n' {
                 line_count += 1;
                 line_count == 2
@@ -469,14 +469,14 @@ mod tests {
             );
         }
 
-        assert("||lorem|  ipsum", cx);
-        assert("|lo|rem|  ipsum", cx);
-        assert("|lorem||  ipsum", cx);
-        assert("lorem| |  |ipsum", cx);
-        assert("lorem\n|||\nipsum", cx);
-        assert("lorem\n||ipsum|", cx);
-        assert("lorem,|| |ipsum", cx);
-        assert("|lorem||, ipsum", cx);
+        assert("ˇˇloremˇ  ipsum", cx);
+        assert("ˇloˇremˇ  ipsum", cx);
+        assert("ˇloremˇˇ  ipsum", cx);
+        assert("loremˇ ˇ  ˇipsum", cx);
+        assert("lorem\nˇˇˇ\nipsum", cx);
+        assert("lorem\nˇˇipsumˇ", cx);
+        assert("lorem,ˇˇ ˇipsum", cx);
+        assert("ˇloremˇˇ, ipsum", cx);
     }
 
     #[gpui::test]

crates/editor/src/test.rs 🔗

@@ -20,7 +20,7 @@ use std::{
 };
 use util::{
     assert_set_eq, set_eq,
-    test::{generate_marked_text, marked_text, parse_marked_text},
+    test::{generate_marked_text, marked_text_offsets, marked_text_ranges},
 };
 use workspace::{pane, AppState, Workspace, WorkspaceHandle};
 
@@ -37,7 +37,7 @@ pub fn marked_display_snapshot(
     text: &str,
     cx: &mut gpui::MutableAppContext,
 ) -> (DisplaySnapshot, Vec<DisplayPoint>) {
-    let (unmarked_text, markers) = marked_text(text);
+    let (unmarked_text, markers) = marked_text_offsets(text);
 
     let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
     let font_id = cx
@@ -59,7 +59,7 @@ pub fn marked_display_snapshot(
 }
 
 pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) {
-    let (umarked_text, text_ranges) = parse_marked_text(marked_text, true).unwrap();
+    let (umarked_text, text_ranges) = marked_text_ranges(marked_text, true);
     assert_eq!(editor.text(cx), umarked_text);
     editor.change_selections(None, cx, |s| s.select_ranges(text_ranges));
 }
@@ -69,7 +69,7 @@ pub fn assert_text_with_selections(
     marked_text: &str,
     cx: &mut ViewContext<Editor>,
 ) {
-    let (unmarked_text, text_ranges) = parse_marked_text(marked_text, true).unwrap();
+    let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
     assert_eq!(editor.text(cx), unmarked_text);
     assert_eq!(editor.selections.ranges(cx), text_ranges);
 }
@@ -184,7 +184,7 @@ impl<'a> EditorTestContext<'a> {
     }
 
     pub fn ranges(&self, marked_text: &str) -> Vec<Range<usize>> {
-        let (unmarked_text, ranges) = parse_marked_text(marked_text, false).unwrap();
+        let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
         assert_eq!(self.buffer_text(), unmarked_text);
         ranges
     }
@@ -205,7 +205,7 @@ impl<'a> EditorTestContext<'a> {
     }
 
     pub fn set_state(&mut self, marked_text: &str) {
-        let (unmarked_text, selection_ranges) = parse_marked_text(marked_text, true).unwrap();
+        let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
         self.editor.update(self.cx, |editor, cx| {
             editor.set_text(unmarked_text, cx);
             editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
@@ -215,7 +215,7 @@ impl<'a> EditorTestContext<'a> {
     }
 
     pub fn assert_editor_state(&mut self, marked_text: &str) {
-        let (unmarked_text, expected_selections) = parse_marked_text(marked_text, true).unwrap();
+        let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
         let buffer_text = self.buffer_text();
         assert_eq!(
             buffer_text, unmarked_text,

crates/util/src/test/marked_text.rs 🔗

@@ -1,7 +1,6 @@
-use anyhow::{anyhow, Result};
 use std::{cmp::Ordering, collections::HashMap, ops::Range};
 
-pub fn marked_text_by(
+pub fn marked_text_offsets_by(
     marked_text: &str,
     markers: Vec<char>,
 ) -> (String, HashMap<char, Vec<usize>>) {
@@ -20,118 +19,60 @@ pub fn marked_text_by(
     (unmarked_text, extracted_markers)
 }
 
-pub fn marked_text(marked_text: &str) -> (String, Vec<usize>) {
-    let (unmarked_text, mut markers) = marked_text_by(marked_text, vec!['|']);
-    (unmarked_text, markers.remove(&'|').unwrap_or_default())
-}
-
-#[derive(Clone, Eq, PartialEq, Hash)]
-pub enum TextRangeMarker {
-    Empty(char),
-    Range(char, char),
-    ReverseRange(char, char),
-}
-
-impl TextRangeMarker {
-    fn markers(&self) -> Vec<char> {
-        match self {
-            Self::Empty(m) => vec![*m],
-            Self::Range(l, r) => vec![*l, *r],
-            Self::ReverseRange(l, r) => vec![*l, *r],
-        }
-    }
-}
-
-impl From<char> for TextRangeMarker {
-    fn from(marker: char) -> Self {
-        Self::Empty(marker)
-    }
-}
-
-impl From<(char, char)> for TextRangeMarker {
-    fn from((left_marker, right_marker): (char, char)) -> Self {
-        Self::Range(left_marker, right_marker)
-    }
-}
-
 pub fn marked_text_ranges_by(
     marked_text: &str,
     markers: Vec<TextRangeMarker>,
 ) -> (String, HashMap<TextRangeMarker, Vec<Range<usize>>>) {
     let all_markers = markers.iter().flat_map(|m| m.markers()).collect();
 
-    let (unmarked_text, mut marker_offsets) = marked_text_by(marked_text, all_markers);
+    let (unmarked_text, mut marker_offsets) = marked_text_offsets_by(marked_text, all_markers);
     let range_lookup = markers
         .into_iter()
-        .map(|marker| match marker {
-            TextRangeMarker::Empty(empty_marker_char) => {
-                let ranges = marker_offsets
-                    .remove(&empty_marker_char)
-                    .unwrap_or_default()
-                    .into_iter()
-                    .map(|empty_index| empty_index..empty_index)
-                    .collect::<Vec<Range<usize>>>();
-                (marker, ranges)
-            }
-            TextRangeMarker::Range(start_marker, end_marker) => {
-                let starts = marker_offsets.remove(&start_marker).unwrap_or_default();
-                let ends = marker_offsets.remove(&end_marker).unwrap_or_default();
-                assert_eq!(starts.len(), ends.len(), "marked ranges are unbalanced");
-
-                let ranges = starts
-                    .into_iter()
-                    .zip(ends)
-                    .map(|(start, end)| {
-                        assert!(end >= start, "marked ranges must be disjoint");
-                        start..end
-                    })
-                    .collect::<Vec<Range<usize>>>();
-                (marker, ranges)
-            }
-            TextRangeMarker::ReverseRange(start_marker, end_marker) => {
-                let starts = marker_offsets.remove(&start_marker).unwrap_or_default();
-                let ends = marker_offsets.remove(&end_marker).unwrap_or_default();
-                assert_eq!(starts.len(), ends.len(), "marked ranges are unbalanced");
-
-                let ranges = starts
-                    .into_iter()
-                    .zip(ends)
-                    .map(|(start, end)| {
-                        assert!(end >= start, "marked ranges must be disjoint");
-                        end..start
-                    })
-                    .collect::<Vec<Range<usize>>>();
-                (marker, ranges)
-            }
+        .map(|marker| {
+            (
+                marker.clone(),
+                match marker {
+                    TextRangeMarker::Empty(empty_marker_char) => marker_offsets
+                        .remove(&empty_marker_char)
+                        .unwrap_or_default()
+                        .into_iter()
+                        .map(|empty_index| empty_index..empty_index)
+                        .collect::<Vec<Range<usize>>>(),
+                    TextRangeMarker::Range(start_marker, end_marker) => {
+                        let starts = marker_offsets.remove(&start_marker).unwrap_or_default();
+                        let ends = marker_offsets.remove(&end_marker).unwrap_or_default();
+                        assert_eq!(starts.len(), ends.len(), "marked ranges are unbalanced");
+                        starts
+                            .into_iter()
+                            .zip(ends)
+                            .map(|(start, end)| {
+                                assert!(end >= start, "marked ranges must be disjoint");
+                                start..end
+                            })
+                            .collect::<Vec<Range<usize>>>()
+                    }
+                    TextRangeMarker::ReverseRange(start_marker, end_marker) => {
+                        let starts = marker_offsets.remove(&start_marker).unwrap_or_default();
+                        let ends = marker_offsets.remove(&end_marker).unwrap_or_default();
+                        assert_eq!(starts.len(), ends.len(), "marked ranges are unbalanced");
+                        starts
+                            .into_iter()
+                            .zip(ends)
+                            .map(|(start, end)| {
+                                assert!(end >= start, "marked ranges must be disjoint");
+                                end..start
+                            })
+                            .collect::<Vec<Range<usize>>>()
+                    }
+                },
+            )
         })
         .collect();
 
     (unmarked_text, range_lookup)
 }
 
-// Returns ranges delimited by (), [], and <> ranges. Ranges using the same markers
-// must not be overlapping. May also include | for empty ranges
-pub fn marked_text_ranges(full_marked_text: &str) -> (String, Vec<Range<usize>>) {
-    let (unmarked, range_lookup) = marked_text_ranges_by(
-        &full_marked_text,
-        vec![
-            '|'.into(),
-            ('[', ']').into(),
-            ('(', ')').into(),
-            ('<', '>').into(),
-        ],
-    );
-    let mut combined_ranges: Vec<_> = range_lookup.into_values().flatten().collect();
-
-    combined_ranges.sort_by_key(|range| range.start);
-    (unmarked, combined_ranges)
-}
-
-///
-pub fn parse_marked_text(
-    input_text: &str,
-    indicate_cursors: bool,
-) -> Result<(String, Vec<Range<usize>>)> {
+pub fn marked_text_ranges(input_text: &str, indicate_cursors: bool) -> (String, Vec<Range<usize>>) {
     let mut output_text = String::with_capacity(input_text.len());
     let mut ranges = Vec::new();
     let mut prev_input_ix = 0;
@@ -148,7 +89,7 @@ pub fn parse_marked_text(
             "ˇ" => {
                 if current_range_start.is_some() {
                     if current_range_cursor.is_some() {
-                        Err(anyhow!("duplicate point marker 'ˇ' at index {input_ix}"))?;
+                        panic!("duplicate point marker 'ˇ' at index {input_ix}");
                     } else {
                         current_range_cursor = Some(output_len);
                     }
@@ -158,26 +99,26 @@ pub fn parse_marked_text(
             }
             "«" => {
                 if current_range_start.is_some() {
-                    Err(anyhow!(
-                        "unexpected range start marker '«' at index {input_ix}"
-                    ))?;
+                    panic!("unexpected range start marker '«' at index {input_ix}");
                 }
                 current_range_start = Some(output_len);
             }
             "»" => {
-                let current_range_start = current_range_start.take().ok_or_else(|| {
-                    anyhow!("unexpected range end marker '»' at index {input_ix}")
-                })?;
+                let current_range_start = if let Some(start) = current_range_start.take() {
+                    start
+                } else {
+                    panic!("unexpected range end marker '»' at index {input_ix}");
+                };
 
                 let mut reversed = false;
                 if let Some(current_range_cursor) = current_range_cursor.take() {
                     if current_range_cursor == current_range_start {
                         reversed = true;
                     } else if current_range_cursor != output_len {
-                        Err(anyhow!("unexpected 'ˇ' marker in the middle of a range"))?;
+                        panic!("unexpected 'ˇ' marker in the middle of a range");
                     }
                 } else if indicate_cursors {
-                    Err(anyhow!("missing 'ˇ' marker to indicate range direction"))?;
+                    panic!("missing 'ˇ' marker to indicate range direction");
                 }
 
                 ranges.push(if reversed {
@@ -191,7 +132,21 @@ pub fn parse_marked_text(
     }
 
     output_text.push_str(&input_text[prev_input_ix..]);
-    Ok((output_text, ranges))
+    (output_text, ranges)
+}
+
+pub fn marked_text_offsets(marked_text: &str) -> (String, Vec<usize>) {
+    let (text, ranges) = marked_text_ranges(marked_text, false);
+    (
+        text,
+        ranges
+            .into_iter()
+            .map(|range| {
+                assert_eq!(range.start, range.end);
+                range.start
+            })
+            .collect(),
+    )
 }
 
 pub fn generate_marked_text(
@@ -223,14 +178,42 @@ pub fn generate_marked_text(
     marked_text
 }
 
+#[derive(Clone, Eq, PartialEq, Hash)]
+pub enum TextRangeMarker {
+    Empty(char),
+    Range(char, char),
+    ReverseRange(char, char),
+}
+
+impl TextRangeMarker {
+    fn markers(&self) -> Vec<char> {
+        match self {
+            Self::Empty(m) => vec![*m],
+            Self::Range(l, r) => vec![*l, *r],
+            Self::ReverseRange(l, r) => vec![*l, *r],
+        }
+    }
+}
+
+impl From<char> for TextRangeMarker {
+    fn from(marker: char) -> Self {
+        Self::Empty(marker)
+    }
+}
+
+impl From<(char, char)> for TextRangeMarker {
+    fn from((left_marker, right_marker): (char, char)) -> Self {
+        Self::Range(left_marker, right_marker)
+    }
+}
+
 #[cfg(test)]
 mod tests {
-    use super::{generate_marked_text, parse_marked_text};
+    use super::{generate_marked_text, marked_text_ranges};
 
     #[test]
     fn test_marked_text() {
-        let (text, ranges) =
-            parse_marked_text("one «ˇtwo» «threeˇ» «ˇfour» fiveˇ six", true).unwrap();
+        let (text, ranges) = marked_text_ranges("one «ˇtwo» «threeˇ» «ˇfour» fiveˇ six", true);
 
         assert_eq!(text, "one two three four five six");
         assert_eq!(ranges.len(), 4);

crates/vim/src/normal.rs 🔗

@@ -297,7 +297,7 @@ fn paste(_: &mut Workspace, _: &Paste, cx: &mut ViewContext<Workspace>) {
 #[cfg(test)]
 mod test {
     use indoc::indoc;
-    use util::test::marked_text;
+    use util::test::marked_text_offsets;
 
     use crate::{
         state::{
@@ -521,7 +521,7 @@ mod test {
     #[gpui::test]
     async fn test_w(cx: &mut gpui::TestAppContext) {
         let mut cx = VimTestContext::new(cx, true).await;
-        let (_, cursor_offsets) = marked_text(indoc! {"
+        let (_, cursor_offsets) = marked_text_offsets(indoc! {"
             The ˇquickˇ-ˇbrown
             ˇ
             ˇ
@@ -543,7 +543,7 @@ mod test {
         }
 
         // Reset and test ignoring punctuation
-        let (_, cursor_offsets) = marked_text(indoc! {"
+        let (_, cursor_offsets) = marked_text_offsets(indoc! {"
             The ˇquick-brown
             ˇ
             ˇ
@@ -568,7 +568,7 @@ mod test {
     #[gpui::test]
     async fn test_e(cx: &mut gpui::TestAppContext) {
         let mut cx = VimTestContext::new(cx, true).await;
-        let (_, cursor_offsets) = marked_text(indoc! {"
+        let (_, cursor_offsets) = marked_text_offsets(indoc! {"
             Thˇe quicˇkˇ-browˇn
             
             
@@ -590,7 +590,7 @@ mod test {
         }
 
         // Reset and test ignoring punctuation
-        let (_, cursor_offsets) = marked_text(indoc! {"
+        let (_, cursor_offsets) = marked_text_offsets(indoc! {"
             Thˇe quick-browˇn
             
             
@@ -614,7 +614,7 @@ mod test {
     #[gpui::test]
     async fn test_b(cx: &mut gpui::TestAppContext) {
         let mut cx = VimTestContext::new(cx, true).await;
-        let (_, cursor_offsets) = marked_text(indoc! {"
+        let (_, cursor_offsets) = marked_text_offsets(indoc! {"
             ˇˇThe ˇquickˇ-ˇbrown
             ˇ
             ˇ
@@ -636,7 +636,7 @@ mod test {
         }
 
         // Reset and test ignoring punctuation
-        let (_, cursor_offsets) = marked_text(indoc! {"
+        let (_, cursor_offsets) = marked_text_offsets(indoc! {"
             ˇˇThe ˇquick-brown
             ˇ
             ˇ