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

This commit is contained in:
Max Brunsfeld 2022-08-04 10:23:03 -07:00
parent 8e6fb76681
commit 26fdaeb92b
6 changed files with 241 additions and 259 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,6 @@
use anyhow::{anyhow, Result};
use std::{cmp::Ordering, collections::HashMap, ops::Range}; use std::{cmp::Ordering, collections::HashMap, ops::Range};
pub fn marked_text_by( pub fn marked_text_offsets_by(
marked_text: &str, marked_text: &str,
markers: Vec<char>, markers: Vec<char>,
) -> (String, HashMap<char, Vec<usize>>) { ) -> (String, HashMap<char, Vec<usize>>) {
@ -20,118 +19,60 @@ pub fn marked_text_by(
(unmarked_text, extracted_markers) (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( pub fn marked_text_ranges_by(
marked_text: &str, marked_text: &str,
markers: Vec<TextRangeMarker>, markers: Vec<TextRangeMarker>,
) -> (String, HashMap<TextRangeMarker, Vec<Range<usize>>>) { ) -> (String, HashMap<TextRangeMarker, Vec<Range<usize>>>) {
let all_markers = markers.iter().flat_map(|m| m.markers()).collect(); 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 let range_lookup = markers
.into_iter() .into_iter()
.map(|marker| match marker { .map(|marker| {
TextRangeMarker::Empty(empty_marker_char) => { (
let ranges = marker_offsets marker.clone(),
.remove(&empty_marker_char) match marker {
.unwrap_or_default() TextRangeMarker::Empty(empty_marker_char) => marker_offsets
.into_iter() .remove(&empty_marker_char)
.map(|empty_index| empty_index..empty_index) .unwrap_or_default()
.collect::<Vec<Range<usize>>>(); .into_iter()
(marker, ranges) .map(|empty_index| empty_index..empty_index)
} .collect::<Vec<Range<usize>>>(),
TextRangeMarker::Range(start_marker, end_marker) => { TextRangeMarker::Range(start_marker, end_marker) => {
let starts = marker_offsets.remove(&start_marker).unwrap_or_default(); let starts = marker_offsets.remove(&start_marker).unwrap_or_default();
let ends = marker_offsets.remove(&end_marker).unwrap_or_default(); let ends = marker_offsets.remove(&end_marker).unwrap_or_default();
assert_eq!(starts.len(), ends.len(), "marked ranges are unbalanced"); assert_eq!(starts.len(), ends.len(), "marked ranges are unbalanced");
starts
let ranges = starts .into_iter()
.into_iter() .zip(ends)
.zip(ends) .map(|(start, end)| {
.map(|(start, end)| { assert!(end >= start, "marked ranges must be disjoint");
assert!(end >= start, "marked ranges must be disjoint"); start..end
start..end })
}) .collect::<Vec<Range<usize>>>()
.collect::<Vec<Range<usize>>>(); }
(marker, ranges) TextRangeMarker::ReverseRange(start_marker, end_marker) => {
} let starts = marker_offsets.remove(&start_marker).unwrap_or_default();
TextRangeMarker::ReverseRange(start_marker, end_marker) => { let ends = marker_offsets.remove(&end_marker).unwrap_or_default();
let starts = marker_offsets.remove(&start_marker).unwrap_or_default(); assert_eq!(starts.len(), ends.len(), "marked ranges are unbalanced");
let ends = marker_offsets.remove(&end_marker).unwrap_or_default(); starts
assert_eq!(starts.len(), ends.len(), "marked ranges are unbalanced"); .into_iter()
.zip(ends)
let ranges = starts .map(|(start, end)| {
.into_iter() assert!(end >= start, "marked ranges must be disjoint");
.zip(ends) end..start
.map(|(start, end)| { })
assert!(end >= start, "marked ranges must be disjoint"); .collect::<Vec<Range<usize>>>()
end..start }
}) },
.collect::<Vec<Range<usize>>>(); )
(marker, ranges)
}
}) })
.collect(); .collect();
(unmarked_text, range_lookup) (unmarked_text, range_lookup)
} }
// Returns ranges delimited by (), [], and <> ranges. Ranges using the same markers pub fn marked_text_ranges(input_text: &str, indicate_cursors: bool) -> (String, Vec<Range<usize>>) {
// 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>>)> {
let mut output_text = String::with_capacity(input_text.len()); let mut output_text = String::with_capacity(input_text.len());
let mut ranges = Vec::new(); let mut ranges = Vec::new();
let mut prev_input_ix = 0; let mut prev_input_ix = 0;
@ -148,7 +89,7 @@ pub fn parse_marked_text(
"ˇ" => { "ˇ" => {
if current_range_start.is_some() { if current_range_start.is_some() {
if current_range_cursor.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 { } else {
current_range_cursor = Some(output_len); current_range_cursor = Some(output_len);
} }
@ -158,26 +99,26 @@ pub fn parse_marked_text(
} }
"«" => { "«" => {
if current_range_start.is_some() { if current_range_start.is_some() {
Err(anyhow!( panic!("unexpected range start marker '«' at index {input_ix}");
"unexpected range start marker '«' at index {input_ix}"
))?;
} }
current_range_start = Some(output_len); current_range_start = Some(output_len);
} }
"»" => { "»" => {
let current_range_start = current_range_start.take().ok_or_else(|| { let current_range_start = if let Some(start) = current_range_start.take() {
anyhow!("unexpected range end marker '»' at index {input_ix}") start
})?; } else {
panic!("unexpected range end marker '»' at index {input_ix}");
};
let mut reversed = false; let mut reversed = false;
if let Some(current_range_cursor) = current_range_cursor.take() { if let Some(current_range_cursor) = current_range_cursor.take() {
if current_range_cursor == current_range_start { if current_range_cursor == current_range_start {
reversed = true; reversed = true;
} else if current_range_cursor != output_len { } 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 { } else if indicate_cursors {
Err(anyhow!("missing 'ˇ' marker to indicate range direction"))?; panic!("missing 'ˇ' marker to indicate range direction");
} }
ranges.push(if reversed { ranges.push(if reversed {
@ -191,7 +132,21 @@ pub fn parse_marked_text(
} }
output_text.push_str(&input_text[prev_input_ix..]); 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( pub fn generate_marked_text(
@ -223,14 +178,42 @@ pub fn generate_marked_text(
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)] #[cfg(test)]
mod tests { mod tests {
use super::{generate_marked_text, parse_marked_text}; use super::{generate_marked_text, marked_text_ranges};
#[test] #[test]
fn test_marked_text() { fn test_marked_text() {
let (text, ranges) = let (text, ranges) = marked_text_ranges("one «ˇtwo» «threeˇ» «ˇfour» fiveˇ six", true);
parse_marked_text("one «ˇtwo» «threeˇ» «ˇfour» fiveˇ six", true).unwrap();
assert_eq!(text, "one two three four five six"); assert_eq!(text, "one two three four five six");
assert_eq!(ranges.len(), 4); assert_eq!(ranges.len(), 4);

View file

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