Store selections with a right start bias so that autoindent moves them
Previously, cursors at column 0 had to be explicitly moved when those lines were autoindented. This behavior was lost when we moved selections from the buffer to the editor. Now, with the right bias, we get this behavior automatically. Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
43db9e826b
commit
cbd9e186b5
2 changed files with 118 additions and 107 deletions
|
@ -487,25 +487,14 @@ impl Editor {
|
||||||
cx.observe(&display_map, Self::on_display_map_changed)
|
cx.observe(&display_map, Self::on_display_map_changed)
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let mut next_selection_id = 0;
|
let mut this = Self {
|
||||||
let selections = Arc::from(
|
|
||||||
&[Selection {
|
|
||||||
id: post_inc(&mut next_selection_id),
|
|
||||||
start: Anchor::min(),
|
|
||||||
end: Anchor::min(),
|
|
||||||
reversed: false,
|
|
||||||
goal: SelectionGoal::None,
|
|
||||||
}][..],
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
handle: cx.weak_handle(),
|
handle: cx.weak_handle(),
|
||||||
buffer,
|
buffer,
|
||||||
display_map,
|
display_map,
|
||||||
selections,
|
selections: Arc::from([]),
|
||||||
pending_selection: None,
|
pending_selection: None,
|
||||||
columnar_selection_tail: None,
|
columnar_selection_tail: None,
|
||||||
next_selection_id,
|
next_selection_id: 0,
|
||||||
add_selections_state: None,
|
add_selections_state: None,
|
||||||
select_next_state: None,
|
select_next_state: None,
|
||||||
selection_history: Default::default(),
|
selection_history: Default::default(),
|
||||||
|
@ -523,7 +512,16 @@ impl Editor {
|
||||||
mode: EditorMode::Full,
|
mode: EditorMode::Full,
|
||||||
placeholder_text: None,
|
placeholder_text: None,
|
||||||
highlighted_row: None,
|
highlighted_row: None,
|
||||||
}
|
};
|
||||||
|
let selection = Selection {
|
||||||
|
id: post_inc(&mut this.next_selection_id),
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
reversed: false,
|
||||||
|
goal: SelectionGoal::None,
|
||||||
|
};
|
||||||
|
this.update_selections(vec![selection], None, cx);
|
||||||
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_new(
|
pub fn open_new(
|
||||||
|
@ -1263,36 +1261,33 @@ impl Editor {
|
||||||
pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
|
pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
|
||||||
self.start_transaction(cx);
|
self.start_transaction(cx);
|
||||||
let old_selections = self.local_selections::<usize>(cx);
|
let old_selections = self.local_selections::<usize>(cx);
|
||||||
let new_selections = self.buffer.update(cx, |buffer, cx| {
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
let snapshot = buffer.read(cx);
|
|
||||||
let new_selections = old_selections
|
|
||||||
.iter()
|
|
||||||
.map(|selection| Selection {
|
|
||||||
id: selection.id,
|
|
||||||
start: snapshot.anchor_after(selection.start),
|
|
||||||
end: snapshot.anchor_after(selection.end),
|
|
||||||
reversed: false,
|
|
||||||
goal: SelectionGoal::None,
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
drop(snapshot);
|
|
||||||
let edit_ranges = old_selections.iter().map(|s| s.start..s.end);
|
let edit_ranges = old_selections.iter().map(|s| s.start..s.end);
|
||||||
buffer.edit_with_autoindent(edit_ranges, text, cx);
|
buffer.edit_with_autoindent(edit_ranges, text, cx);
|
||||||
|
|
||||||
let snapshot = buffer.read(cx);
|
|
||||||
self.resolve_selections::<usize, _>(new_selections.iter(), &snapshot)
|
|
||||||
.collect()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
self.update_selections(new_selections, Some(Autoscroll::Fit), cx);
|
let selections = self.local_selections::<usize>(cx);
|
||||||
|
self.update_selections(selections, Some(Autoscroll::Fit), cx);
|
||||||
self.end_transaction(cx);
|
self.end_transaction(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn autoclose_pairs(&mut self, cx: &mut ViewContext<Self>) {
|
fn autoclose_pairs(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
let selections = self.local_selections::<usize>(cx);
|
let selections = self.local_selections::<usize>(cx);
|
||||||
let new_autoclose_pair = self.buffer.update(cx, |buffer, cx| {
|
let mut bracket_pair_state = None;
|
||||||
let snapshot = buffer.snapshot(cx);
|
let mut new_selections = None;
|
||||||
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
|
let mut snapshot = buffer.snapshot(cx);
|
||||||
|
let left_biased_selections = selections
|
||||||
|
.iter()
|
||||||
|
.map(|selection| Selection {
|
||||||
|
id: selection.id,
|
||||||
|
start: snapshot.anchor_before(selection.start),
|
||||||
|
end: snapshot.anchor_before(selection.end),
|
||||||
|
reversed: selection.reversed,
|
||||||
|
goal: selection.goal,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let autoclose_pair = snapshot.language().and_then(|language| {
|
let autoclose_pair = snapshot.language().and_then(|language| {
|
||||||
let first_selection_start = selections.first().unwrap().start;
|
let first_selection_start = selections.first().unwrap().start;
|
||||||
let pair = language.brackets().iter().find(|pair| {
|
let pair = language.brackets().iter().find(|pair| {
|
||||||
|
@ -1317,7 +1312,7 @@ impl Editor {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
autoclose_pair.and_then(|pair| {
|
if let Some(pair) = autoclose_pair {
|
||||||
let selection_ranges = selections
|
let selection_ranges = selections
|
||||||
.iter()
|
.iter()
|
||||||
.map(|selection| {
|
.map(|selection| {
|
||||||
|
@ -1327,11 +1322,16 @@ impl Editor {
|
||||||
.collect::<SmallVec<[_; 32]>>();
|
.collect::<SmallVec<[_; 32]>>();
|
||||||
|
|
||||||
buffer.edit(selection_ranges, &pair.end, cx);
|
buffer.edit(selection_ranges, &pair.end, cx);
|
||||||
let snapshot = buffer.snapshot(cx);
|
snapshot = buffer.snapshot(cx);
|
||||||
|
|
||||||
|
new_selections = Some(
|
||||||
|
self.resolve_selections::<usize, _>(left_biased_selections.iter(), &snapshot)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
|
|
||||||
if pair.end.len() == 1 {
|
if pair.end.len() == 1 {
|
||||||
let mut delta = 0;
|
let mut delta = 0;
|
||||||
Some(BracketPairState {
|
bracket_pair_state = Some(BracketPairState {
|
||||||
ranges: selections
|
ranges: selections
|
||||||
.iter()
|
.iter()
|
||||||
.map(move |selection| {
|
.map(move |selection| {
|
||||||
|
@ -1341,13 +1341,17 @@ impl Editor {
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
pair,
|
pair,
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
self.autoclose_stack.extend(new_autoclose_pair);
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(new_selections) = new_selections {
|
||||||
|
self.update_selections(new_selections, None, cx);
|
||||||
|
}
|
||||||
|
if let Some(bracket_pair_state) = bracket_pair_state {
|
||||||
|
self.autoclose_stack.push(bracket_pair_state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skip_autoclose_end(&mut self, text: &str, cx: &mut ViewContext<Self>) -> bool {
|
fn skip_autoclose_end(&mut self, text: &str, cx: &mut ViewContext<Self>) -> bool {
|
||||||
|
@ -3265,12 +3269,19 @@ impl Editor {
|
||||||
self.pause_cursor_blinking(cx);
|
self.pause_cursor_blinking(cx);
|
||||||
|
|
||||||
self.set_selections(
|
self.set_selections(
|
||||||
Arc::from_iter(selections.into_iter().map(|selection| Selection {
|
Arc::from_iter(selections.into_iter().map(|selection| {
|
||||||
|
let end_bias = if selection.end > selection.start {
|
||||||
|
Bias::Left
|
||||||
|
} else {
|
||||||
|
Bias::Right
|
||||||
|
};
|
||||||
|
Selection {
|
||||||
id: selection.id,
|
id: selection.id,
|
||||||
start: buffer.anchor_before(selection.start),
|
start: buffer.anchor_after(selection.start),
|
||||||
end: buffer.anchor_before(selection.end),
|
end: buffer.anchor_at(selection.end, end_bias),
|
||||||
reversed: selection.reversed,
|
reversed: selection.reversed,
|
||||||
goal: selection.goal,
|
goal: selection.goal,
|
||||||
|
}
|
||||||
})),
|
})),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -5730,6 +5741,63 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_autoindent_selections(mut cx: gpui::TestAppContext) {
|
||||||
|
let settings = cx.read(EditorSettings::test);
|
||||||
|
let language = Some(Arc::new(
|
||||||
|
Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
brackets: vec![
|
||||||
|
BracketPair {
|
||||||
|
start: "{".to_string(),
|
||||||
|
end: "}".to_string(),
|
||||||
|
close: false,
|
||||||
|
newline: true,
|
||||||
|
},
|
||||||
|
BracketPair {
|
||||||
|
start: "(".to_string(),
|
||||||
|
end: ")".to_string(),
|
||||||
|
close: false,
|
||||||
|
newline: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Some(tree_sitter_rust::language()),
|
||||||
|
)
|
||||||
|
.with_indents_query(
|
||||||
|
r#"
|
||||||
|
(_ "(" ")" @end) @indent
|
||||||
|
(_ "{" "}" @end) @indent
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let text = "fn a() {}";
|
||||||
|
|
||||||
|
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx));
|
||||||
|
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
|
let (_, editor) = cx.add_window(|cx| build_editor(buffer, settings, cx));
|
||||||
|
editor
|
||||||
|
.condition(&cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
editor.update(&mut cx, |editor, cx| {
|
||||||
|
editor.select_ranges([5..5, 8..8, 9..9], None, cx);
|
||||||
|
editor.newline(&Newline, cx);
|
||||||
|
assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
|
||||||
|
assert_eq!(
|
||||||
|
editor.selected_ranges(cx),
|
||||||
|
&[
|
||||||
|
Point::new(1, 4)..Point::new(1, 4),
|
||||||
|
Point::new(3, 4)..Point::new(3, 4),
|
||||||
|
Point::new(5, 0)..Point::new(5, 0)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_autoclose_pairs(mut cx: gpui::TestAppContext) {
|
async fn test_autoclose_pairs(mut cx: gpui::TestAppContext) {
|
||||||
let settings = cx.read(EditorSettings::test);
|
let settings = cx.read(EditorSettings::test);
|
||||||
|
|
|
@ -330,63 +330,6 @@ fn test_edit_with_autoindent(cx: &mut MutableAppContext) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need another approach to managing selections with auto-indent
|
|
||||||
|
|
||||||
// #[gpui::test]
|
|
||||||
// fn test_autoindent_moves_selections(cx: &mut MutableAppContext) {
|
|
||||||
// cx.add_model(|cx| {
|
|
||||||
// let text = "fn a() {}";
|
|
||||||
|
|
||||||
// let mut buffer =
|
|
||||||
// Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
|
|
||||||
|
|
||||||
// let selection_set_id = buffer.add_selection_set::<usize>(&[], cx);
|
|
||||||
// buffer.start_transaction();
|
|
||||||
// buffer.edit_with_autoindent([5..5, 9..9], "\n\n", cx);
|
|
||||||
// buffer
|
|
||||||
// .update_selection_set(
|
|
||||||
// selection_set_id,
|
|
||||||
// &[
|
|
||||||
// Selection {
|
|
||||||
// id: 0,
|
|
||||||
// start: Point::new(1, 0),
|
|
||||||
// end: Point::new(1, 0),
|
|
||||||
// reversed: false,
|
|
||||||
// goal: SelectionGoal::None,
|
|
||||||
// },
|
|
||||||
// Selection {
|
|
||||||
// id: 1,
|
|
||||||
// start: Point::new(4, 0),
|
|
||||||
// end: Point::new(4, 0),
|
|
||||||
// reversed: false,
|
|
||||||
// goal: SelectionGoal::None,
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// cx,
|
|
||||||
// )
|
|
||||||
// .unwrap();
|
|
||||||
// assert_eq!(buffer.text(), "fn a(\n\n) {}\n\n");
|
|
||||||
|
|
||||||
// // TODO! Come up with a different approach to moving selections now that we don't manage selection sets in the buffer
|
|
||||||
|
|
||||||
// // Ending the transaction runs the auto-indent. The selection
|
|
||||||
// // at the start of the auto-indented row is pushed to the right.
|
|
||||||
// buffer.end_transaction(cx);
|
|
||||||
// assert_eq!(buffer.text(), "fn a(\n \n) {}\n\n");
|
|
||||||
// let selection_ranges = buffer
|
|
||||||
// .selection_set(selection_set_id)
|
|
||||||
// .unwrap()
|
|
||||||
// .selections::<Point>(&buffer)
|
|
||||||
// .map(|selection| selection.start.to_point(&buffer)..selection.end.to_point(&buffer))
|
|
||||||
// .collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// assert_eq!(selection_ranges[0], empty(Point::new(1, 4)));
|
|
||||||
// assert_eq!(selection_ranges[1], empty(Point::new(4, 0)));
|
|
||||||
|
|
||||||
// buffer
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) {
|
fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) {
|
||||||
cx.add_model(|cx| {
|
cx.add_model(|cx| {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue