From 26fdaeb92b315ffd2aa5fb9638a68637c61adec2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Aug 2022 10:23:03 -0700 Subject: [PATCH] Use new marked ranges format whenever we don't need overlapping ranges --- 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(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 6bf37be813..ab5b7155a5 100644 --- a/crates/editor/src/display_map.rs +++ b/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] diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5bcdbd8f37..7c2d560a41 100644 --- a/crates/editor/src/editor.rs +++ b/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, 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, 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::(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) { - 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), diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 656473dbe8..0db5cc0812 100644 --- a/crates/editor/src/movement.rs +++ b/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] diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 57d220d6f3..30f336c68e 100644 --- a/crates/editor/src/test.rs +++ b/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) { - 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) { - 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, ) { - 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> { - 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, diff --git a/crates/util/src/test/marked_text.rs b/crates/util/src/test/marked_text.rs index 539134121c..0b630ea49f 100644 --- a/crates/util/src/test/marked_text.rs +++ b/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, ) -> (String, HashMap>) { @@ -20,118 +19,60 @@ pub fn marked_text_by( (unmarked_text, extracted_markers) } -pub fn marked_text(marked_text: &str) -> (String, Vec) { - 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 { - match self { - Self::Empty(m) => vec![*m], - Self::Range(l, r) => vec![*l, *r], - Self::ReverseRange(l, r) => vec![*l, *r], - } - } -} - -impl From 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, ) -> (String, HashMap>>) { 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::>>(); - (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::>>(); - (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::>>(); - (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::>>(), + 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::>>() + } + 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::>>() + } + }, + ) }) .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>) { - 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>)> { +pub fn marked_text_ranges(input_text: &str, indicate_cursors: bool) -> (String, Vec>) { 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) { + 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 { + match self { + Self::Empty(m) => vec![*m], + Self::Range(l, r) => vec![*l, *r], + Self::ReverseRange(l, r) => vec![*l, *r], + } + } +} + +impl From 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); diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index ce18e7306a..508477ea46 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -297,7 +297,7 @@ fn paste(_: &mut Workspace, _: &Paste, cx: &mut ViewContext) { #[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 ˇ ˇ