mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-10 14:06:11 +03:00
Restructure autoclosing to account for multi-language documents
This commit is contained in:
parent
67e188a015
commit
2b0794f5ae
@ -410,7 +410,7 @@ pub struct Editor {
|
||||
add_selections_state: Option<AddSelectionsState>,
|
||||
select_next_state: Option<SelectNextState>,
|
||||
selection_history: SelectionHistory,
|
||||
autoclose_stack: InvalidationStack<BracketPairState>,
|
||||
autoclose_regions: Vec<AutocloseRegion>,
|
||||
snippet_stack: InvalidationStack<SnippetState>,
|
||||
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
|
||||
ime_transaction: Option<TransactionId>,
|
||||
@ -569,8 +569,9 @@ struct SelectNextState {
|
||||
done: bool,
|
||||
}
|
||||
|
||||
struct BracketPairState {
|
||||
ranges: Vec<Range<Anchor>>,
|
||||
struct AutocloseRegion {
|
||||
selection_id: usize,
|
||||
range: Range<Anchor>,
|
||||
pair: BracketPair,
|
||||
}
|
||||
|
||||
@ -1010,7 +1011,7 @@ impl Editor {
|
||||
add_selections_state: None,
|
||||
select_next_state: None,
|
||||
selection_history: Default::default(),
|
||||
autoclose_stack: Default::default(),
|
||||
autoclose_regions: Default::default(),
|
||||
snippet_stack: Default::default(),
|
||||
select_larger_syntax_node_stack: Vec::new(),
|
||||
ime_transaction: Default::default(),
|
||||
@ -1401,8 +1402,7 @@ impl Editor {
|
||||
self.add_selections_state = None;
|
||||
self.select_next_state = None;
|
||||
self.select_larger_syntax_node_stack.clear();
|
||||
self.autoclose_stack
|
||||
.invalidate(&self.selections.disjoint_anchors(), buffer);
|
||||
self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
|
||||
self.snippet_stack
|
||||
.invalidate(&self.selections.disjoint_anchors(), buffer);
|
||||
self.take_rename(false, cx);
|
||||
@ -1849,15 +1849,158 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.skip_autoclose_end(text, cx) {
|
||||
self.transact(cx, |this, cx| {
|
||||
if !this.surround_with_bracket_pair(text, cx) {
|
||||
this.insert(text, cx);
|
||||
this.autoclose_bracket_pairs(cx);
|
||||
let text: Arc<str> = text.into();
|
||||
let selections = self.selections.all_adjusted(cx);
|
||||
let mut edits = Vec::new();
|
||||
let mut new_selections = Vec::with_capacity(selections.len());
|
||||
let mut new_autoclose_regions = Vec::new();
|
||||
let snapshot = self.buffer.read(cx).read(cx);
|
||||
|
||||
for (selection, autoclose_region) in
|
||||
self.selections_with_autoclose_regions(selections, &snapshot)
|
||||
{
|
||||
if let Some(language) = snapshot.language_at(selection.head()) {
|
||||
// Determine if the inserted text matches the opening or closing
|
||||
// bracket of any of this language's bracket pairs.
|
||||
let mut bracket_pair = None;
|
||||
let mut is_bracket_pair_start = false;
|
||||
for pair in language.brackets() {
|
||||
if pair.start.ends_with(text.as_ref()) {
|
||||
bracket_pair = Some(pair.clone());
|
||||
is_bracket_pair_start = true;
|
||||
break;
|
||||
} else if pair.end.as_str() == text.as_ref() {
|
||||
bracket_pair = Some(pair.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
self.trigger_completion_on_input(text, cx);
|
||||
|
||||
if let Some(bracket_pair) = bracket_pair {
|
||||
if selection.is_empty() {
|
||||
if is_bracket_pair_start {
|
||||
let prefix_len = bracket_pair.start.len() - text.len();
|
||||
|
||||
// If the inserted text is a suffix of an opening bracket and the
|
||||
// selection is preceded by the rest of the opening bracket, then
|
||||
// insert the closing bracket.
|
||||
let should_autoclose = selection.start.column > (prefix_len as u32)
|
||||
&& snapshot.contains_str_at(
|
||||
Point::new(
|
||||
selection.start.row,
|
||||
selection.start.column - (prefix_len as u32),
|
||||
),
|
||||
&bracket_pair.start[..prefix_len],
|
||||
)
|
||||
&& snapshot
|
||||
.chars_at(selection.start)
|
||||
.next()
|
||||
.map_or(true, |c| language.should_autoclose_before(c));
|
||||
if should_autoclose {
|
||||
let anchor = snapshot.anchor_before(selection.end);
|
||||
new_selections
|
||||
.push((selection.map(|_| anchor.clone()), text.len()));
|
||||
new_autoclose_regions.push((
|
||||
anchor.clone(),
|
||||
text.len(),
|
||||
selection.id,
|
||||
bracket_pair.clone(),
|
||||
));
|
||||
edits.push((
|
||||
selection.range(),
|
||||
format!("{}{}", text, bracket_pair.end).into(),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
} else if let Some(region) = autoclose_region {
|
||||
// If the selection is followed by an auto-inserted closing bracket,
|
||||
// then don't insert anything else; just move the selection past the
|
||||
// closing bracket.
|
||||
let should_skip = selection.end == region.range.end.to_point(&snapshot);
|
||||
if should_skip {
|
||||
let anchor = snapshot.anchor_after(selection.end);
|
||||
new_selections.push((
|
||||
selection.map(|_| anchor.clone()),
|
||||
region.pair.end.len(),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If an opening bracket is typed while text is selected, then
|
||||
// surround that text with the bracket pair.
|
||||
else if is_bracket_pair_start {
|
||||
edits.push((selection.start..selection.start, text.clone()));
|
||||
edits.push((
|
||||
selection.end..selection.end,
|
||||
bracket_pair.end.as_str().into(),
|
||||
));
|
||||
new_selections.push((
|
||||
Selection {
|
||||
id: selection.id,
|
||||
start: snapshot.anchor_after(selection.start),
|
||||
end: snapshot.anchor_before(selection.end),
|
||||
reversed: selection.reversed,
|
||||
goal: selection.goal,
|
||||
},
|
||||
0,
|
||||
));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not handling any auto-close operation, then just replace the selected
|
||||
// text with the given input and move the selection to the end of the
|
||||
// newly inserted text.
|
||||
let anchor = snapshot.anchor_after(selection.end);
|
||||
new_selections.push((selection.map(|_| anchor.clone()), 0));
|
||||
edits.push((selection.start..selection.end, text.clone()));
|
||||
}
|
||||
|
||||
drop(snapshot);
|
||||
self.transact(cx, |this, cx| {
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
|
||||
});
|
||||
|
||||
let new_anchor_selections = new_selections.iter().map(|e| &e.0);
|
||||
let new_selection_deltas = new_selections.iter().map(|e| e.1);
|
||||
let snapshot = this.buffer.read(cx).read(cx);
|
||||
let new_selections = resolve_multiple::<usize, _>(new_anchor_selections, &snapshot)
|
||||
.zip(new_selection_deltas)
|
||||
.map(|(selection, delta)| selection.map(|e| e + delta))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut i = 0;
|
||||
for (position, delta, selection_id, pair) in new_autoclose_regions {
|
||||
let position = position.to_offset(&snapshot) + delta;
|
||||
let start = snapshot.anchor_before(position);
|
||||
let end = snapshot.anchor_after(position);
|
||||
while let Some(existing_state) = this.autoclose_regions.get(i) {
|
||||
match existing_state.range.start.cmp(&start, &snapshot) {
|
||||
Ordering::Less => i += 1,
|
||||
Ordering::Greater => break,
|
||||
Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) {
|
||||
Ordering::Less => i += 1,
|
||||
Ordering::Equal => break,
|
||||
Ordering::Greater => break,
|
||||
},
|
||||
}
|
||||
}
|
||||
this.autoclose_regions.insert(
|
||||
i,
|
||||
AutocloseRegion {
|
||||
selection_id,
|
||||
range: start..end,
|
||||
pair,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
drop(snapshot);
|
||||
this.change_selections(None, cx, |s| s.select(new_selections));
|
||||
this.trigger_completion_on_input(&text, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
|
||||
@ -2029,232 +2172,89 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn surround_with_bracket_pair(&mut self, text: &str, cx: &mut ViewContext<Self>) -> 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
|
||||
.selections
|
||||
.all::<usize>(cx)
|
||||
.iter()
|
||||
.any(|selection| selection.is_empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut selections = self.selections.disjoint_anchors().to_vec();
|
||||
for selection in &mut selections {
|
||||
selection.end = selection.end.bias_left(&snapshot);
|
||||
}
|
||||
drop(snapshot);
|
||||
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
let pair_start: Arc<str> = pair.start.clone().into();
|
||||
let pair_end: Arc<str> = pair.end.clone().into();
|
||||
buffer.edit(
|
||||
selections.iter().flat_map(|s| {
|
||||
[
|
||||
(s.start.clone()..s.start.clone(), pair_start.clone()),
|
||||
(s.end.clone()..s.end.clone(), pair_end.clone()),
|
||||
]
|
||||
}),
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let snapshot = self.buffer.read(cx).read(cx);
|
||||
for selection in &mut selections {
|
||||
selection.end = selection.end.bias_right(&snapshot);
|
||||
}
|
||||
drop(snapshot);
|
||||
|
||||
self.change_selections(None, cx, |s| s.select_anchors(selections));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn autoclose_bracket_pairs(&mut self, cx: &mut ViewContext<Self>) {
|
||||
/// If any empty selections is touching the start of its innermost containing autoclose
|
||||
/// region, expand it to select the brackets.
|
||||
fn select_autoclose_pair(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let selections = self.selections.all::<usize>(cx);
|
||||
let mut bracket_pair_state = None;
|
||||
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.map(|p| snapshot.anchor_before(p)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let autoclose_pair = snapshot.language().and_then(|language| {
|
||||
let first_selection_start = selections.first().unwrap().start;
|
||||
let pair = language.brackets().iter().find(|pair| {
|
||||
pair.close
|
||||
&& snapshot.contains_str_at(
|
||||
first_selection_start.saturating_sub(pair.start.len()),
|
||||
&pair.start,
|
||||
)
|
||||
});
|
||||
pair.and_then(|pair| {
|
||||
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,
|
||||
) {
|
||||
snapshot
|
||||
.chars_at(selection.start)
|
||||
.next()
|
||||
.map_or(true, |c| language.should_autoclose_before(c))
|
||||
} else {
|
||||
false
|
||||
let buffer = self.buffer.read(cx).read(cx);
|
||||
let mut new_selections = Vec::new();
|
||||
for (mut selection, region) in self.selections_with_autoclose_regions(selections, &buffer) {
|
||||
if let (Some(region), true) = (region, selection.is_empty()) {
|
||||
let mut range = region.range.to_offset(&buffer);
|
||||
if selection.start == range.start {
|
||||
if range.start >= region.pair.start.len() {
|
||||
range.start -= region.pair.start.len();
|
||||
if buffer.contains_str_at(range.start, ®ion.pair.start) {
|
||||
if buffer.contains_str_at(range.end, ®ion.pair.end) {
|
||||
range.end += region.pair.end.len();
|
||||
selection.start = range.start;
|
||||
selection.end = range.end;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if should_autoclose {
|
||||
Some(pair.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(pair) = autoclose_pair {
|
||||
let selection_ranges = selections
|
||||
.iter()
|
||||
.map(|selection| {
|
||||
let start = selection.start.to_offset(&snapshot);
|
||||
start..start
|
||||
})
|
||||
.collect::<SmallVec<[_; 32]>>();
|
||||
|
||||
let pair_end: Arc<str> = pair.end.clone().into();
|
||||
buffer.edit(
|
||||
selection_ranges
|
||||
.iter()
|
||||
.map(|range| (range.clone(), pair_end.clone())),
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
snapshot = buffer.snapshot(cx);
|
||||
|
||||
new_selections = Some(
|
||||
resolve_multiple::<usize, _>(left_biased_selections.iter(), &snapshot)
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
if pair.end.len() == 1 {
|
||||
let mut delta = 0;
|
||||
bracket_pair_state = Some(BracketPairState {
|
||||
ranges: selections
|
||||
.iter()
|
||||
.map(move |selection| {
|
||||
let offset = selection.start + delta;
|
||||
delta += 1;
|
||||
snapshot.anchor_before(offset)..snapshot.anchor_after(offset)
|
||||
})
|
||||
.collect(),
|
||||
pair,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
new_selections.push(selection);
|
||||
}
|
||||
|
||||
if let Some(new_selections) = new_selections {
|
||||
self.change_selections(None, cx, |s| {
|
||||
s.select(new_selections);
|
||||
});
|
||||
}
|
||||
if let Some(bracket_pair_state) = bracket_pair_state {
|
||||
self.autoclose_stack.push(bracket_pair_state);
|
||||
}
|
||||
drop(buffer);
|
||||
self.change_selections(None, cx, |selections| selections.select(new_selections));
|
||||
}
|
||||
|
||||
fn skip_autoclose_end(&mut self, text: &str, cx: &mut ViewContext<Self>) -> bool {
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let old_selections = self.selections.all::<usize>(cx);
|
||||
let autoclose_pair = if let Some(autoclose_pair) = self.autoclose_stack.last() {
|
||||
autoclose_pair
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
if text != autoclose_pair.pair.end {
|
||||
return false;
|
||||
}
|
||||
/// Iterate the given selections, and for each one, find the smallest surrounding
|
||||
/// autoclose region. This uses the ordering of the selections and the autoclose
|
||||
/// regions to avoid repeated comparisons.
|
||||
fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
|
||||
&'a self,
|
||||
selections: impl IntoIterator<Item = Selection<D>>,
|
||||
buffer: &'a MultiBufferSnapshot,
|
||||
) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
|
||||
let mut i = 0;
|
||||
let mut pair_states = self.autoclose_regions.as_slice();
|
||||
selections.into_iter().map(move |selection| {
|
||||
let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
|
||||
|
||||
debug_assert_eq!(old_selections.len(), autoclose_pair.ranges.len());
|
||||
|
||||
if old_selections
|
||||
.iter()
|
||||
.zip(autoclose_pair.ranges.iter().map(|r| r.to_offset(&buffer)))
|
||||
.all(|(selection, autoclose_range)| {
|
||||
let autoclose_range_end = autoclose_range.end.to_offset(&buffer);
|
||||
selection.is_empty() && selection.start == autoclose_range_end
|
||||
})
|
||||
{
|
||||
let new_selections = old_selections
|
||||
.into_iter()
|
||||
.map(|selection| {
|
||||
let cursor = selection.start + 1;
|
||||
Selection {
|
||||
id: selection.id,
|
||||
start: cursor,
|
||||
end: cursor,
|
||||
reversed: false,
|
||||
goal: SelectionGoal::None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
self.autoclose_stack.pop();
|
||||
self.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
||||
s.select(new_selections);
|
||||
});
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn select_autoclose_pair(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let old_selections = self.selections.all::<usize>(cx);
|
||||
let autoclose_pair = if let Some(autoclose_pair) = self.autoclose_stack.last() {
|
||||
autoclose_pair
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
|
||||
debug_assert_eq!(old_selections.len(), autoclose_pair.ranges.len());
|
||||
|
||||
let mut new_selections = Vec::new();
|
||||
for (selection, autoclose_range) in old_selections
|
||||
.iter()
|
||||
.zip(autoclose_pair.ranges.iter().map(|r| r.to_offset(&buffer)))
|
||||
{
|
||||
if selection.is_empty()
|
||||
&& autoclose_range.is_empty()
|
||||
&& selection.start == autoclose_range.start
|
||||
{
|
||||
new_selections.push(Selection {
|
||||
id: selection.id,
|
||||
start: selection.start - autoclose_pair.pair.start.len(),
|
||||
end: selection.end + autoclose_pair.pair.end.len(),
|
||||
reversed: true,
|
||||
goal: selection.goal,
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
let mut enclosing = None;
|
||||
while let Some(pair_state) = pair_states.get(i) {
|
||||
if pair_state.range.end.to_offset(buffer) < range.start {
|
||||
pair_states = &pair_states[i + 1..];
|
||||
i = 0;
|
||||
} else if pair_state.range.start.to_offset(buffer) > range.end {
|
||||
break;
|
||||
} else if pair_state.selection_id == selection.id {
|
||||
enclosing = Some(pair_state);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.change_selections(Some(Autoscroll::Fit), cx, |selections| {
|
||||
selections.select(new_selections)
|
||||
(selection.clone(), enclosing)
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove any autoclose regions that no longer contain their selection.
|
||||
fn invalidate_autoclose_regions(
|
||||
&mut self,
|
||||
mut selections: &[Selection<Anchor>],
|
||||
buffer: &MultiBufferSnapshot,
|
||||
) {
|
||||
self.autoclose_regions.retain(|state| {
|
||||
let mut i = 0;
|
||||
while let Some(selection) = selections.get(i) {
|
||||
if selection.end.cmp(&state.range.start, buffer).is_lt() {
|
||||
selections = &selections[1..];
|
||||
continue;
|
||||
}
|
||||
if selection.start.cmp(&state.range.end, buffer).is_gt() {
|
||||
break;
|
||||
}
|
||||
if selection.id == state.selection_id {
|
||||
return true;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
false
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
|
||||
@ -2909,51 +2909,47 @@ impl Editor {
|
||||
|
||||
pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
|
||||
self.transact(cx, |this, cx| {
|
||||
if !this.select_autoclose_pair(cx) {
|
||||
let mut selections = this.selections.all::<Point>(cx);
|
||||
if !this.selections.line_mode {
|
||||
let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
for selection in &mut selections {
|
||||
if selection.is_empty() {
|
||||
let old_head = selection.head();
|
||||
let mut new_head = movement::left(
|
||||
&display_map,
|
||||
old_head.to_display_point(&display_map),
|
||||
)
|
||||
.to_point(&display_map);
|
||||
if let Some((buffer, line_buffer_range)) = display_map
|
||||
.buffer_snapshot
|
||||
.buffer_line_for_row(old_head.row)
|
||||
{
|
||||
let indent_size =
|
||||
buffer.indent_size_for_line(line_buffer_range.start.row);
|
||||
let language_name =
|
||||
buffer.language().map(|language| language.name());
|
||||
let indent_len = match indent_size.kind {
|
||||
IndentKind::Space => {
|
||||
cx.global::<Settings>().tab_size(language_name.as_deref())
|
||||
}
|
||||
IndentKind::Tab => NonZeroU32::new(1).unwrap(),
|
||||
};
|
||||
if old_head.column <= indent_size.len && old_head.column > 0 {
|
||||
let indent_len = indent_len.get();
|
||||
new_head = cmp::min(
|
||||
new_head,
|
||||
Point::new(
|
||||
old_head.row,
|
||||
((old_head.column - 1) / indent_len) * indent_len,
|
||||
),
|
||||
);
|
||||
this.select_autoclose_pair(cx);
|
||||
let mut selections = this.selections.all::<Point>(cx);
|
||||
if !this.selections.line_mode {
|
||||
let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
for selection in &mut selections {
|
||||
if selection.is_empty() {
|
||||
let old_head = selection.head();
|
||||
let mut new_head =
|
||||
movement::left(&display_map, old_head.to_display_point(&display_map))
|
||||
.to_point(&display_map);
|
||||
if let Some((buffer, line_buffer_range)) = display_map
|
||||
.buffer_snapshot
|
||||
.buffer_line_for_row(old_head.row)
|
||||
{
|
||||
let indent_size =
|
||||
buffer.indent_size_for_line(line_buffer_range.start.row);
|
||||
let language_name = buffer.language().map(|language| language.name());
|
||||
let indent_len = match indent_size.kind {
|
||||
IndentKind::Space => {
|
||||
cx.global::<Settings>().tab_size(language_name.as_deref())
|
||||
}
|
||||
IndentKind::Tab => NonZeroU32::new(1).unwrap(),
|
||||
};
|
||||
if old_head.column <= indent_size.len && old_head.column > 0 {
|
||||
let indent_len = indent_len.get();
|
||||
new_head = cmp::min(
|
||||
new_head,
|
||||
Point::new(
|
||||
old_head.row,
|
||||
((old_head.column - 1) / indent_len) * indent_len,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
selection.set_head(new_head, SelectionGoal::None);
|
||||
}
|
||||
|
||||
selection.set_head(new_head, SelectionGoal::None);
|
||||
}
|
||||
}
|
||||
|
||||
this.change_selections(Some(Autoscroll::Fit), cx, |s| s.select(selections));
|
||||
}
|
||||
|
||||
this.change_selections(Some(Autoscroll::Fit), cx, |s| s.select(selections));
|
||||
this.insert("", cx);
|
||||
});
|
||||
}
|
||||
@ -3957,17 +3953,16 @@ impl Editor {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.transact(cx, |this, cx| {
|
||||
if !this.select_autoclose_pair(cx) {
|
||||
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
||||
let line_mode = s.line_mode;
|
||||
s.move_with(|map, selection| {
|
||||
if selection.is_empty() && !line_mode {
|
||||
let cursor = movement::previous_word_start(map, selection.head());
|
||||
selection.set_head(cursor, SelectionGoal::None);
|
||||
}
|
||||
});
|
||||
this.select_autoclose_pair(cx);
|
||||
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
||||
let line_mode = s.line_mode;
|
||||
s.move_with(|map, selection| {
|
||||
if selection.is_empty() && !line_mode {
|
||||
let cursor = movement::previous_word_start(map, selection.head());
|
||||
selection.set_head(cursor, SelectionGoal::None);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
this.insert("", cx);
|
||||
});
|
||||
}
|
||||
@ -3978,17 +3973,16 @@ impl Editor {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.transact(cx, |this, cx| {
|
||||
if !this.select_autoclose_pair(cx) {
|
||||
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
||||
let line_mode = s.line_mode;
|
||||
s.move_with(|map, selection| {
|
||||
if selection.is_empty() && !line_mode {
|
||||
let cursor = movement::previous_subword_start(map, selection.head());
|
||||
selection.set_head(cursor, SelectionGoal::None);
|
||||
}
|
||||
});
|
||||
this.select_autoclose_pair(cx);
|
||||
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
||||
let line_mode = s.line_mode;
|
||||
s.move_with(|map, selection| {
|
||||
if selection.is_empty() && !line_mode {
|
||||
let cursor = movement::previous_subword_start(map, selection.head());
|
||||
selection.set_head(cursor, SelectionGoal::None);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
this.insert("", cx);
|
||||
});
|
||||
}
|
||||
@ -6495,12 +6489,6 @@ impl<T> DerefMut for InvalidationStack<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl InvalidationRegion for BracketPairState {
|
||||
fn ranges(&self) -> &[Range<Anchor>] {
|
||||
&self.ranges
|
||||
}
|
||||
}
|
||||
|
||||
impl InvalidationRegion for SnippetState {
|
||||
fn ranges(&self) -> &[Range<Anchor>] {
|
||||
&self.ranges[self.active_index]
|
||||
|
@ -1,6 +1,6 @@
|
||||
name = "HTML"
|
||||
path_suffixes = ["html"]
|
||||
autoclose_before = ">"
|
||||
autoclose_before = ">})"
|
||||
brackets = [
|
||||
{ start = "<", end = ">", close = true, newline = true },
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
|
Loading…
Reference in New Issue
Block a user