fix bug in marked_range utils
This commit is contained in:
parent
98f9575653
commit
e104cb94e7
3 changed files with 217 additions and 227 deletions
|
@ -2735,28 +2735,31 @@ impl Editor {
|
||||||
pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
|
pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
let mut selections = self.selections.all::<Point>(cx);
|
let mut selections = self.selections.all::<Point>(cx);
|
||||||
for selection in &mut selections {
|
if !self.selections.line_mode {
|
||||||
if selection.is_empty() && !self.selections.line_mode {
|
for selection in &mut selections {
|
||||||
let old_head = selection.head();
|
if selection.is_empty() {
|
||||||
let mut new_head =
|
let old_head = selection.head();
|
||||||
movement::left(&display_map, old_head.to_display_point(&display_map))
|
let mut new_head =
|
||||||
.to_point(&display_map);
|
movement::left(&display_map, old_head.to_display_point(&display_map))
|
||||||
if let Some((buffer, line_buffer_range)) = display_map
|
.to_point(&display_map);
|
||||||
.buffer_snapshot
|
if let Some((buffer, line_buffer_range)) = display_map
|
||||||
.buffer_line_for_row(old_head.row)
|
.buffer_snapshot
|
||||||
{
|
.buffer_line_for_row(old_head.row)
|
||||||
let indent_column = buffer.indent_column_for_line(line_buffer_range.start.row);
|
{
|
||||||
let language_name = buffer.language().map(|language| language.name());
|
let indent_column =
|
||||||
let indent = cx.global::<Settings>().tab_size(language_name.as_deref());
|
buffer.indent_column_for_line(line_buffer_range.start.row);
|
||||||
if old_head.column <= indent_column && old_head.column > 0 {
|
let language_name = buffer.language().map(|language| language.name());
|
||||||
new_head = cmp::min(
|
let indent = cx.global::<Settings>().tab_size(language_name.as_deref());
|
||||||
new_head,
|
if old_head.column <= indent_column && old_head.column > 0 {
|
||||||
Point::new(old_head.row, ((old_head.column - 1) / indent) * indent),
|
new_head = cmp::min(
|
||||||
);
|
new_head,
|
||||||
|
Point::new(old_head.row, ((old_head.column - 1) / indent) * indent),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
selection.set_head(new_head, SelectionGoal::None);
|
selection.set_head(new_head, SelectionGoal::None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7492,8 +7495,7 @@ mod tests {
|
||||||
|The qu[ick b}rown"});
|
|The qu[ick b}rown"});
|
||||||
cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
|
cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
|
||||||
cx.assert_editor_state(indoc! {"
|
cx.assert_editor_state(indoc! {"
|
||||||
|
|
|fox jumps over
|
||||||
fox jumps over
|
|
||||||
the lazy dog|"});
|
the lazy dog|"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7516,13 +7518,11 @@ mod tests {
|
||||||
cx.update_editor(|e, _| e.selections.line_mode = true);
|
cx.update_editor(|e, _| e.selections.line_mode = true);
|
||||||
cx.set_state(indoc! {"
|
cx.set_state(indoc! {"
|
||||||
The |quick |brown
|
The |quick |brown
|
||||||
fox {jum]ps over|
|
fox {jum]ps over
|
||||||
the lazy dog
|
the lazy dog
|
||||||
|The qu[ick b}rown"});
|
|The qu[ick b}rown"});
|
||||||
cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
|
cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
|
||||||
cx.assert_editor_state(indoc! {"
|
cx.assert_editor_state("|the lazy dog|");
|
||||||
|
|
|
||||||
the lazy dog|"});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -7830,131 +7830,79 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_clipboard(cx: &mut gpui::MutableAppContext) {
|
async fn test_clipboard(cx: &mut gpui::TestAppContext) {
|
||||||
cx.set_global(Settings::test(cx));
|
let mut cx = EditorTestContext::new(cx).await;
|
||||||
let buffer = MultiBuffer::build_simple("one✅ two three four five six ", cx);
|
|
||||||
let view = cx
|
|
||||||
.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx))
|
|
||||||
.1;
|
|
||||||
|
|
||||||
// Cut with three selections. Clipboard text is divided into three slices.
|
cx.set_state("[one✅ }two [three }four [five }six ");
|
||||||
view.update(cx, |view, cx| {
|
cx.update_editor(|e, cx| e.cut(&Cut, cx));
|
||||||
view.change_selections(None, cx, |s| s.select_ranges(vec![0..7, 11..17, 22..27]));
|
cx.assert_editor_state("|two |four |six ");
|
||||||
view.cut(&Cut, cx);
|
|
||||||
assert_eq!(view.display_text(cx), "two four six ");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Paste with three cursors. Each cursor pastes one slice of the clipboard text.
|
// Paste with three cursors. Each cursor pastes one slice of the clipboard text.
|
||||||
view.update(cx, |view, cx| {
|
cx.set_state("two |four |six |");
|
||||||
view.change_selections(None, cx, |s| s.select_ranges(vec![4..4, 9..9, 13..13]));
|
cx.update_editor(|e, cx| e.paste(&Paste, cx));
|
||||||
view.paste(&Paste, cx);
|
cx.assert_editor_state("two one✅ |four three |six five |");
|
||||||
assert_eq!(view.display_text(cx), "two one✅ four three six five ");
|
|
||||||
assert_eq!(
|
|
||||||
view.selections.display_ranges(cx),
|
|
||||||
&[
|
|
||||||
DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
|
|
||||||
DisplayPoint::new(0, 22)..DisplayPoint::new(0, 22),
|
|
||||||
DisplayPoint::new(0, 31)..DisplayPoint::new(0, 31)
|
|
||||||
]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Paste again but with only two cursors. Since the number of cursors doesn't
|
// Paste again but with only two cursors. Since the number of cursors doesn't
|
||||||
// match the number of slices in the clipboard, the entire clipboard text
|
// match the number of slices in the clipboard, the entire clipboard text
|
||||||
// is pasted at each cursor.
|
// is pasted at each cursor.
|
||||||
view.update(cx, |view, cx| {
|
cx.set_state("|two one✅ four three six five |");
|
||||||
view.change_selections(None, cx, |s| s.select_ranges(vec![0..0, 31..31]));
|
cx.update_editor(|e, cx| {
|
||||||
view.handle_input(&Input("( ".into()), cx);
|
e.handle_input(&Input("( ".into()), cx);
|
||||||
view.paste(&Paste, cx);
|
e.paste(&Paste, cx);
|
||||||
view.handle_input(&Input(") ".into()), cx);
|
e.handle_input(&Input(") ".into()), cx);
|
||||||
assert_eq!(
|
|
||||||
view.display_text(cx),
|
|
||||||
"( one✅ \nthree \nfive ) two one✅ four three six five ( one✅ \nthree \nfive ) "
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
view.update(cx, |view, cx| {
|
|
||||||
view.change_selections(None, cx, |s| s.select_ranges(vec![0..0]));
|
|
||||||
view.handle_input(&Input("123\n4567\n89\n".into()), cx);
|
|
||||||
assert_eq!(
|
|
||||||
view.display_text(cx),
|
|
||||||
"123\n4567\n89\n( one✅ \nthree \nfive ) two one✅ four three six five ( one✅ \nthree \nfive ) "
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
cx.assert_editor_state(indoc! {"
|
||||||
|
( one✅
|
||||||
|
three
|
||||||
|
five ) |two one✅ four three six five ( one✅
|
||||||
|
three
|
||||||
|
five ) |"});
|
||||||
|
|
||||||
// Cut with three selections, one of which is full-line.
|
// Cut with three selections, one of which is full-line.
|
||||||
view.update(cx, |view, cx| {
|
cx.set_state(indoc! {"
|
||||||
view.change_selections(None, cx, |s| s.select_display_ranges(
|
1[2}3
|
||||||
[
|
4|567
|
||||||
DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2),
|
[8}9"});
|
||||||
DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
|
cx.update_editor(|e, cx| e.cut(&Cut, cx));
|
||||||
DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
|
cx.assert_editor_state(indoc! {"
|
||||||
],
|
1|3
|
||||||
));
|
|9"});
|
||||||
view.cut(&Cut, cx);
|
|
||||||
assert_eq!(
|
|
||||||
view.display_text(cx),
|
|
||||||
"13\n9\n( one✅ \nthree \nfive ) two one✅ four three six five ( one✅ \nthree \nfive ) "
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Paste with three selections, noticing how the copied selection that was full-line
|
// Paste with three selections, noticing how the copied selection that was full-line
|
||||||
// gets inserted before the second cursor.
|
// gets inserted before the second cursor.
|
||||||
view.update(cx, |view, cx| {
|
cx.set_state(indoc! {"
|
||||||
view.change_selections(None, cx, |s| s.select_display_ranges(
|
1|3
|
||||||
[
|
9|
|
||||||
DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
|
[o}ne"});
|
||||||
DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
|
cx.update_editor(|e, cx| e.paste(&Paste, cx));
|
||||||
DisplayPoint::new(2, 2)..DisplayPoint::new(2, 3),
|
cx.assert_editor_state(indoc! {"
|
||||||
],
|
12|3
|
||||||
));
|
4567
|
||||||
view.paste(&Paste, cx);
|
9|
|
||||||
assert_eq!(
|
8|ne"});
|
||||||
view.display_text(cx),
|
|
||||||
"123\n4567\n9\n( 8ne✅ \nthree \nfive ) two one✅ four three six five ( one✅ \nthree \nfive ) "
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
view.selections.display_ranges(cx),
|
|
||||||
&[
|
|
||||||
DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
|
|
||||||
DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
|
|
||||||
DisplayPoint::new(3, 3)..DisplayPoint::new(3, 3),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Copy with a single cursor only, which writes the whole line into the clipboard.
|
// Copy with a single cursor only, which writes the whole line into the clipboard.
|
||||||
view.update(cx, |view, cx| {
|
cx.set_state(indoc! {"
|
||||||
view.change_selections(None, cx, |s| {
|
The quick brown
|
||||||
s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)])
|
fox ju|mps over
|
||||||
});
|
the lazy dog"});
|
||||||
view.copy(&Copy, cx);
|
cx.update_editor(|e, cx| e.copy(&Copy, cx));
|
||||||
});
|
cx.assert_clipboard_content(Some("fox jumps over\n"));
|
||||||
|
|
||||||
// Paste with three selections, noticing how the copied full-line selection is inserted
|
// Paste with three selections, noticing how the copied full-line selection is inserted
|
||||||
// before the empty selections but replaces the selection that is non-empty.
|
// before the empty selections but replaces the selection that is non-empty.
|
||||||
view.update(cx, |view, cx| {
|
cx.set_state(indoc! {"
|
||||||
view.change_selections(None, cx, |s| s.select_display_ranges(
|
T|he quick brown
|
||||||
[
|
[fo}x jumps over
|
||||||
DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
|
t|he lazy dog"});
|
||||||
DisplayPoint::new(1, 0)..DisplayPoint::new(1, 2),
|
cx.update_editor(|e, cx| e.paste(&Paste, cx));
|
||||||
DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
|
cx.assert_editor_state(indoc! {"
|
||||||
],
|
fox jumps over
|
||||||
));
|
T|he quick brown
|
||||||
view.paste(&Paste, cx);
|
fox jumps over
|
||||||
assert_eq!(
|
|x jumps over
|
||||||
view.display_text(cx),
|
fox jumps over
|
||||||
"123\n123\n123\n67\n123\n9\n( 8ne✅ \nthree \nfive ) two one✅ four three six five ( one✅ \nthree \nfive ) "
|
t|he lazy dog"});
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
view.selections.display_ranges(cx),
|
|
||||||
&[
|
|
||||||
DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
|
|
||||||
DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
|
|
||||||
DisplayPoint::new(5, 1)..DisplayPoint::new(5, 1),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -8693,8 +8641,10 @@ mod tests {
|
||||||
fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text_ranges: &str) {
|
fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text_ranges: &str) {
|
||||||
let range_markers = ('<', '>');
|
let range_markers = ('<', '>');
|
||||||
let (expected_text, mut selection_ranges_lookup) =
|
let (expected_text, mut selection_ranges_lookup) =
|
||||||
marked_text_ranges_by(marked_text_ranges, vec![range_markers.clone()]);
|
marked_text_ranges_by(marked_text_ranges, vec![range_markers.clone().into()]);
|
||||||
let selection_ranges = selection_ranges_lookup.remove(&range_markers).unwrap();
|
let selection_ranges = selection_ranges_lookup
|
||||||
|
.remove(&range_markers.into())
|
||||||
|
.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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ use indoc::indoc;
|
||||||
|
|
||||||
use collections::BTreeMap;
|
use collections::BTreeMap;
|
||||||
use gpui::{keymap::Keystroke, ModelHandle, ViewContext, ViewHandle};
|
use gpui::{keymap::Keystroke, ModelHandle, ViewContext, ViewHandle};
|
||||||
use itertools::{Either, Itertools};
|
|
||||||
use language::Selection;
|
use language::Selection;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use util::{
|
use util::{
|
||||||
|
@ -138,17 +137,26 @@ impl<'a> EditorTestContext<'a> {
|
||||||
// `{` to `]` represents a non empty selection with the head at `{`
|
// `{` to `]` represents a non empty selection with the head at `{`
|
||||||
pub fn set_state(&mut self, text: &str) {
|
pub fn set_state(&mut self, text: &str) {
|
||||||
self.editor.update(self.cx, |editor, cx| {
|
self.editor.update(self.cx, |editor, cx| {
|
||||||
let (text_with_ranges, empty_selections) = marked_text(&text);
|
let (unmarked_text, mut selection_ranges) = marked_text_ranges_by(
|
||||||
let (unmarked_text, mut selection_ranges) =
|
&text,
|
||||||
marked_text_ranges_by(&text_with_ranges, vec![('[', '}'), ('{', ']')]);
|
vec!['|'.into(), ('[', '}').into(), ('{', ']').into()],
|
||||||
|
);
|
||||||
editor.set_text(unmarked_text, cx);
|
editor.set_text(unmarked_text, cx);
|
||||||
|
|
||||||
let mut selections: Vec<Range<usize>> = empty_selections
|
let mut selections: Vec<Range<usize>> =
|
||||||
.into_iter()
|
selection_ranges.remove(&'|'.into()).unwrap_or_default();
|
||||||
.map(|offset| offset..offset)
|
selections.extend(
|
||||||
.collect();
|
selection_ranges
|
||||||
selections.extend(selection_ranges.remove(&('{', ']')).unwrap_or_default());
|
.remove(&('{', ']').into())
|
||||||
selections.extend(selection_ranges.remove(&('[', '}')).unwrap_or_default());
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|range| range.end..range.start),
|
||||||
|
);
|
||||||
|
selections.extend(
|
||||||
|
selection_ranges
|
||||||
|
.remove(&('[', '}').into())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
editor.change_selections(Some(Autoscroll::Fit), cx, |s| s.select_ranges(selections));
|
editor.change_selections(Some(Autoscroll::Fit), cx, |s| s.select_ranges(selections));
|
||||||
})
|
})
|
||||||
|
@ -159,17 +167,23 @@ impl<'a> EditorTestContext<'a> {
|
||||||
// `[` to `}` represents a non empty selection with the head at `}`
|
// `[` to `}` represents a non empty selection with the head at `}`
|
||||||
// `{` to `]` represents a non empty selection with the head at `{`
|
// `{` to `]` represents a non empty selection with the head at `{`
|
||||||
pub fn assert_editor_state(&mut self, text: &str) {
|
pub fn assert_editor_state(&mut self, text: &str) {
|
||||||
let (text_with_ranges, expected_empty_selections) = marked_text(&text);
|
let (unmarked_text, mut selection_ranges) = marked_text_ranges_by(
|
||||||
let (unmarked_text, mut selection_ranges) =
|
&text,
|
||||||
marked_text_ranges_by(&text_with_ranges, vec![('[', '}'), ('{', ']')]);
|
vec!['|'.into(), ('[', '}').into(), ('{', ']').into()],
|
||||||
|
);
|
||||||
let editor_text = self.editor_text();
|
let editor_text = self.editor_text();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor_text, unmarked_text,
|
editor_text, unmarked_text,
|
||||||
"Unmarked text doesn't match editor text"
|
"Unmarked text doesn't match editor text"
|
||||||
);
|
);
|
||||||
|
|
||||||
let expected_reverse_selections = selection_ranges.remove(&('{', ']')).unwrap_or_default();
|
let expected_empty_selections = selection_ranges.remove(&'|'.into()).unwrap_or_default();
|
||||||
let expected_forward_selections = selection_ranges.remove(&('[', '}')).unwrap_or_default();
|
let expected_reverse_selections = selection_ranges
|
||||||
|
.remove(&('{', ']').into())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let expected_forward_selections = selection_ranges
|
||||||
|
.remove(&('[', '}').into())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
self.assert_selections(
|
self.assert_selections(
|
||||||
expected_empty_selections,
|
expected_empty_selections,
|
||||||
|
@ -180,65 +194,53 @@ impl<'a> EditorTestContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
|
pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
|
||||||
let (expected_empty_selections, expected_non_empty_selections): (Vec<_>, Vec<_>) =
|
let mut empty_selections = Vec::new();
|
||||||
expected_selections.into_iter().partition_map(|selection| {
|
let mut reverse_selections = Vec::new();
|
||||||
if selection.is_empty() {
|
let mut forward_selections = Vec::new();
|
||||||
Either::Left(selection.head())
|
|
||||||
} else {
|
|
||||||
Either::Right(selection)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let (expected_reverse_selections, expected_forward_selections): (Vec<_>, Vec<_>) =
|
for selection in expected_selections {
|
||||||
expected_non_empty_selections
|
let range = selection.range();
|
||||||
.into_iter()
|
if selection.is_empty() {
|
||||||
.partition_map(|selection| {
|
empty_selections.push(range);
|
||||||
let range = selection.start..selection.end;
|
} else if selection.reversed {
|
||||||
if selection.reversed {
|
reverse_selections.push(range);
|
||||||
Either::Left(range)
|
} else {
|
||||||
} else {
|
forward_selections.push(range)
|
||||||
Either::Right(range)
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
self.assert_selections(
|
self.assert_selections(
|
||||||
expected_empty_selections,
|
empty_selections,
|
||||||
expected_reverse_selections,
|
reverse_selections,
|
||||||
expected_forward_selections,
|
forward_selections,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_selections(
|
fn assert_selections(
|
||||||
&mut self,
|
&mut self,
|
||||||
expected_empty_selections: Vec<usize>,
|
expected_empty_selections: Vec<Range<usize>>,
|
||||||
expected_reverse_selections: Vec<Range<usize>>,
|
expected_reverse_selections: Vec<Range<usize>>,
|
||||||
expected_forward_selections: Vec<Range<usize>>,
|
expected_forward_selections: Vec<Range<usize>>,
|
||||||
asserted_text: Option<String>,
|
asserted_text: Option<String>,
|
||||||
) {
|
) {
|
||||||
let (empty_selections, reverse_selections, forward_selections) =
|
let (empty_selections, reverse_selections, forward_selections) =
|
||||||
self.editor.read_with(self.cx, |editor, cx| {
|
self.editor.read_with(self.cx, |editor, cx| {
|
||||||
let (empty_selections, non_empty_selections): (Vec<_>, Vec<_>) = editor
|
let mut empty_selections = Vec::new();
|
||||||
.selections
|
let mut reverse_selections = Vec::new();
|
||||||
.all::<usize>(cx)
|
let mut forward_selections = Vec::new();
|
||||||
.into_iter()
|
|
||||||
.partition_map(|selection| {
|
for selection in editor.selections.all::<usize>(cx) {
|
||||||
if selection.is_empty() {
|
let range = selection.range();
|
||||||
Either::Left(selection.head())
|
if selection.is_empty() {
|
||||||
} else {
|
empty_selections.push(range);
|
||||||
Either::Right(selection)
|
} else if selection.reversed {
|
||||||
}
|
reverse_selections.push(range);
|
||||||
});
|
} else {
|
||||||
|
forward_selections.push(range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (reverse_selections, forward_selections): (Vec<_>, Vec<_>) =
|
|
||||||
non_empty_selections.into_iter().partition_map(|selection| {
|
|
||||||
let range = selection.start..selection.end;
|
|
||||||
if selection.reversed {
|
|
||||||
Either::Left(range)
|
|
||||||
} else {
|
|
||||||
Either::Right(range)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
(empty_selections, reverse_selections, forward_selections)
|
(empty_selections, reverse_selections, forward_selections)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -258,7 +260,7 @@ impl<'a> EditorTestContext<'a> {
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
err.map(|missing| {
|
err.map(|missing| {
|
||||||
let mut error_text = unmarked_text.clone();
|
let mut error_text = unmarked_text.clone();
|
||||||
error_text.insert(missing, '|');
|
error_text.insert(missing.start, '|');
|
||||||
error_text
|
error_text
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -316,14 +318,14 @@ impl<'a> EditorTestContext<'a> {
|
||||||
|
|
||||||
fn insert_markers(
|
fn insert_markers(
|
||||||
&mut self,
|
&mut self,
|
||||||
empty_selections: &Vec<usize>,
|
empty_selections: &Vec<Range<usize>>,
|
||||||
reverse_selections: &Vec<Range<usize>>,
|
reverse_selections: &Vec<Range<usize>>,
|
||||||
forward_selections: &Vec<Range<usize>>,
|
forward_selections: &Vec<Range<usize>>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut editor_text_with_selections = self.editor_text();
|
let mut editor_text_with_selections = self.editor_text();
|
||||||
let mut selection_marks = BTreeMap::new();
|
let mut selection_marks = BTreeMap::new();
|
||||||
for offset in empty_selections {
|
for range in empty_selections {
|
||||||
selection_marks.insert(offset, '|');
|
selection_marks.insert(&range.start, '|');
|
||||||
}
|
}
|
||||||
for range in reverse_selections {
|
for range in reverse_selections {
|
||||||
selection_marks.insert(&range.start, '{');
|
selection_marks.insert(&range.start, '{');
|
||||||
|
|
|
@ -24,31 +24,67 @@ pub fn marked_text(marked_text: &str) -> (String, Vec<usize>) {
|
||||||
(unmarked_text, markers.remove(&'|').unwrap_or_default())
|
(unmarked_text, markers.remove(&'|').unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
|
pub enum TextRangeMarker {
|
||||||
|
Empty(char),
|
||||||
|
Range(char, char),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextRangeMarker {
|
||||||
|
fn markers(&self) -> Vec<char> {
|
||||||
|
match self {
|
||||||
|
Self::Empty(m) => vec![*m],
|
||||||
|
Self::Range(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,
|
||||||
delimiters: Vec<(char, char)>,
|
markers: Vec<TextRangeMarker>,
|
||||||
) -> (String, HashMap<(char, char), Vec<Range<usize>>>) {
|
) -> (String, HashMap<TextRangeMarker, Vec<Range<usize>>>) {
|
||||||
let all_markers = delimiters
|
let all_markers = markers.iter().flat_map(|m| m.markers()).collect();
|
||||||
.iter()
|
|
||||||
.flat_map(|(start, end)| [*start, *end])
|
|
||||||
.collect();
|
|
||||||
let (unmarked_text, mut markers) = marked_text_by(marked_text, all_markers);
|
|
||||||
let range_lookup = delimiters
|
|
||||||
.into_iter()
|
|
||||||
.map(|(start_marker, end_marker)| {
|
|
||||||
let starts = markers.remove(&start_marker).unwrap_or_default();
|
|
||||||
let ends = markers.remove(&end_marker).unwrap_or_default();
|
|
||||||
assert_eq!(starts.len(), ends.len(), "marked ranges are unbalanced");
|
|
||||||
|
|
||||||
let ranges = starts
|
let (unmarked_text, mut marker_offsets) = marked_text_by(marked_text, all_markers);
|
||||||
.into_iter()
|
let range_lookup = markers
|
||||||
.zip(ends)
|
.into_iter()
|
||||||
.map(|(start, end)| {
|
.map(|marker| match marker {
|
||||||
assert!(end >= start, "marked ranges must be disjoint");
|
TextRangeMarker::Empty(empty_marker_char) => {
|
||||||
start..end
|
let ranges = marker_offsets
|
||||||
})
|
.remove(&empty_marker_char)
|
||||||
.collect::<Vec<Range<usize>>>();
|
.unwrap_or_default()
|
||||||
((start_marker, end_marker), ranges)
|
.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)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -58,14 +94,16 @@ pub fn marked_text_ranges_by(
|
||||||
// Returns ranges delimited by (), [], and <> ranges. Ranges using the same markers
|
// Returns ranges delimited by (), [], and <> ranges. Ranges using the same markers
|
||||||
// must not be overlapping. May also include | for empty ranges
|
// must not be overlapping. May also include | for empty ranges
|
||||||
pub fn marked_text_ranges(full_marked_text: &str) -> (String, Vec<Range<usize>>) {
|
pub fn marked_text_ranges(full_marked_text: &str) -> (String, Vec<Range<usize>>) {
|
||||||
let (range_marked_text, empty_offsets) = marked_text(full_marked_text);
|
let (unmarked, range_lookup) = marked_text_ranges_by(
|
||||||
let (unmarked, range_lookup) =
|
&full_marked_text,
|
||||||
marked_text_ranges_by(&range_marked_text, vec![('[', ']'), ('(', ')'), ('<', '>')]);
|
vec![
|
||||||
let mut combined_ranges: Vec<_> = range_lookup
|
'|'.into(),
|
||||||
.into_values()
|
('[', ']').into(),
|
||||||
.flatten()
|
('(', ')').into(),
|
||||||
.chain(empty_offsets.into_iter().map(|offset| offset..offset))
|
('<', '>').into(),
|
||||||
.collect();
|
],
|
||||||
|
);
|
||||||
|
let mut combined_ranges: Vec<_> = range_lookup.into_values().flatten().collect();
|
||||||
|
|
||||||
combined_ranges.sort_by_key(|range| range.start);
|
combined_ranges.sort_by_key(|range| range.start);
|
||||||
(unmarked, combined_ranges)
|
(unmarked, combined_ranges)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue