editor: Defer the effects of change_selections
to end of transact
(#31731)
In quite a few places the selection is changed multiple times in a transaction. For example, `backspace` might do it 3 times: * `select_autoclose_pair` * selection of the ranges to delete * `insert` of empty string also updates selection Before this change, each of these selection changes appended to selection history and did a bunch of work that's only relevant to selections the user actually sees. So for each backspace, `editor::UndoSelection` would need to be invoked 3-4 times before the cursor actually moves. It still needs to be run twice after this change, but that is a separate issue. Signature help even had a `backspace_pressed: bool` as an incomplete workaround, to avoid it flickering due to the selection switching between being a range and being cursor-like. The original motivation for this change is work I'm doing on not re-querying completions when the language server provides a response that has `is_incomplete: false`. Whether the menu is still visible is determined by the cursor position, and this was complicated by it seeing `backspace` temporarily moving the head of the selection 1 character to the left. This change also removes some redundant uses of `push_to_selection_history`. Not super stoked with the name `DeferredSelectionEffectsState`. Naming is hard. Release Notes: - N/A
This commit is contained in:
parent
1445af559b
commit
d7f0241d7b
3 changed files with 108 additions and 48 deletions
|
@ -936,6 +936,8 @@ pub struct Editor {
|
||||||
select_next_state: Option<SelectNextState>,
|
select_next_state: Option<SelectNextState>,
|
||||||
select_prev_state: Option<SelectNextState>,
|
select_prev_state: Option<SelectNextState>,
|
||||||
selection_history: SelectionHistory,
|
selection_history: SelectionHistory,
|
||||||
|
defer_selection_effects: bool,
|
||||||
|
deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
|
||||||
autoclose_regions: Vec<AutocloseRegion>,
|
autoclose_regions: Vec<AutocloseRegion>,
|
||||||
snippet_stack: InvalidationStack<SnippetState>,
|
snippet_stack: InvalidationStack<SnippetState>,
|
||||||
select_syntax_node_history: SelectSyntaxNodeHistory,
|
select_syntax_node_history: SelectSyntaxNodeHistory,
|
||||||
|
@ -1195,6 +1197,14 @@ impl Default for SelectionHistoryMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct DeferredSelectionEffectsState {
|
||||||
|
changed: bool,
|
||||||
|
show_completions: bool,
|
||||||
|
autoscroll: Option<Autoscroll>,
|
||||||
|
old_cursor_position: Anchor,
|
||||||
|
history_entry: SelectionHistoryEntry,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct SelectionHistory {
|
struct SelectionHistory {
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
|
@ -1791,6 +1801,8 @@ impl Editor {
|
||||||
select_next_state: None,
|
select_next_state: None,
|
||||||
select_prev_state: None,
|
select_prev_state: None,
|
||||||
selection_history: SelectionHistory::default(),
|
selection_history: SelectionHistory::default(),
|
||||||
|
defer_selection_effects: false,
|
||||||
|
deferred_selection_effects_state: None,
|
||||||
autoclose_regions: Vec::new(),
|
autoclose_regions: Vec::new(),
|
||||||
snippet_stack: InvalidationStack::default(),
|
snippet_stack: InvalidationStack::default(),
|
||||||
select_syntax_node_history: SelectSyntaxNodeHistory::default(),
|
select_syntax_node_history: SelectSyntaxNodeHistory::default(),
|
||||||
|
@ -2954,6 +2966,9 @@ impl Editor {
|
||||||
Subscription::join(other_subscription, this_subscription)
|
Subscription::join(other_subscription, this_subscription)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Changes selections using the provided mutation function. Changes to `self.selections` occur
|
||||||
|
/// immediately, but when run within `transact` or `with_selection_effects_deferred` other
|
||||||
|
/// effects of selection change occur at the end of the transaction.
|
||||||
pub fn change_selections<R>(
|
pub fn change_selections<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
autoscroll: Option<Autoscroll>,
|
autoscroll: Option<Autoscroll>,
|
||||||
|
@ -2961,39 +2976,105 @@ impl Editor {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
|
change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
|
||||||
) -> R {
|
) -> R {
|
||||||
self.change_selections_inner(autoscroll, true, window, cx, change)
|
self.change_selections_inner(true, autoscroll, window, cx, change)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change_selections_inner<R>(
|
pub(crate) fn change_selections_without_showing_completions<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
autoscroll: Option<Autoscroll>,
|
autoscroll: Option<Autoscroll>,
|
||||||
request_completions: bool,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
|
change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
|
||||||
) -> R {
|
) -> R {
|
||||||
let old_cursor_position = self.selections.newest_anchor().head();
|
self.change_selections_inner(false, autoscroll, window, cx, change)
|
||||||
self.push_to_selection_history();
|
}
|
||||||
|
|
||||||
|
fn change_selections_inner<R>(
|
||||||
|
&mut self,
|
||||||
|
show_completions: bool,
|
||||||
|
autoscroll: Option<Autoscroll>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
|
||||||
|
) -> R {
|
||||||
|
if let Some(state) = &mut self.deferred_selection_effects_state {
|
||||||
|
state.autoscroll = autoscroll.or(state.autoscroll);
|
||||||
|
state.show_completions = show_completions;
|
||||||
|
let (changed, result) = self.selections.change_with(cx, change);
|
||||||
|
state.changed |= changed;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
let mut state = DeferredSelectionEffectsState {
|
||||||
|
changed: false,
|
||||||
|
show_completions,
|
||||||
|
autoscroll,
|
||||||
|
old_cursor_position: self.selections.newest_anchor().head(),
|
||||||
|
history_entry: SelectionHistoryEntry {
|
||||||
|
selections: self.selections.disjoint_anchors(),
|
||||||
|
select_next_state: self.select_next_state.clone(),
|
||||||
|
select_prev_state: self.select_prev_state.clone(),
|
||||||
|
add_selections_state: self.add_selections_state.clone(),
|
||||||
|
},
|
||||||
|
};
|
||||||
let (changed, result) = self.selections.change_with(cx, change);
|
let (changed, result) = self.selections.change_with(cx, change);
|
||||||
|
state.changed = state.changed || changed;
|
||||||
|
if self.defer_selection_effects {
|
||||||
|
self.deferred_selection_effects_state = Some(state);
|
||||||
|
} else {
|
||||||
|
self.apply_selection_effects(state, window, cx);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
if changed {
|
/// Defers the effects of selection change, so that the effects of multiple calls to
|
||||||
if let Some(autoscroll) = autoscroll {
|
/// `change_selections` are applied at the end. This way these intermediate states aren't added
|
||||||
|
/// to selection history and the state of popovers based on selection position aren't
|
||||||
|
/// erroneously updated.
|
||||||
|
pub fn with_selection_effects_deferred<R>(
|
||||||
|
&mut self,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
|
||||||
|
) -> R {
|
||||||
|
let already_deferred = self.defer_selection_effects;
|
||||||
|
self.defer_selection_effects = true;
|
||||||
|
let result = update(self, window, cx);
|
||||||
|
if !already_deferred {
|
||||||
|
self.defer_selection_effects = false;
|
||||||
|
if let Some(state) = self.deferred_selection_effects_state.take() {
|
||||||
|
self.apply_selection_effects(state, window, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_selection_effects(
|
||||||
|
&mut self,
|
||||||
|
state: DeferredSelectionEffectsState,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
if state.changed {
|
||||||
|
self.selection_history.push(state.history_entry);
|
||||||
|
|
||||||
|
if let Some(autoscroll) = state.autoscroll {
|
||||||
self.request_autoscroll(autoscroll, cx);
|
self.request_autoscroll(autoscroll, cx);
|
||||||
}
|
}
|
||||||
self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
|
|
||||||
|
|
||||||
if self.should_open_signature_help_automatically(
|
let old_cursor_position = &state.old_cursor_position;
|
||||||
|
|
||||||
|
self.selections_did_change(
|
||||||
|
true,
|
||||||
&old_cursor_position,
|
&old_cursor_position,
|
||||||
self.signature_help_state.backspace_pressed(),
|
state.show_completions,
|
||||||
|
window,
|
||||||
cx,
|
cx,
|
||||||
) {
|
);
|
||||||
|
|
||||||
|
if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
|
||||||
self.show_signature_help(&ShowSignatureHelp, window, cx);
|
self.show_signature_help(&ShowSignatureHelp, window, cx);
|
||||||
}
|
}
|
||||||
self.signature_help_state.set_backspace_pressed(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
|
pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
|
||||||
|
@ -3877,9 +3958,12 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
let had_active_inline_completion = this.has_active_inline_completion();
|
let had_active_inline_completion = this.has_active_inline_completion();
|
||||||
this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
|
this.change_selections_without_showing_completions(
|
||||||
s.select(new_selections)
|
Some(Autoscroll::fit()),
|
||||||
});
|
window,
|
||||||
|
cx,
|
||||||
|
|s| s.select(new_selections),
|
||||||
|
);
|
||||||
|
|
||||||
if !bracket_inserted {
|
if !bracket_inserted {
|
||||||
if let Some(on_type_format_task) =
|
if let Some(on_type_format_task) =
|
||||||
|
@ -9033,7 +9117,6 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.signature_help_state.set_backspace_pressed(true);
|
|
||||||
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||||
s.select(selections)
|
s.select(selections)
|
||||||
});
|
});
|
||||||
|
@ -12755,7 +12838,6 @@ impl Editor {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
|
self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
|
||||||
|
|
||||||
self.push_to_selection_history();
|
|
||||||
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));
|
||||||
|
|
||||||
self.select_next_match_internal(&display_map, false, None, window, cx)?;
|
self.select_next_match_internal(&display_map, false, None, window, cx)?;
|
||||||
|
@ -12808,7 +12890,6 @@ impl Editor {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
|
self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
|
||||||
self.push_to_selection_history();
|
|
||||||
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));
|
||||||
self.select_next_match_internal(
|
self.select_next_match_internal(
|
||||||
&display_map,
|
&display_map,
|
||||||
|
@ -12827,7 +12908,6 @@ impl Editor {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
|
self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
|
||||||
self.push_to_selection_history();
|
|
||||||
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 buffer = &display_map.buffer_snapshot;
|
let buffer = &display_map.buffer_snapshot;
|
||||||
let mut selections = self.selections.all::<usize>(cx);
|
let mut selections = self.selections.all::<usize>(cx);
|
||||||
|
@ -15697,24 +15777,17 @@ impl Editor {
|
||||||
self.selections_did_change(false, &old_cursor_position, true, window, cx);
|
self.selections_did_change(false, &old_cursor_position, true, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_to_selection_history(&mut self) {
|
|
||||||
self.selection_history.push(SelectionHistoryEntry {
|
|
||||||
selections: self.selections.disjoint_anchors(),
|
|
||||||
select_next_state: self.select_next_state.clone(),
|
|
||||||
select_prev_state: self.select_prev_state.clone(),
|
|
||||||
add_selections_state: self.add_selections_state.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn transact(
|
pub fn transact(
|
||||||
&mut self,
|
&mut self,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
|
update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
|
||||||
) -> Option<TransactionId> {
|
) -> Option<TransactionId> {
|
||||||
self.start_transaction_at(Instant::now(), window, cx);
|
self.with_selection_effects_deferred(window, cx, |this, window, cx| {
|
||||||
update(self, window, cx);
|
this.start_transaction_at(Instant::now(), window, cx);
|
||||||
self.end_transaction_at(Instant::now(), cx)
|
update(this, window, cx);
|
||||||
|
this.end_transaction_at(Instant::now(), cx)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_transaction_at(
|
pub fn start_transaction_at(
|
||||||
|
|
|
@ -600,7 +600,7 @@ pub(crate) fn handle_from(
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.change_selections_inner(None, false, window, cx, |s| {
|
this.change_selections_without_showing_completions(None, window, cx, |s| {
|
||||||
s.select(base_selections);
|
s.select(base_selections);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
|
@ -74,8 +74,6 @@ impl Editor {
|
||||||
pub(super) fn should_open_signature_help_automatically(
|
pub(super) fn should_open_signature_help_automatically(
|
||||||
&mut self,
|
&mut self,
|
||||||
old_cursor_position: &Anchor,
|
old_cursor_position: &Anchor,
|
||||||
backspace_pressed: bool,
|
|
||||||
|
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if !(self.signature_help_state.is_shown() || self.auto_signature_help_enabled(cx)) {
|
if !(self.signature_help_state.is_shown() || self.auto_signature_help_enabled(cx)) {
|
||||||
|
@ -84,9 +82,7 @@ impl Editor {
|
||||||
let newest_selection = self.selections.newest::<usize>(cx);
|
let newest_selection = self.selections.newest::<usize>(cx);
|
||||||
let head = newest_selection.head();
|
let head = newest_selection.head();
|
||||||
|
|
||||||
// There are two cases where the head and tail of a selection are different: selecting multiple ranges and using backspace.
|
if !newest_selection.is_empty() && head != newest_selection.tail() {
|
||||||
// If we don’t exclude the backspace case, signature_help will blink every time backspace is pressed, so we need to prevent this.
|
|
||||||
if !newest_selection.is_empty() && !backspace_pressed && head != newest_selection.tail() {
|
|
||||||
self.signature_help_state
|
self.signature_help_state
|
||||||
.hide(SignatureHelpHiddenBy::Selection);
|
.hide(SignatureHelpHiddenBy::Selection);
|
||||||
return false;
|
return false;
|
||||||
|
@ -232,7 +228,6 @@ pub struct SignatureHelpState {
|
||||||
task: Option<Task<()>>,
|
task: Option<Task<()>>,
|
||||||
popover: Option<SignatureHelpPopover>,
|
popover: Option<SignatureHelpPopover>,
|
||||||
hidden_by: Option<SignatureHelpHiddenBy>,
|
hidden_by: Option<SignatureHelpHiddenBy>,
|
||||||
backspace_pressed: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignatureHelpState {
|
impl SignatureHelpState {
|
||||||
|
@ -254,14 +249,6 @@ impl SignatureHelpState {
|
||||||
self.popover.as_mut()
|
self.popover.as_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn backspace_pressed(&self) -> bool {
|
|
||||||
self.backspace_pressed
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_backspace_pressed(&mut self, backspace_pressed: bool) {
|
|
||||||
self.backspace_pressed = backspace_pressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_popover(&mut self, popover: SignatureHelpPopover) {
|
pub fn set_popover(&mut self, popover: SignatureHelpPopover) {
|
||||||
self.popover = Some(popover);
|
self.popover = Some(popover);
|
||||||
self.hidden_by = None;
|
self.hidden_by = None;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue