diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1ffe18114b..91d02b1aa0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1640,8 +1640,10 @@ impl Editor { let text = action.0.as_ref(); if !self.skip_autoclose_end(text, cx) { self.start_transaction(cx); - self.insert(text, cx); - self.autoclose_pairs(cx); + if !self.surround_with_bracket_pair(text, cx) { + self.insert(text, cx); + self.autoclose_bracket_pairs(cx); + } self.end_transaction(cx); self.trigger_completion_on_input(text, cx); } @@ -1824,7 +1826,54 @@ impl Editor { } } - fn autoclose_pairs(&mut self, cx: &mut ViewContext) { + fn surround_with_bracket_pair(&mut self, text: &str, cx: &mut ViewContext) -> bool { + let snapshot = self.buffer.read(cx).snapshot(cx); + if let Some(pair) = snapshot + .language() + .and_then(|language| language.brackets().iter().find(|b| b.start == text)) + .cloned() + { + if self + .local_selections::(cx) + .iter() + .any(|selection| selection.is_empty()) + { + false + } else { + let mut selections = self.selections.to_vec(); + for selection in &mut selections { + selection.end = selection.end.bias_left(&snapshot); + } + drop(snapshot); + + self.buffer.update(cx, |buffer, cx| { + buffer.edit( + selections.iter().map(|s| s.start.clone()..s.start.clone()), + &pair.start, + cx, + ); + buffer.edit( + selections.iter().map(|s| s.end.clone()..s.end.clone()), + &pair.end, + cx, + ); + }); + + let snapshot = self.buffer.read(cx).read(cx); + for selection in &mut selections { + selection.end = selection.end.bias_right(&snapshot); + } + drop(snapshot); + + self.set_selections(selections.into(), None, cx); + true + } + } else { + false + } + } + + fn autoclose_bracket_pairs(&mut self, cx: &mut ViewContext) { let selections = self.local_selections::(cx); let mut bracket_pair_state = None; let mut new_selections = None; @@ -1850,11 +1899,24 @@ impl Editor { ) }); pair.and_then(|pair| { - let should_autoclose = selections[1..].iter().all(|selection| { - snapshot.contains_str_at( + let should_autoclose = selections.iter().all(|selection| { + // Ensure all selections are parked at the end of a pair start. + if snapshot.contains_str_at( selection.start.saturating_sub(pair.start.len()), &pair.start, - ) + ) { + // Autoclose only if the next character is a whitespace or a pair end + // (possibly a different one from the pair we are inserting). + snapshot + .chars_at(selection.start) + .next() + .map_or(true, |ch| ch.is_whitespace()) + || language.brackets().iter().any(|pair| { + snapshot.contains_str_at(selection.start, &pair.end) + }) + } else { + false + } }); if should_autoclose { @@ -3201,6 +3263,11 @@ impl Editor { } } + pub fn finalize_last_transaction(&mut self, cx: &mut ViewContext) { + self.buffer + .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx)); + } + pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.local_selections::(cx); @@ -8083,6 +8150,38 @@ mod tests { " .unindent() ); + + view.finalize_last_transaction(cx); + view.select_display_ranges(&[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)], cx); + view.handle_input(&Input("{".to_string()), cx); + assert_eq!( + view.text(cx), + " + {a + + /* + * + " + .unindent() + ); + + view.undo(&Undo, cx); + view.select_display_ranges(&[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1)], cx); + view.handle_input(&Input("{".to_string()), cx); + assert_eq!( + view.text(cx), + " + {a} + + /* + * + " + .unindent() + ); + assert_eq!( + view.selected_display_ranges(cx), + [DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)] + ); }); }