editor: Extract `fold` and `selection` out of `editor.rs` (#56070)

Mikhail Pertsev created

cc @SomeoneToIgnore

## Summary

Follow-up to #56030 

This mechanically extracts two editor topics into focused sibling
modules:

- `crates/editor/src/fold.rs`
- `crates/editor/src/selection.rs`

One odd boundary remains: several selection state types still live in
`editor.rs`. I didn't move them because those caused that "huge 11k
diff" in the previous PR, so I propose to move them later.

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- N/A

Change summary

crates/editor/src/editor.rs    |  996 -------------------------------
crates/editor/src/fold.rs      | 1095 ++++++++++++++++++++++++++++++++++++
crates/editor/src/selection.rs |  899 +++++++++++++++++++++++++++++
3 files changed, 2,019 insertions(+), 971 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -22,6 +22,7 @@ mod document_colors;
 mod document_symbols;
 mod editor_settings;
 mod element;
+mod fold;
 mod folding_ranges;
 mod git;
 mod highlight_matching_bracket;
@@ -62,6 +63,7 @@ mod completions;
 mod config;
 mod diagnostics;
 mod rewrap;
+mod selection;
 
 pub(crate) use actions::*;
 pub use code_actions::CodeActionProvider;
@@ -1444,13 +1446,6 @@ impl GutterDimensions {
     pub fn full_width(&self) -> Pixels {
         self.margin + self.width
     }
-
-    /// The width of the space reserved for the fold indicators,
-    /// use alongside 'justify_end' and `gutter_width` to
-    /// right align content with the line numbers
-    pub fn fold_area_width(&self) -> Pixels {
-        self.margin + self.right_padding
-    }
 }
 
 struct CharacterDimensions {
@@ -2784,30 +2779,6 @@ impl Editor {
             .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
     }
 
-    pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
-        if self
-            .selections
-            .pending_anchor()
-            .is_some_and(|pending_selection| {
-                let snapshot = self.buffer().read(cx).snapshot(cx);
-                pending_selection.range().includes(range, &snapshot)
-            })
-        {
-            return true;
-        }
-
-        self.selections
-            .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
-            .into_iter()
-            .any(|selection| {
-                // This is needed to cover a corner case, if we just check for an existing
-                // selection in the fold range, having a cursor at the start of the fold
-                // marks it as selected. Non-empty selections don't cause this.
-                let length = selection.end - selection.start;
-                length > 0
-            })
-    }
-
     pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
         self.key_context_internal(self.has_active_edit_prediction(), window, cx)
     }
@@ -3621,449 +3592,6 @@ impl Editor {
         self.use_modal_editing
     }
 
-    fn selections_did_change(
-        &mut self,
-        local: bool,
-        old_cursor_position: &Anchor,
-        effects: SelectionEffects,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.last_selection_from_search = effects.from_search;
-        window.invalidate_character_coordinates();
-
-        // Copy selections to primary selection buffer
-        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
-        if local {
-            let selections = self
-                .selections
-                .all::<MultiBufferOffset>(&self.display_snapshot(cx));
-            let buffer_handle = self.buffer.read(cx).read(cx);
-
-            let mut text = String::new();
-            for (index, selection) in selections.iter().enumerate() {
-                let text_for_selection = buffer_handle
-                    .text_for_range(selection.start..selection.end)
-                    .collect::<String>();
-
-                text.push_str(&text_for_selection);
-                if index != selections.len() - 1 {
-                    text.push('\n');
-                }
-            }
-
-            if !text.is_empty() {
-                cx.write_to_primary(ClipboardItem::new_string(text));
-            }
-        }
-
-        let selection_anchors = self.selections.disjoint_anchors_arc();
-
-        if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
-            self.buffer.update(cx, |buffer, cx| {
-                buffer.set_active_selections(
-                    &selection_anchors,
-                    self.selections.line_mode(),
-                    self.cursor_shape,
-                    cx,
-                )
-            });
-        }
-        let display_map = self
-            .display_map
-            .update(cx, |display_map, cx| display_map.snapshot(cx));
-        let buffer = display_map.buffer_snapshot();
-        if self.selections.count() == 1 {
-            self.add_selections_state = None;
-        }
-        self.select_next_state = None;
-        self.select_prev_state = None;
-        self.select_syntax_node_history.try_clear();
-        self.invalidate_autoclose_regions(&selection_anchors, buffer);
-        self.snippet_stack.invalidate(&selection_anchors, buffer);
-        self.take_rename(false, window, cx);
-
-        let newest_selection = self.selections.newest_anchor();
-        let new_cursor_position = newest_selection.head();
-        let selection_start = newest_selection.start;
-
-        if effects.nav_history.is_none() || effects.nav_history == Some(true) {
-            self.push_to_nav_history(
-                *old_cursor_position,
-                Some(new_cursor_position.to_point(buffer)),
-                false,
-                effects.nav_history == Some(true),
-                cx,
-            );
-        }
-
-        if local {
-            if let Some((anchor, _)) = buffer.anchor_to_buffer_anchor(new_cursor_position) {
-                self.register_buffer(anchor.buffer_id, cx);
-            }
-
-            let mut context_menu = self.context_menu.borrow_mut();
-            let completion_menu = match context_menu.as_ref() {
-                Some(CodeContextMenu::Completions(menu)) => Some(menu),
-                Some(CodeContextMenu::CodeActions(_)) => {
-                    *context_menu = None;
-                    None
-                }
-                None => None,
-            };
-            let completion_position = completion_menu.map(|menu| menu.initial_position);
-            drop(context_menu);
-
-            if effects.completions
-                && let Some(completion_position) = completion_position
-            {
-                let start_offset = selection_start.to_offset(buffer);
-                let position_matches = start_offset == completion_position.to_offset(buffer);
-                let continue_showing = if let Some((snap, ..)) =
-                    buffer.point_to_buffer_offset(completion_position)
-                    && !snap.capability.editable()
-                {
-                    false
-                } else if position_matches {
-                    if self.snippet_stack.is_empty() {
-                        buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
-                            == Some(CharKind::Word)
-                    } else {
-                        // Snippet choices can be shown even when the cursor is in whitespace.
-                        // Dismissing the menu with actions like backspace is handled by
-                        // invalidation regions.
-                        true
-                    }
-                } else {
-                    false
-                };
-
-                if continue_showing {
-                    self.open_or_update_completions_menu(None, None, false, window, cx);
-                } else {
-                    self.hide_context_menu(window, cx);
-                }
-            }
-
-            hide_hover(self, cx);
-
-            self.refresh_code_actions_for_selection(window, cx);
-            self.refresh_document_highlights(cx);
-            refresh_linked_ranges(self, window, cx);
-
-            self.refresh_selected_text_highlights(&display_map, false, window, cx);
-            self.refresh_matching_bracket_highlights(&display_map, cx);
-            self.refresh_outline_symbols_at_cursor(cx);
-            self.update_visible_edit_prediction(window, cx);
-            self.hide_blame_popover(true, cx);
-            if self.git_blame_inline_enabled {
-                self.start_inline_blame_timer(window, cx);
-            }
-        }
-
-        self.blink_manager.update(cx, BlinkManager::pause_blinking);
-
-        if local && !self.suppress_selection_callback {
-            if let Some(callback) = self.on_local_selections_changed.as_ref() {
-                let cursor_position = self.selections.newest::<Point>(&display_map).head();
-                callback(cursor_position, window, cx);
-            }
-        }
-
-        cx.emit(EditorEvent::SelectionsChanged { local });
-
-        let selections = &self.selections.disjoint_anchors_arc();
-        if local && let Some(buffer_snapshot) = buffer.as_singleton() {
-            let inmemory_selections = selections
-                .iter()
-                .map(|s| {
-                    let start = s.range().start.text_anchor_in(buffer_snapshot);
-                    let end = s.range().end.text_anchor_in(buffer_snapshot);
-                    (start..end).to_point(buffer_snapshot)
-                })
-                .collect();
-            self.update_restoration_data(cx, |data| {
-                data.selections = inmemory_selections;
-            });
-
-            if WorkspaceSettings::get(None, cx).restore_on_startup
-                != RestoreOnStartupBehavior::EmptyTab
-                && let Some(workspace_id) = self.workspace_serialization_id(cx)
-            {
-                let snapshot = self.buffer().read(cx).snapshot(cx);
-                let selections = selections.clone();
-                let background_executor = cx.background_executor().clone();
-                let editor_id = cx.entity().entity_id().as_u64() as ItemId;
-                let db = EditorDb::global(cx);
-                self.serialize_selections = cx.background_spawn(async move {
-                    background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
-                    let db_selections = selections
-                        .iter()
-                        .map(|selection| {
-                            (
-                                selection.start.to_offset(&snapshot).0,
-                                selection.end.to_offset(&snapshot).0,
-                            )
-                        })
-                        .collect();
-
-                    db.save_editor_selections(editor_id, workspace_id, db_selections)
-                        .await
-                        .with_context(|| {
-                            format!(
-                                "persisting editor selections for editor {editor_id}, \
-                                workspace {workspace_id:?}"
-                            )
-                        })
-                        .log_err();
-                });
-            }
-        }
-
-        cx.notify();
-    }
-
-    fn folds_did_change(&mut self, cx: &mut Context<Self>) {
-        use text::ToOffset as _;
-
-        if self.mode.is_minimap()
-            || WorkspaceSettings::get(None, cx).restore_on_startup
-                == RestoreOnStartupBehavior::EmptyTab
-        {
-            return;
-        }
-
-        let display_snapshot = self
-            .display_map
-            .update(cx, |display_map, cx| display_map.snapshot(cx));
-        let Some(buffer_snapshot) = display_snapshot.buffer_snapshot().as_singleton() else {
-            return;
-        };
-        let inmemory_folds = display_snapshot
-            .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
-            .map(|fold| {
-                let start = fold.range.start.text_anchor_in(buffer_snapshot);
-                let end = fold.range.end.text_anchor_in(buffer_snapshot);
-                (start..end).to_point(buffer_snapshot)
-            })
-            .collect();
-        self.update_restoration_data(cx, |data| {
-            data.folds = inmemory_folds;
-        });
-
-        let Some(workspace_id) = self.workspace_serialization_id(cx) else {
-            return;
-        };
-
-        // Get file path for path-based fold storage (survives tab close)
-        let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
-            project::File::from_dyn(buffer.read(cx).file())
-                .map(|file| Arc::<Path>::from(file.abs_path(cx)))
-        }) else {
-            return;
-        };
-
-        let background_executor = cx.background_executor().clone();
-        const FINGERPRINT_LEN: usize = 32;
-        let db_folds = display_snapshot
-            .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
-            .map(|fold| {
-                let start = fold
-                    .range
-                    .start
-                    .text_anchor_in(buffer_snapshot)
-                    .to_offset(buffer_snapshot);
-                let end = fold
-                    .range
-                    .end
-                    .text_anchor_in(buffer_snapshot)
-                    .to_offset(buffer_snapshot);
-
-                // Extract fingerprints - content at fold boundaries for validation on restore
-                // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
-                // content that might change independently.
-                // start_fp: first min(32, fold_len) bytes of fold content
-                // end_fp: last min(32, fold_len) bytes of fold content
-                // Clip to character boundaries to handle multibyte UTF-8 characters.
-                let fold_len = end - start;
-                let start_fp_end = buffer_snapshot
-                    .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
-                let start_fp: String = buffer_snapshot
-                    .text_for_range(start..start_fp_end)
-                    .collect();
-                let end_fp_start = buffer_snapshot
-                    .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
-                let end_fp: String = buffer_snapshot.text_for_range(end_fp_start..end).collect();
-
-                (start, end, start_fp, end_fp)
-            })
-            .collect::<Vec<_>>();
-        let db = EditorDb::global(cx);
-        self.serialize_folds = cx.background_spawn(async move {
-            background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
-            if db_folds.is_empty() {
-                // No folds - delete any persisted folds for this file
-                db.delete_file_folds(workspace_id, file_path)
-                    .await
-                    .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
-                    .log_err();
-            } else {
-                db.save_file_folds(workspace_id, file_path, db_folds)
-                    .await
-                    .with_context(|| {
-                        format!("persisting file folds for workspace {workspace_id:?}")
-                    })
-                    .log_err();
-            }
-        });
-    }
-
-    pub fn sync_selections(
-        &mut self,
-        other: Entity<Editor>,
-        cx: &mut Context<Self>,
-    ) -> gpui::Subscription {
-        let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
-        if !other_selections.is_empty() {
-            self.selections
-                .change_with(&self.display_snapshot(cx), |selections| {
-                    selections.select_anchors(other_selections);
-                });
-        }
-
-        let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
-            if let EditorEvent::SelectionsChanged { local: true } = other_evt {
-                let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
-                if other_selections.is_empty() {
-                    return;
-                }
-                let snapshot = this.display_snapshot(cx);
-                this.selections.change_with(&snapshot, |selections| {
-                    selections.select_anchors(other_selections);
-                });
-            }
-        });
-
-        let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
-            if let EditorEvent::SelectionsChanged { local: true } = this_evt {
-                let these_selections = this.selections.disjoint_anchors().to_vec();
-                if these_selections.is_empty() {
-                    return;
-                }
-                other.update(cx, |other_editor, cx| {
-                    let snapshot = other_editor.display_snapshot(cx);
-                    other_editor
-                        .selections
-                        .change_with(&snapshot, |selections| {
-                            selections.select_anchors(these_selections);
-                        })
-                });
-            }
-        });
-
-        Subscription::join(other_subscription, this_subscription)
-    }
-
-    fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
-        if self.buffer().read(cx).is_singleton() {
-            return;
-        }
-        let snapshot = self.buffer.read(cx).snapshot(cx);
-        let buffer_ids: HashSet<BufferId> = self
-            .selections
-            .disjoint_anchor_ranges()
-            .flat_map(|range| snapshot.buffer_ids_for_range(range))
-            .collect();
-        for buffer_id in buffer_ids {
-            self.unfold_buffer(buffer_id, cx);
-        }
-    }
-
-    /// 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>(
-        &mut self,
-        effects: SelectionEffects,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-        change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
-    ) -> R {
-        let snapshot = self.display_snapshot(cx);
-        if let Some(state) = &mut self.deferred_selection_effects_state {
-            state.effects.scroll = effects.scroll.or(state.effects.scroll);
-            state.effects.completions = effects.completions;
-            state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
-            let (changed, result) = self.selections.change_with(&snapshot, change);
-            state.changed |= changed;
-            return result;
-        }
-        let mut state = DeferredSelectionEffectsState {
-            changed: false,
-            effects,
-            old_cursor_position: self.selections.newest_anchor().head(),
-            history_entry: SelectionHistoryEntry {
-                selections: self.selections.disjoint_anchors_arc(),
-                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(&snapshot, 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
-    }
-
-    /// Defers the effects of selection change, so that the effects of multiple calls to
-    /// `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.effects.scroll {
-                self.request_autoscroll(autoscroll, cx);
-            }
-
-            let old_cursor_position = &state.old_cursor_position;
-
-            self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
-
-            if self.should_open_signature_help_automatically(old_cursor_position, cx) {
-                self.show_signature_help_auto(window, cx);
-            }
-        }
-    }
-
     pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
     where
         I: IntoIterator<Item = (Range<S>, T)>,
@@ -4118,515 +3646,41 @@ impl Editor {
         });
     }
 
-    fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
-        self.hide_context_menu(window, cx);
+    pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
+        self.selection_mark_mode = false;
+        self.selection_drag_state = SelectionDragState::None;
 
-        match phase {
-            SelectPhase::Begin {
-                position,
-                add,
-                click_count,
-            } => self.begin_selection(position, add, click_count, window, cx),
-            SelectPhase::BeginColumnar {
-                position,
-                goal_column,
-                reset,
-                mode,
-            } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
-            SelectPhase::Extend {
-                position,
-                click_count,
-            } => self.extend_selection(position, click_count, window, cx),
-            SelectPhase::Update {
-                position,
-                goal_column,
-                scroll_delta,
-            } => self.update_selection(position, goal_column, scroll_delta, window, cx),
-            SelectPhase::End => self.end_selection(window, cx),
+        if self.dismiss_menus_and_popups(true, window, cx) {
+            cx.notify();
+            return;
         }
-    }
-
-    fn extend_selection(
-        &mut self,
-        position: DisplayPoint,
-        click_count: usize,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let tail = self
-            .selections
-            .newest::<MultiBufferOffset>(&display_map)
-            .tail();
-        let click_count = click_count.max(match self.selections.select_mode() {
-            SelectMode::Character => 1,
-            SelectMode::Word(_) => 2,
-            SelectMode::Line(_) => 3,
-            SelectMode::All => 4,
-        });
-        self.begin_selection(position, false, click_count, window, cx);
-
-        let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
-
-        let current_selection = match self.selections.select_mode() {
-            SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
-            SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
-        };
-
-        let mut pending_selection = self
-            .selections
-            .pending_anchor()
-            .cloned()
-            .expect("extend_selection not called with pending selection");
-
-        if pending_selection
-            .start
-            .cmp(&current_selection.start, display_map.buffer_snapshot())
-            == Ordering::Greater
-        {
-            pending_selection.start = current_selection.start;
+        if self.clear_expanded_diff_hunks(cx) {
+            cx.notify();
+            return;
         }
-        if pending_selection
-            .end
-            .cmp(&current_selection.end, display_map.buffer_snapshot())
-            == Ordering::Less
-        {
-            pending_selection.end = current_selection.end;
-            pending_selection.reversed = true;
+        if self.show_git_blame_gutter {
+            self.show_git_blame_gutter = false;
+            cx.notify();
+            return;
         }
 
-        let mut pending_mode = self.selections.pending_mode().unwrap();
-        match &mut pending_mode {
-            SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
-            _ => {}
+        if self.mode.is_full()
+            && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
+        {
+            cx.notify();
+            return;
         }
 
-        let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
-            SelectionEffects::scroll(Autoscroll::fit())
-        } else {
-            SelectionEffects::no_scroll()
-        };
-
-        self.change_selections(effects, window, cx, |s| {
-            s.set_pending(pending_selection.clone(), pending_mode);
-            s.set_is_extending(true);
-        });
+        cx.propagate();
     }
 
-    fn begin_selection(
+    pub fn dismiss_menus_and_popups(
         &mut self,
-        position: DisplayPoint,
-        add: bool,
-        click_count: usize,
+        is_user_requested: bool,
         window: &mut Window,
         cx: &mut Context<Self>,
-    ) {
-        if !self.focus_handle.is_focused(window) {
-            self.last_focused_descendant = None;
-            window.focus(&self.focus_handle, cx);
-        }
-
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let buffer = display_map.buffer_snapshot();
-        let position = display_map.clip_point(position, Bias::Left);
-
-        let start;
-        let end;
-        let mode;
-        let mut auto_scroll;
-        match click_count {
-            1 => {
-                start = buffer.anchor_before(position.to_point(&display_map));
-                end = start;
-                mode = SelectMode::Character;
-                auto_scroll = true;
-            }
-            2 => {
-                let position = display_map
-                    .clip_point(position, Bias::Left)
-                    .to_offset(&display_map, Bias::Left);
-                let (range, _) = buffer.surrounding_word(position, None);
-                start = buffer.anchor_before(range.start);
-                end = buffer.anchor_before(range.end);
-                mode = SelectMode::Word(start..end);
-                auto_scroll = true;
-            }
-            3 => {
-                let position = display_map
-                    .clip_point(position, Bias::Left)
-                    .to_point(&display_map);
-                let line_start = display_map.prev_line_boundary(position).0;
-                let next_line_start = buffer.clip_point(
-                    display_map.next_line_boundary(position).0 + Point::new(1, 0),
-                    Bias::Left,
-                );
-                start = buffer.anchor_before(line_start);
-                end = buffer.anchor_before(next_line_start);
-                mode = SelectMode::Line(start..end);
-                auto_scroll = true;
-            }
-            _ => {
-                start = buffer.anchor_before(MultiBufferOffset(0));
-                end = buffer.anchor_before(buffer.len());
-                mode = SelectMode::All;
-                auto_scroll = false;
-            }
-        }
-        auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
-
-        let point_to_delete: Option<usize> = {
-            let selected_points: Vec<Selection<Point>> =
-                self.selections.disjoint_in_range(start..end, &display_map);
-
-            if !add || click_count > 1 {
-                None
-            } else if !selected_points.is_empty() {
-                Some(selected_points[0].id)
-            } else {
-                let clicked_point_already_selected =
-                    self.selections.disjoint_anchors().iter().find(|selection| {
-                        selection.start.to_point(buffer) == start.to_point(buffer)
-                            || selection.end.to_point(buffer) == end.to_point(buffer)
-                    });
-
-                clicked_point_already_selected.map(|selection| selection.id)
-            }
-        };
-
-        let selections_count = self.selections.count();
-        let effects = if auto_scroll {
-            SelectionEffects::default()
-        } else {
-            SelectionEffects::no_scroll()
-        };
-
-        self.change_selections(effects, window, cx, |s| {
-            if let Some(point_to_delete) = point_to_delete {
-                s.delete(point_to_delete);
-
-                if selections_count == 1 {
-                    s.set_pending_anchor_range(start..end, mode);
-                }
-            } else {
-                if !add {
-                    s.clear_disjoint();
-                }
-
-                s.set_pending_anchor_range(start..end, mode);
-            }
-        });
-    }
-
-    fn begin_columnar_selection(
-        &mut self,
-        position: DisplayPoint,
-        goal_column: u32,
-        reset: bool,
-        mode: ColumnarMode,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        if !self.focus_handle.is_focused(window) {
-            self.last_focused_descendant = None;
-            window.focus(&self.focus_handle, cx);
-        }
-
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-
-        if reset {
-            let pointer_position = display_map
-                .buffer_snapshot()
-                .anchor_before(position.to_point(&display_map));
-
-            self.change_selections(
-                SelectionEffects::scroll(Autoscroll::newest()),
-                window,
-                cx,
-                |s| {
-                    s.clear_disjoint();
-                    s.set_pending_anchor_range(
-                        pointer_position..pointer_position,
-                        SelectMode::Character,
-                    );
-                },
-            );
-        };
-
-        let tail = self.selections.newest::<Point>(&display_map).tail();
-        let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
-        self.columnar_selection_state = match mode {
-            ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
-                selection_tail: selection_anchor,
-                display_point: if reset {
-                    if position.column() != goal_column {
-                        Some(DisplayPoint::new(position.row(), goal_column))
-                    } else {
-                        None
-                    }
-                } else {
-                    None
-                },
-            }),
-            ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
-                selection_tail: selection_anchor,
-            }),
-        };
-
-        if !reset {
-            self.select_columns(position, goal_column, &display_map, window, cx);
-        }
-    }
-
-    fn update_selection(
-        &mut self,
-        position: DisplayPoint,
-        goal_column: u32,
-        scroll_delta: gpui::Point<f32>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-
-        if self.columnar_selection_state.is_some() {
-            self.select_columns(position, goal_column, &display_map, window, cx);
-        } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
-            let buffer = display_map.buffer_snapshot();
-            let head;
-            let tail;
-            let mode = self.selections.pending_mode().unwrap();
-            match &mode {
-                SelectMode::Character => {
-                    head = position.to_point(&display_map);
-                    tail = pending.tail().to_point(buffer);
-                }
-                SelectMode::Word(original_range) => {
-                    let offset = display_map
-                        .clip_point(position, Bias::Left)
-                        .to_offset(&display_map, Bias::Left);
-                    let original_range = original_range.to_offset(buffer);
-
-                    let head_offset = if buffer.is_inside_word(offset, None)
-                        || original_range.contains(&offset)
-                    {
-                        let (word_range, _) = buffer.surrounding_word(offset, None);
-                        if word_range.start < original_range.start {
-                            word_range.start
-                        } else {
-                            word_range.end
-                        }
-                    } else {
-                        offset
-                    };
-
-                    head = head_offset.to_point(buffer);
-                    if head_offset <= original_range.start {
-                        tail = original_range.end.to_point(buffer);
-                    } else {
-                        tail = original_range.start.to_point(buffer);
-                    }
-                }
-                SelectMode::Line(original_range) => {
-                    let original_range = original_range.to_point(display_map.buffer_snapshot());
-
-                    let position = display_map
-                        .clip_point(position, Bias::Left)
-                        .to_point(&display_map);
-                    let line_start = display_map.prev_line_boundary(position).0;
-                    let next_line_start = buffer.clip_point(
-                        display_map.next_line_boundary(position).0 + Point::new(1, 0),
-                        Bias::Left,
-                    );
-
-                    if line_start < original_range.start {
-                        head = line_start
-                    } else {
-                        head = next_line_start
-                    }
-
-                    if head <= original_range.start {
-                        tail = original_range.end;
-                    } else {
-                        tail = original_range.start;
-                    }
-                }
-                SelectMode::All => {
-                    return;
-                }
-            };
-
-            if head < tail {
-                pending.start = buffer.anchor_before(head);
-                pending.end = buffer.anchor_before(tail);
-                pending.reversed = true;
-            } else {
-                pending.start = buffer.anchor_before(tail);
-                pending.end = buffer.anchor_before(head);
-                pending.reversed = false;
-            }
-
-            self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-                s.set_pending(pending.clone(), mode);
-            });
-        } else {
-            log::error!("update_selection dispatched with no pending selection");
-            return;
-        }
-
-        self.apply_scroll_delta(scroll_delta, window, cx);
-        cx.notify();
-    }
-
-    fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        self.columnar_selection_state.take();
-        if let Some(pending_mode) = self.selections.pending_mode() {
-            let selections = self
-                .selections
-                .all::<MultiBufferOffset>(&self.display_snapshot(cx));
-            self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-                s.select(selections);
-                s.clear_pending();
-                if s.is_extending() {
-                    s.set_is_extending(false);
-                } else {
-                    s.set_select_mode(pending_mode);
-                }
-            });
-        }
-    }
-
-    fn select_columns(
-        &mut self,
-        head: DisplayPoint,
-        goal_column: u32,
-        display_map: &DisplaySnapshot,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
-            return;
-        };
-
-        let tail = match columnar_state {
-            ColumnarSelectionState::FromMouse {
-                selection_tail,
-                display_point,
-            } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
-            ColumnarSelectionState::FromSelection { selection_tail } => {
-                selection_tail.to_display_point(display_map)
-            }
-        };
-
-        let start_row = cmp::min(tail.row(), head.row());
-        let end_row = cmp::max(tail.row(), head.row());
-        let start_column = cmp::min(tail.column(), goal_column);
-        let end_column = cmp::max(tail.column(), goal_column);
-        let reversed = start_column < tail.column();
-
-        let selection_ranges = (start_row.0..=end_row.0)
-            .map(DisplayRow)
-            .filter_map(|row| {
-                if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
-                    || start_column <= display_map.line_len(row))
-                    && !display_map.is_block_line(row)
-                {
-                    let start = display_map
-                        .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
-                        .to_point(display_map);
-                    let end = display_map
-                        .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
-                        .to_point(display_map);
-                    if reversed {
-                        Some(end..start)
-                    } else {
-                        Some(start..end)
-                    }
-                } else {
-                    None
-                }
-            })
-            .collect::<Vec<_>>();
-        if selection_ranges.is_empty() {
-            return;
-        }
-
-        let ranges = match columnar_state {
-            ColumnarSelectionState::FromMouse { .. } => {
-                let mut non_empty_ranges = selection_ranges
-                    .iter()
-                    .filter(|selection_range| selection_range.start != selection_range.end)
-                    .peekable();
-                if non_empty_ranges.peek().is_some() {
-                    non_empty_ranges.cloned().collect()
-                } else {
-                    selection_ranges
-                }
-            }
-            _ => selection_ranges,
-        };
-
-        self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges(ranges);
-        });
-        cx.notify();
-    }
-
-    pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
-        self.selections
-            .all_adjusted(snapshot)
-            .iter()
-            .any(|selection| !selection.is_empty())
-    }
-
-    pub fn has_pending_nonempty_selection(&self) -> bool {
-        let pending_nonempty_selection = match self.selections.pending_anchor() {
-            Some(Selection { start, end, .. }) => start != end,
-            None => false,
-        };
-
-        pending_nonempty_selection
-            || (self.columnar_selection_state.is_some()
-                && self.selections.disjoint_anchors().len() > 1)
-    }
-
-    pub fn has_pending_selection(&self) -> bool {
-        self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
-    }
-
-    pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
-        self.selection_mark_mode = false;
-        self.selection_drag_state = SelectionDragState::None;
-
-        if self.dismiss_menus_and_popups(true, window, cx) {
-            cx.notify();
-            return;
-        }
-        if self.clear_expanded_diff_hunks(cx) {
-            cx.notify();
-            return;
-        }
-        if self.show_git_blame_gutter {
-            self.show_git_blame_gutter = false;
-            cx.notify();
-            return;
-        }
-
-        if self.mode.is_full()
-            && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
-        {
-            cx.notify();
-            return;
-        }
-
-        cx.propagate();
-    }
-
-    pub fn dismiss_menus_and_popups(
-        &mut self,
-        is_user_requested: bool,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> bool {
-        let mut dismissed = false;
+    ) -> bool {
+        let mut dismissed = false;
 
         dismissed |= self.take_rename(false, window, cx).is_some();
         dismissed |= self.hide_blame_popover(true, cx);

crates/editor/src/fold.rs 🔗

@@ -0,0 +1,1095 @@
+use super::*;
+
+impl GutterDimensions {
+    /// The width of the space reserved for the fold indicators,
+    /// use alongside 'justify_end' and `gutter_width` to
+    /// right align content with the line numbers
+    pub fn fold_area_width(&self) -> Pixels {
+        self.margin + self.right_padding
+    }
+}
+
+impl EditorSnapshot {
+    pub fn render_crease_toggle(
+        &self,
+        buffer_row: MultiBufferRow,
+        row_contains_cursor: bool,
+        editor: Entity<Editor>,
+        window: &mut Window,
+        cx: &mut App,
+    ) -> Option<AnyElement> {
+        let folded = self.is_line_folded(buffer_row);
+        let mut is_foldable = false;
+
+        if let Some(crease) = self
+            .crease_snapshot
+            .query_row(buffer_row, self.buffer_snapshot())
+        {
+            is_foldable = true;
+            match crease {
+                Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
+                    if let Some(render_toggle) = render_toggle {
+                        let toggle_callback =
+                            Arc::new(move |folded, window: &mut Window, cx: &mut App| {
+                                if folded {
+                                    editor.update(cx, |editor, cx| {
+                                        editor.fold_at(buffer_row, window, cx)
+                                    });
+                                } else {
+                                    editor.update(cx, |editor, cx| {
+                                        editor.unfold_at(buffer_row, window, cx)
+                                    });
+                                }
+                            });
+                        return Some((render_toggle)(
+                            buffer_row,
+                            folded,
+                            toggle_callback,
+                            window,
+                            cx,
+                        ));
+                    }
+                }
+            }
+        }
+
+        is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
+
+        if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
+            Some(
+                Disclosure::new(("gutter_crease", buffer_row.0), !folded)
+                    .toggle_state(folded)
+                    .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
+                        if folded {
+                            this.unfold_at(buffer_row, window, cx);
+                        } else {
+                            this.fold_at(buffer_row, window, cx);
+                        }
+                    }))
+                    .into_any_element(),
+            )
+        } else {
+            None
+        }
+    }
+
+    pub fn render_crease_trailer(
+        &self,
+        buffer_row: MultiBufferRow,
+        window: &mut Window,
+        cx: &mut App,
+    ) -> Option<AnyElement> {
+        let folded = self.is_line_folded(buffer_row);
+        if let Crease::Inline { render_trailer, .. } = self
+            .crease_snapshot
+            .query_row(buffer_row, self.buffer_snapshot())?
+        {
+            let render_trailer = render_trailer.as_ref()?;
+            Some(render_trailer(buffer_row, folded, window, cx))
+        } else {
+            None
+        }
+    }
+}
+
+impl Editor {
+    pub fn toggle_fold(
+        &mut self,
+        _: &actions::ToggleFold,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if self.buffer_kind(cx) == ItemBufferKind::Singleton {
+            let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+            let selection = self.selections.newest::<Point>(&display_map);
+
+            let range = if selection.is_empty() {
+                let point = selection.head().to_display_point(&display_map);
+                let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
+                let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
+                    .to_point(&display_map);
+                start..end
+            } else {
+                selection.range()
+            };
+            if display_map.folds_in_range(range).next().is_some() {
+                self.unfold_lines(&Default::default(), window, cx)
+            } else {
+                self.fold(&Default::default(), window, cx)
+            }
+        } else {
+            let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
+            let buffer_ids: HashSet<_> = self
+                .selections
+                .disjoint_anchor_ranges()
+                .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
+                .collect();
+
+            let should_unfold = buffer_ids
+                .iter()
+                .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
+
+            for buffer_id in buffer_ids {
+                if should_unfold {
+                    self.unfold_buffer(buffer_id, cx);
+                } else {
+                    self.fold_buffer(buffer_id, cx);
+                }
+            }
+        }
+    }
+
+    pub fn toggle_fold_recursive(
+        &mut self,
+        _: &actions::ToggleFoldRecursive,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
+
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+        let range = if selection.is_empty() {
+            let point = selection.head().to_display_point(&display_map);
+            let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
+            let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
+                .to_point(&display_map);
+            start..end
+        } else {
+            selection.range()
+        };
+        if display_map.folds_in_range(range).next().is_some() {
+            self.unfold_recursive(&Default::default(), window, cx)
+        } else {
+            self.fold_recursive(&Default::default(), window, cx)
+        }
+    }
+
+    pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
+        if self.buffer_kind(cx) == ItemBufferKind::Singleton {
+            let mut to_fold = Vec::new();
+            let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+            let selections = self.selections.all_adjusted(&display_map);
+
+            for selection in selections {
+                let range = selection.range().sorted();
+                let buffer_start_row = range.start.row;
+
+                if range.start.row != range.end.row {
+                    let mut found = false;
+                    let mut row = range.start.row;
+                    while row <= range.end.row {
+                        if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
+                        {
+                            found = true;
+                            row = crease.range().end.row + 1;
+                            to_fold.push(crease);
+                        } else {
+                            row += 1
+                        }
+                    }
+                    if found {
+                        continue;
+                    }
+                }
+
+                for row in (0..=range.start.row).rev() {
+                    if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
+                        && crease.range().end.row >= buffer_start_row
+                    {
+                        to_fold.push(crease);
+                        if row <= range.start.row {
+                            break;
+                        }
+                    }
+                }
+            }
+
+            self.fold_creases(to_fold, true, window, cx);
+        } else {
+            let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
+            let buffer_ids = self
+                .selections
+                .disjoint_anchor_ranges()
+                .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
+                .collect::<HashSet<_>>();
+            for buffer_id in buffer_ids {
+                self.fold_buffer(buffer_id, cx);
+            }
+        }
+    }
+
+    pub fn toggle_fold_all(
+        &mut self,
+        _: &actions::ToggleFoldAll,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let has_folds = if self.buffer.read(cx).is_singleton() {
+            let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+            let has_folds = display_map
+                .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
+                .next()
+                .is_some();
+            has_folds
+        } else {
+            let snapshot = self.buffer.read(cx).snapshot(cx);
+            let has_folds = snapshot
+                .all_buffer_ids()
+                .any(|buffer_id| self.is_buffer_folded(buffer_id, cx));
+            has_folds
+        };
+
+        if has_folds {
+            self.unfold_all(&actions::UnfoldAll, window, cx);
+        } else {
+            self.fold_all(&actions::FoldAll, window, cx);
+        }
+    }
+
+    pub fn fold_at_level_1(
+        &mut self,
+        _: &actions::FoldAtLevel1,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
+    }
+
+    pub fn fold_at_level_2(
+        &mut self,
+        _: &actions::FoldAtLevel2,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
+    }
+
+    pub fn fold_at_level_3(
+        &mut self,
+        _: &actions::FoldAtLevel3,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
+    }
+
+    pub fn fold_at_level_4(
+        &mut self,
+        _: &actions::FoldAtLevel4,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
+    }
+
+    pub fn fold_at_level_5(
+        &mut self,
+        _: &actions::FoldAtLevel5,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
+    }
+
+    pub fn fold_at_level_6(
+        &mut self,
+        _: &actions::FoldAtLevel6,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
+    }
+
+    pub fn fold_at_level_7(
+        &mut self,
+        _: &actions::FoldAtLevel7,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
+    }
+
+    pub fn fold_at_level_8(
+        &mut self,
+        _: &actions::FoldAtLevel8,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
+    }
+
+    pub fn fold_at_level_9(
+        &mut self,
+        _: &actions::FoldAtLevel9,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
+    }
+
+    pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
+        if self.buffer.read(cx).is_singleton() {
+            let mut fold_ranges = Vec::new();
+            let snapshot = self.buffer.read(cx).snapshot(cx);
+
+            for row in 0..snapshot.max_row().0 {
+                if let Some(foldable_range) = self
+                    .snapshot(window, cx)
+                    .crease_for_buffer_row(MultiBufferRow(row))
+                {
+                    fold_ranges.push(foldable_range);
+                }
+            }
+
+            self.fold_creases(fold_ranges, true, window, cx);
+        } else {
+            self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
+                editor
+                    .update_in(cx, |editor, _, cx| {
+                        let snapshot = editor.buffer.read(cx).snapshot(cx);
+                        for buffer_id in snapshot.all_buffer_ids() {
+                            editor.fold_buffer(buffer_id, cx);
+                        }
+                    })
+                    .ok();
+            });
+        }
+    }
+
+    pub fn fold_function_bodies(
+        &mut self,
+        _: &actions::FoldFunctionBodies,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let snapshot = self.buffer.read(cx).snapshot(cx);
+
+        let ranges = snapshot
+            .text_object_ranges(
+                MultiBufferOffset(0)..snapshot.len(),
+                TreeSitterOptions::default(),
+            )
+            .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
+            .collect::<Vec<_>>();
+
+        let creases = ranges
+            .into_iter()
+            .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
+            .collect();
+
+        self.fold_creases(creases, true, window, cx);
+    }
+
+    pub fn fold_recursive(
+        &mut self,
+        _: &actions::FoldRecursive,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let mut to_fold = Vec::new();
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+        let selections = self.selections.all_adjusted(&display_map);
+
+        for selection in selections {
+            let range = selection.range().sorted();
+            let buffer_start_row = range.start.row;
+
+            if range.start.row != range.end.row {
+                let mut found = false;
+                for row in range.start.row..=range.end.row {
+                    if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
+                        found = true;
+                        to_fold.push(crease);
+                    }
+                }
+                if found {
+                    continue;
+                }
+            }
+
+            for row in (0..=range.start.row).rev() {
+                if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
+                    if crease.range().end.row >= buffer_start_row {
+                        to_fold.push(crease);
+                    } else {
+                        break;
+                    }
+                }
+            }
+        }
+
+        self.fold_creases(to_fold, true, window, cx);
+    }
+
+    pub fn fold_at(
+        &mut self,
+        buffer_row: MultiBufferRow,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+
+        if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
+            let autoscroll = self
+                .selections
+                .all::<Point>(&display_map)
+                .iter()
+                .any(|selection| crease.range().overlaps(&selection.range()));
+
+            self.fold_creases(vec![crease], autoscroll, window, cx);
+        }
+    }
+
+    pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
+        if self.buffer_kind(cx) == ItemBufferKind::Singleton {
+            let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+            let buffer = display_map.buffer_snapshot();
+            let selections = self.selections.all::<Point>(&display_map);
+            let ranges = selections
+                .iter()
+                .map(|s| {
+                    let range = s.display_range(&display_map).sorted();
+                    let mut start = range.start.to_point(&display_map);
+                    let mut end = range.end.to_point(&display_map);
+                    start.column = 0;
+                    end.column = buffer.line_len(MultiBufferRow(end.row));
+                    start..end
+                })
+                .collect::<Vec<_>>();
+
+            self.unfold_ranges(&ranges, true, true, cx);
+        } else {
+            let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
+            let buffer_ids = self
+                .selections
+                .disjoint_anchor_ranges()
+                .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
+                .collect::<HashSet<_>>();
+            for buffer_id in buffer_ids {
+                self.unfold_buffer(buffer_id, cx);
+            }
+        }
+    }
+
+    pub fn unfold_recursive(
+        &mut self,
+        _: &UnfoldRecursive,
+        _window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+        let selections = self.selections.all::<Point>(&display_map);
+        let ranges = selections
+            .iter()
+            .map(|s| {
+                let mut range = s.display_range(&display_map).sorted();
+                *range.start.column_mut() = 0;
+                *range.end.column_mut() = display_map.line_len(range.end.row());
+                let start = range.start.to_point(&display_map);
+                let end = range.end.to_point(&display_map);
+                start..end
+            })
+            .collect::<Vec<_>>();
+
+        self.unfold_ranges(&ranges, true, true, cx);
+    }
+
+    pub fn unfold_at(
+        &mut self,
+        buffer_row: MultiBufferRow,
+        _window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+
+        let intersection_range = Point::new(buffer_row.0, 0)
+            ..Point::new(
+                buffer_row.0,
+                display_map.buffer_snapshot().line_len(buffer_row),
+            );
+
+        let autoscroll = self
+            .selections
+            .all::<Point>(&display_map)
+            .iter()
+            .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
+
+        self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
+    }
+
+    pub fn unfold_all(
+        &mut self,
+        _: &actions::UnfoldAll,
+        _window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if self.buffer.read(cx).is_singleton() {
+            let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+            self.unfold_ranges(
+                &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
+                true,
+                true,
+                cx,
+            );
+        } else {
+            self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
+                editor
+                    .update(cx, |editor, cx| {
+                        let snapshot = editor.buffer.read(cx).snapshot(cx);
+                        for buffer_id in snapshot.all_buffer_ids() {
+                            editor.unfold_buffer(buffer_id, cx);
+                        }
+                    })
+                    .ok();
+            });
+        }
+    }
+
+    pub fn fold_selected_ranges(
+        &mut self,
+        _: &FoldSelectedRanges,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+        let selections = self.selections.all_adjusted(&display_map);
+        let ranges = selections
+            .into_iter()
+            .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
+            .collect::<Vec<_>>();
+        self.fold_creases(ranges, true, window, cx);
+    }
+
+    pub fn fold_ranges<T: ToOffset + Clone>(
+        &mut self,
+        ranges: Vec<Range<T>>,
+        auto_scroll: bool,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+        let ranges = ranges
+            .into_iter()
+            .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
+            .collect::<Vec<_>>();
+        self.fold_creases(ranges, auto_scroll, window, cx);
+    }
+
+    pub fn fold_creases<T: ToOffset + Clone>(
+        &mut self,
+        creases: Vec<Crease<T>>,
+        auto_scroll: bool,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if creases.is_empty() {
+            return;
+        }
+
+        self.display_map.update(cx, |map, cx| map.fold(creases, cx));
+
+        if auto_scroll {
+            self.request_autoscroll(Autoscroll::fit(), cx);
+        }
+
+        cx.notify();
+
+        self.scrollbar_marker_state.dirty = true;
+        self.update_data_on_scroll(false, window, cx);
+        self.folds_did_change(cx);
+    }
+
+    /// Removes any folds whose ranges intersect any of the given ranges.
+    pub fn unfold_ranges<T: ToOffset + Clone>(
+        &mut self,
+        ranges: &[Range<T>],
+        inclusive: bool,
+        auto_scroll: bool,
+        cx: &mut Context<Self>,
+    ) {
+        self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
+            map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
+        });
+        self.folds_did_change(cx);
+    }
+
+    pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
+        self.fold_buffers([buffer_id], cx);
+    }
+
+    pub fn fold_buffers(
+        &mut self,
+        buffer_ids: impl IntoIterator<Item = BufferId>,
+        cx: &mut Context<Self>,
+    ) {
+        if self.buffer().read(cx).is_singleton() {
+            return;
+        }
+
+        let ids_to_fold: Vec<BufferId> = buffer_ids
+            .into_iter()
+            .filter(|id| !self.is_buffer_folded(*id, cx))
+            .collect();
+
+        if ids_to_fold.is_empty() {
+            return;
+        }
+
+        self.display_map.update(cx, |display_map, cx| {
+            display_map.fold_buffers(ids_to_fold.clone(), cx)
+        });
+
+        let snapshot = self.display_snapshot(cx);
+        self.selections.change_with(&snapshot, |selections| {
+            for buffer_id in ids_to_fold.iter().copied() {
+                selections.remove_selections_from_buffer(buffer_id);
+            }
+        });
+
+        cx.emit(EditorEvent::BufferFoldToggled {
+            ids: ids_to_fold,
+            folded: true,
+        });
+        cx.notify();
+    }
+
+    pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
+        if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
+            return;
+        }
+        self.display_map.update(cx, |display_map, cx| {
+            display_map.unfold_buffers([buffer_id], cx);
+        });
+        cx.emit(EditorEvent::BufferFoldToggled {
+            ids: vec![buffer_id],
+            folded: false,
+        });
+        cx.notify();
+    }
+
+    pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
+        self.display_map.read(cx).is_buffer_folded(buffer)
+    }
+
+    pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
+        if self.buffer().read(cx).is_singleton() {
+            return false;
+        }
+        !self.folded_buffers(cx).is_empty()
+    }
+
+    pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
+        self.display_map.read(cx).folded_buffers()
+    }
+
+    pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
+        self.display_map.update(cx, |display_map, cx| {
+            display_map.disable_header_for_buffer(buffer_id, cx);
+        });
+        cx.notify();
+    }
+
+    /// Removes any folds with the given ranges.
+    pub fn remove_folds_with_type<T: ToOffset + Clone>(
+        &mut self,
+        ranges: &[Range<T>],
+        type_id: TypeId,
+        auto_scroll: bool,
+        cx: &mut Context<Self>,
+    ) {
+        self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
+            map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
+        });
+        self.folds_did_change(cx);
+    }
+
+    pub fn update_renderer_widths(
+        &mut self,
+        widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
+        cx: &mut Context<Self>,
+    ) -> bool {
+        self.display_map
+            .update(cx, |map, cx| map.update_fold_widths(widths, cx))
+    }
+
+    pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
+        self.display_map.read(cx).fold_placeholder.clone()
+    }
+
+    pub fn insert_creases(
+        &mut self,
+        creases: impl IntoIterator<Item = Crease<Anchor>>,
+        cx: &mut Context<Self>,
+    ) -> Vec<CreaseId> {
+        self.display_map
+            .update(cx, |map, cx| map.insert_creases(creases, cx))
+    }
+
+    pub fn remove_creases(
+        &mut self,
+        ids: impl IntoIterator<Item = CreaseId>,
+        cx: &mut Context<Self>,
+    ) -> Vec<(CreaseId, Range<Anchor>)> {
+        self.display_map
+            .update(cx, |map, cx| map.remove_creases(ids, cx))
+    }
+
+    pub(super) fn fold_at_level(
+        &mut self,
+        fold_at: &FoldAtLevel,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if !self.buffer.read(cx).is_singleton() {
+            return;
+        }
+
+        let fold_at_level = fold_at.0;
+        let snapshot = self.buffer.read(cx).snapshot(cx);
+        let mut to_fold = Vec::new();
+        let mut stack = vec![(0, snapshot.max_row().0, 1)];
+
+        let row_ranges_to_keep: Vec<Range<u32>> = self
+            .selections
+            .all::<Point>(&self.display_snapshot(cx))
+            .into_iter()
+            .map(|sel| sel.start.row..sel.end.row)
+            .collect();
+
+        while let Some((mut start_row, end_row, current_level)) = stack.pop() {
+            while start_row < end_row {
+                match self
+                    .snapshot(window, cx)
+                    .crease_for_buffer_row(MultiBufferRow(start_row))
+                {
+                    Some(crease) => {
+                        let nested_start_row = crease.range().start.row + 1;
+                        let nested_end_row = crease.range().end.row;
+
+                        if current_level < fold_at_level {
+                            stack.push((nested_start_row, nested_end_row, current_level + 1));
+                        } else if current_level == fold_at_level {
+                            // Fold iff there is no selection completely contained within the fold region
+                            if !row_ranges_to_keep.iter().any(|selection| {
+                                selection.end >= nested_start_row
+                                    && selection.start <= nested_end_row
+                            }) {
+                                to_fold.push(crease);
+                            }
+                        }
+
+                        start_row = nested_end_row + 1;
+                    }
+                    None => start_row += 1,
+                }
+            }
+        }
+
+        self.fold_creases(to_fold, true, window, cx);
+    }
+
+    pub(super) fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
+        if self.buffer().read(cx).is_singleton() {
+            return;
+        }
+        let snapshot = self.buffer.read(cx).snapshot(cx);
+        let buffer_ids: HashSet<BufferId> = self
+            .selections
+            .disjoint_anchor_ranges()
+            .flat_map(|range| snapshot.buffer_ids_for_range(range))
+            .collect();
+        for buffer_id in buffer_ids {
+            self.unfold_buffer(buffer_id, cx);
+        }
+    }
+
+    pub(super) fn folds_did_change(&mut self, cx: &mut Context<Self>) {
+        use text::ToOffset as _;
+
+        if self.mode.is_minimap()
+            || WorkspaceSettings::get(None, cx).restore_on_startup
+                == RestoreOnStartupBehavior::EmptyTab
+        {
+            return;
+        }
+
+        let display_snapshot = self
+            .display_map
+            .update(cx, |display_map, cx| display_map.snapshot(cx));
+        let Some(buffer_snapshot) = display_snapshot.buffer_snapshot().as_singleton() else {
+            return;
+        };
+        let inmemory_folds = display_snapshot
+            .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
+            .map(|fold| {
+                let start = fold.range.start.text_anchor_in(buffer_snapshot);
+                let end = fold.range.end.text_anchor_in(buffer_snapshot);
+                (start..end).to_point(buffer_snapshot)
+            })
+            .collect();
+        self.update_restoration_data(cx, |data| {
+            data.folds = inmemory_folds;
+        });
+
+        let Some(workspace_id) = self.workspace_serialization_id(cx) else {
+            return;
+        };
+
+        // Get file path for path-based fold storage (survives tab close)
+        let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
+            project::File::from_dyn(buffer.read(cx).file())
+                .map(|file| Arc::<Path>::from(file.abs_path(cx)))
+        }) else {
+            return;
+        };
+
+        let background_executor = cx.background_executor().clone();
+        const FINGERPRINT_LEN: usize = 32;
+        let db_folds = display_snapshot
+            .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
+            .map(|fold| {
+                let start = fold
+                    .range
+                    .start
+                    .text_anchor_in(buffer_snapshot)
+                    .to_offset(buffer_snapshot);
+                let end = fold
+                    .range
+                    .end
+                    .text_anchor_in(buffer_snapshot)
+                    .to_offset(buffer_snapshot);
+
+                // Extract fingerprints - content at fold boundaries for validation on restore
+                // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
+                // content that might change independently.
+                // start_fp: first min(32, fold_len) bytes of fold content
+                // end_fp: last min(32, fold_len) bytes of fold content
+                // Clip to character boundaries to handle multibyte UTF-8 characters.
+                let fold_len = end - start;
+                let start_fp_end = buffer_snapshot
+                    .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
+                let start_fp: String = buffer_snapshot
+                    .text_for_range(start..start_fp_end)
+                    .collect();
+                let end_fp_start = buffer_snapshot
+                    .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
+                let end_fp: String = buffer_snapshot.text_for_range(end_fp_start..end).collect();
+
+                (start, end, start_fp, end_fp)
+            })
+            .collect::<Vec<_>>();
+        let db = EditorDb::global(cx);
+        self.serialize_folds = cx.background_spawn(async move {
+            background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
+            if db_folds.is_empty() {
+                // No folds - delete any persisted folds for this file
+                db.delete_file_folds(workspace_id, file_path)
+                    .await
+                    .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
+                    .log_err();
+            } else {
+                db.save_file_folds(workspace_id, file_path, db_folds)
+                    .await
+                    .with_context(|| {
+                        format!("persisting file folds for workspace {workspace_id:?}")
+                    })
+                    .log_err();
+            }
+        });
+    }
+
+    pub(super) fn refresh_single_line_folds(
+        &mut self,
+        window: &mut Window,
+        cx: &mut Context<Editor>,
+    ) {
+        struct NewlineFold;
+        let type_id = std::any::TypeId::of::<NewlineFold>();
+        if !self.mode.is_single_line() {
+            return;
+        }
+        let snapshot = self.snapshot(window, cx);
+        if snapshot.buffer_snapshot().max_point().row == 0 {
+            return;
+        }
+        let task = cx.background_spawn(async move {
+            let new_newlines = snapshot
+                .buffer_chars_at(MultiBufferOffset(0))
+                .filter_map(|(c, i)| {
+                    if c == '\n' {
+                        Some(
+                            snapshot.buffer_snapshot().anchor_after(i)
+                                ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
+                        )
+                    } else {
+                        None
+                    }
+                })
+                .collect::<Vec<_>>();
+            let existing_newlines = snapshot
+                .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
+                .filter_map(|fold| {
+                    if fold.placeholder.type_tag == Some(type_id) {
+                        Some(fold.range.start..fold.range.end)
+                    } else {
+                        None
+                    }
+                })
+                .collect::<Vec<_>>();
+
+            (new_newlines, existing_newlines)
+        });
+        self.folding_newlines = cx.spawn(async move |this, cx| {
+            let (new_newlines, existing_newlines) = task.await;
+            if new_newlines == existing_newlines {
+                return;
+            }
+            let placeholder = FoldPlaceholder {
+                render: Arc::new(move |_, _, cx| {
+                    div()
+                        .bg(cx.theme().status().hint_background)
+                        .border_b_1()
+                        .size_full()
+                        .font(ThemeSettings::get_global(cx).buffer_font.clone())
+                        .border_color(cx.theme().status().hint)
+                        .child("\\n")
+                        .into_any()
+                }),
+                constrain_width: false,
+                merge_adjacent: false,
+                type_tag: Some(type_id),
+                collapsed_text: None,
+            };
+            let creases = new_newlines
+                .into_iter()
+                .map(|range| Crease::simple(range, placeholder.clone()))
+                .collect();
+            this.update(cx, |this, cx| {
+                this.display_map.update(cx, |display_map, cx| {
+                    display_map.remove_folds_with_type(existing_newlines, type_id, cx);
+                    display_map.fold(creases, cx);
+                });
+            })
+            .ok();
+        });
+    }
+
+    /// Load folds from the file_folds database table by file path.
+    /// Used when manually opening a file that was previously closed.
+    pub(super) fn load_folds_from_db(
+        &mut self,
+        workspace_id: WorkspaceId,
+        file_path: PathBuf,
+        window: &mut Window,
+        cx: &mut Context<Editor>,
+    ) {
+        if self.mode.is_minimap()
+            || WorkspaceSettings::get(None, cx).restore_on_startup
+                == RestoreOnStartupBehavior::EmptyTab
+        {
+            return;
+        }
+
+        let Some(folds) = EditorDb::global(cx)
+            .get_file_folds(workspace_id, &file_path)
+            .log_err()
+        else {
+            return;
+        };
+        if folds.is_empty() {
+            return;
+        }
+
+        let snapshot = self.buffer.read(cx).snapshot(cx);
+        let snapshot_len = snapshot.len().0;
+
+        // Helper: search for fingerprint in buffer, return offset if found
+        let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
+            let search_start = snapshot
+                .clip_offset(MultiBufferOffset(search_start), Bias::Left)
+                .0;
+            let search_end = snapshot_len.saturating_sub(fingerprint.len());
+
+            let mut byte_offset = search_start;
+            for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
+                if byte_offset > search_end {
+                    break;
+                }
+                if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
+                    return Some(byte_offset);
+                }
+                byte_offset += ch.len_utf8();
+            }
+            None
+        };
+
+        let mut search_start = 0usize;
+
+        let valid_folds: Vec<_> = folds
+            .into_iter()
+            .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
+                let sfp = start_fp?;
+                let efp = end_fp?;
+                let efp_len = efp.len();
+
+                let start_matches = stored_start < snapshot_len
+                    && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
+                let efp_check_pos = stored_end.saturating_sub(efp_len);
+                let end_matches = efp_check_pos >= stored_start
+                    && stored_end <= snapshot_len
+                    && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
+
+                let (new_start, new_end) = if start_matches && end_matches {
+                    (stored_start, stored_end)
+                } else if sfp == efp {
+                    let new_start = find_fingerprint(&sfp, search_start)?;
+                    let fold_len = stored_end - stored_start;
+                    let new_end = new_start + fold_len;
+                    (new_start, new_end)
+                } else {
+                    let new_start = find_fingerprint(&sfp, search_start)?;
+                    let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
+                    let new_end = efp_pos + efp_len;
+                    (new_start, new_end)
+                };
+
+                search_start = new_end;
+
+                if new_end <= new_start {
+                    return None;
+                }
+
+                Some(
+                    snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
+                        ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
+                )
+            })
+            .collect();
+
+        if !valid_folds.is_empty() {
+            self.fold_ranges(valid_folds, false, window, cx);
+        }
+    }
+
+    fn remove_folds_with<T: ToOffset + Clone>(
+        &mut self,
+        ranges: &[Range<T>],
+        auto_scroll: bool,
+        cx: &mut Context<Self>,
+        update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
+    ) {
+        if ranges.is_empty() {
+            return;
+        }
+
+        self.display_map.update(cx, update);
+
+        if auto_scroll {
+            self.request_autoscroll(Autoscroll::fit(), cx);
+        }
+
+        cx.notify();
+        self.scrollbar_marker_state.dirty = true;
+        self.active_indent_guides_state.dirty = true;
+    }
+}

crates/editor/src/selection.rs 🔗

@@ -0,0 +1,899 @@
+use super::*;
+
+impl Editor {
+    pub fn sync_selections(
+        &mut self,
+        other: Entity<Editor>,
+        cx: &mut Context<Self>,
+    ) -> gpui::Subscription {
+        let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
+        if !other_selections.is_empty() {
+            self.selections
+                .change_with(&self.display_snapshot(cx), |selections| {
+                    selections.select_anchors(other_selections);
+                });
+        }
+
+        let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
+            if let EditorEvent::SelectionsChanged { local: true } = other_evt {
+                let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
+                if other_selections.is_empty() {
+                    return;
+                }
+                let snapshot = this.display_snapshot(cx);
+                this.selections.change_with(&snapshot, |selections| {
+                    selections.select_anchors(other_selections);
+                });
+            }
+        });
+
+        let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
+            if let EditorEvent::SelectionsChanged { local: true } = this_evt {
+                let these_selections = this.selections.disjoint_anchors().to_vec();
+                if these_selections.is_empty() {
+                    return;
+                }
+                other.update(cx, |other_editor, cx| {
+                    let snapshot = other_editor.display_snapshot(cx);
+                    other_editor
+                        .selections
+                        .change_with(&snapshot, |selections| {
+                            selections.select_anchors(these_selections);
+                        })
+                });
+            }
+        });
+
+        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>(
+        &mut self,
+        effects: SelectionEffects,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+        change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
+    ) -> R {
+        let snapshot = self.display_snapshot(cx);
+        if let Some(state) = &mut self.deferred_selection_effects_state {
+            state.effects.scroll = effects.scroll.or(state.effects.scroll);
+            state.effects.completions = effects.completions;
+            state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
+            let (changed, result) = self.selections.change_with(&snapshot, change);
+            state.changed |= changed;
+            return result;
+        }
+        let mut state = DeferredSelectionEffectsState {
+            changed: false,
+            effects,
+            old_cursor_position: self.selections.newest_anchor().head(),
+            history_entry: SelectionHistoryEntry {
+                selections: self.selections.disjoint_anchors_arc(),
+                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(&snapshot, 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
+    }
+
+    /// Defers the effects of selection change, so that the effects of multiple calls to
+    /// `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
+    }
+
+    pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
+        self.selections
+            .all_adjusted(snapshot)
+            .iter()
+            .any(|selection| !selection.is_empty())
+    }
+
+    pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
+        if self
+            .selections
+            .pending_anchor()
+            .is_some_and(|pending_selection| {
+                let snapshot = self.buffer().read(cx).snapshot(cx);
+                pending_selection.range().includes(range, &snapshot)
+            })
+        {
+            return true;
+        }
+
+        self.selections
+            .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
+            .into_iter()
+            .any(|selection| {
+                // This is needed to cover a corner case, if we just check for an existing
+                // selection in the fold range, having a cursor at the start of the fold
+                // marks it as selected. Non-empty selections don't cause this.
+                let length = selection.end - selection.start;
+                length > 0
+            })
+    }
+
+    pub fn has_pending_nonempty_selection(&self) -> bool {
+        let pending_nonempty_selection = match self.selections.pending_anchor() {
+            Some(Selection { start, end, .. }) => start != end,
+            None => false,
+        };
+
+        pending_nonempty_selection
+            || (self.columnar_selection_state.is_some()
+                && self.selections.disjoint_anchors().len() > 1)
+    }
+
+    pub fn has_pending_selection(&self) -> bool {
+        self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
+    }
+
+    pub fn set_selections_from_remote(
+        &mut self,
+        selections: Vec<Selection<Anchor>>,
+        pending_selection: Option<Selection<Anchor>>,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let old_cursor_position = self.selections.newest_anchor().head();
+        self.selections
+            .change_with(&self.display_snapshot(cx), |s| {
+                s.select_anchors(selections);
+                if let Some(pending_selection) = pending_selection {
+                    s.set_pending(pending_selection, SelectMode::Character);
+                } else {
+                    s.clear_pending();
+                }
+            });
+        self.selections_did_change(
+            false,
+            &old_cursor_position,
+            SelectionEffects::default(),
+            window,
+            cx,
+        );
+    }
+
+    pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
+        if self.selection_mark_mode {
+            self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
+                s.move_with(&mut |_, sel| {
+                    sel.collapse_to(sel.head(), SelectionGoal::None);
+                });
+            })
+        }
+        self.selection_mark_mode = true;
+        cx.notify();
+    }
+
+    pub fn swap_selection_ends(
+        &mut self,
+        _: &actions::SwapSelectionEnds,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
+            s.move_with(&mut |_, sel| {
+                if sel.start != sel.end {
+                    sel.reversed = !sel.reversed
+                }
+            });
+        });
+        self.request_autoscroll(Autoscroll::newest(), cx);
+        cx.notify();
+    }
+
+    pub(super) fn select(
+        &mut self,
+        phase: SelectPhase,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.hide_context_menu(window, cx);
+
+        match phase {
+            SelectPhase::Begin {
+                position,
+                add,
+                click_count,
+            } => self.begin_selection(position, add, click_count, window, cx),
+            SelectPhase::BeginColumnar {
+                position,
+                goal_column,
+                reset,
+                mode,
+            } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
+            SelectPhase::Extend {
+                position,
+                click_count,
+            } => self.extend_selection(position, click_count, window, cx),
+            SelectPhase::Update {
+                position,
+                goal_column,
+                scroll_delta,
+            } => self.update_selection(position, goal_column, scroll_delta, window, cx),
+            SelectPhase::End => self.end_selection(window, cx),
+        }
+    }
+
+    pub(super) fn extend_selection(
+        &mut self,
+        position: DisplayPoint,
+        click_count: usize,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+        let tail = self
+            .selections
+            .newest::<MultiBufferOffset>(&display_map)
+            .tail();
+        let click_count = click_count.max(match self.selections.select_mode() {
+            SelectMode::Character => 1,
+            SelectMode::Word(_) => 2,
+            SelectMode::Line(_) => 3,
+            SelectMode::All => 4,
+        });
+        self.begin_selection(position, false, click_count, window, cx);
+
+        let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
+
+        let current_selection = match self.selections.select_mode() {
+            SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
+            SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
+        };
+
+        let Some((mut pending_selection, mut pending_mode)) = self.pending_selection_and_mode()
+        else {
+            log::error!("extend_selection dispatched with no pending selection");
+            return;
+        };
+
+        if pending_selection
+            .start
+            .cmp(&current_selection.start, display_map.buffer_snapshot())
+            == Ordering::Greater
+        {
+            pending_selection.start = current_selection.start;
+        }
+        if pending_selection
+            .end
+            .cmp(&current_selection.end, display_map.buffer_snapshot())
+            == Ordering::Less
+        {
+            pending_selection.end = current_selection.end;
+            pending_selection.reversed = true;
+        }
+
+        match &mut pending_mode {
+            SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
+            _ => {}
+        }
+
+        let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
+            SelectionEffects::scroll(Autoscroll::fit())
+        } else {
+            SelectionEffects::no_scroll()
+        };
+
+        self.change_selections(effects, window, cx, |s| {
+            s.set_pending(pending_selection.clone(), pending_mode);
+            s.set_is_extending(true);
+        });
+    }
+
+    pub(super) fn begin_selection(
+        &mut self,
+        position: DisplayPoint,
+        add: bool,
+        click_count: usize,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if !self.focus_handle.is_focused(window) {
+            self.last_focused_descendant = None;
+            window.focus(&self.focus_handle, cx);
+        }
+
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+        let buffer = display_map.buffer_snapshot();
+        let position = display_map.clip_point(position, Bias::Left);
+
+        let start;
+        let end;
+        let mode;
+        let mut auto_scroll;
+        match click_count {
+            1 => {
+                start = buffer.anchor_before(position.to_point(&display_map));
+                end = start;
+                mode = SelectMode::Character;
+                auto_scroll = true;
+            }
+            2 => {
+                let position = display_map
+                    .clip_point(position, Bias::Left)
+                    .to_offset(&display_map, Bias::Left);
+                let (range, _) = buffer.surrounding_word(position, None);
+                start = buffer.anchor_before(range.start);
+                end = buffer.anchor_before(range.end);
+                mode = SelectMode::Word(start..end);
+                auto_scroll = true;
+            }
+            3 => {
+                let position = display_map
+                    .clip_point(position, Bias::Left)
+                    .to_point(&display_map);
+                let line_start = display_map.prev_line_boundary(position).0;
+                let next_line_start = buffer.clip_point(
+                    display_map.next_line_boundary(position).0 + Point::new(1, 0),
+                    Bias::Left,
+                );
+                start = buffer.anchor_before(line_start);
+                end = buffer.anchor_before(next_line_start);
+                mode = SelectMode::Line(start..end);
+                auto_scroll = true;
+            }
+            _ => {
+                start = buffer.anchor_before(MultiBufferOffset(0));
+                end = buffer.anchor_before(buffer.len());
+                mode = SelectMode::All;
+                auto_scroll = false;
+            }
+        }
+        auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
+
+        let point_to_delete: Option<usize> = {
+            let selected_points: Vec<Selection<Point>> =
+                self.selections.disjoint_in_range(start..end, &display_map);
+
+            if !add || click_count > 1 {
+                None
+            } else if !selected_points.is_empty() {
+                Some(selected_points[0].id)
+            } else {
+                let clicked_point_already_selected =
+                    self.selections.disjoint_anchors().iter().find(|selection| {
+                        selection.start.to_point(buffer) == start.to_point(buffer)
+                            || selection.end.to_point(buffer) == end.to_point(buffer)
+                    });
+
+                clicked_point_already_selected.map(|selection| selection.id)
+            }
+        };
+
+        let selections_count = self.selections.count();
+        let effects = if auto_scroll {
+            SelectionEffects::default()
+        } else {
+            SelectionEffects::no_scroll()
+        };
+
+        self.change_selections(effects, window, cx, |s| {
+            if let Some(point_to_delete) = point_to_delete {
+                s.delete(point_to_delete);
+
+                if selections_count == 1 {
+                    s.set_pending_anchor_range(start..end, mode);
+                }
+            } else {
+                if !add {
+                    s.clear_disjoint();
+                }
+
+                s.set_pending_anchor_range(start..end, mode);
+            }
+        });
+    }
+
+    pub(super) fn begin_columnar_selection(
+        &mut self,
+        position: DisplayPoint,
+        goal_column: u32,
+        reset: bool,
+        mode: ColumnarMode,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if !self.focus_handle.is_focused(window) {
+            self.last_focused_descendant = None;
+            window.focus(&self.focus_handle, cx);
+        }
+
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+
+        if reset {
+            let pointer_position = display_map
+                .buffer_snapshot()
+                .anchor_before(position.to_point(&display_map));
+
+            self.change_selections(
+                SelectionEffects::scroll(Autoscroll::newest()),
+                window,
+                cx,
+                |s| {
+                    s.clear_disjoint();
+                    s.set_pending_anchor_range(
+                        pointer_position..pointer_position,
+                        SelectMode::Character,
+                    );
+                },
+            );
+        };
+
+        let tail = self.selections.newest::<Point>(&display_map).tail();
+        let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
+        self.columnar_selection_state = match mode {
+            ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
+                selection_tail: selection_anchor,
+                display_point: if reset {
+                    if position.column() != goal_column {
+                        Some(DisplayPoint::new(position.row(), goal_column))
+                    } else {
+                        None
+                    }
+                } else {
+                    None
+                },
+            }),
+            ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
+                selection_tail: selection_anchor,
+            }),
+        };
+
+        if !reset {
+            self.select_columns(position, goal_column, &display_map, window, cx);
+        }
+    }
+
+    pub(super) fn update_selection(
+        &mut self,
+        position: DisplayPoint,
+        goal_column: u32,
+        scroll_delta: gpui::Point<f32>,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+
+        if self.columnar_selection_state.is_some() {
+            self.select_columns(position, goal_column, &display_map, window, cx);
+        } else if let Some((mut pending, mode)) = self.pending_selection_and_mode() {
+            let buffer = display_map.buffer_snapshot();
+            let head;
+            let tail;
+            match &mode {
+                SelectMode::Character => {
+                    head = position.to_point(&display_map);
+                    tail = pending.tail().to_point(buffer);
+                }
+                SelectMode::Word(original_range) => {
+                    let offset = display_map
+                        .clip_point(position, Bias::Left)
+                        .to_offset(&display_map, Bias::Left);
+                    let original_range = original_range.to_offset(buffer);
+
+                    let head_offset = if buffer.is_inside_word(offset, None)
+                        || original_range.contains(&offset)
+                    {
+                        let (word_range, _) = buffer.surrounding_word(offset, None);
+                        if word_range.start < original_range.start {
+                            word_range.start
+                        } else {
+                            word_range.end
+                        }
+                    } else {
+                        offset
+                    };
+
+                    head = head_offset.to_point(buffer);
+                    if head_offset <= original_range.start {
+                        tail = original_range.end.to_point(buffer);
+                    } else {
+                        tail = original_range.start.to_point(buffer);
+                    }
+                }
+                SelectMode::Line(original_range) => {
+                    let original_range = original_range.to_point(display_map.buffer_snapshot());
+
+                    let position = display_map
+                        .clip_point(position, Bias::Left)
+                        .to_point(&display_map);
+                    let line_start = display_map.prev_line_boundary(position).0;
+                    let next_line_start = buffer.clip_point(
+                        display_map.next_line_boundary(position).0 + Point::new(1, 0),
+                        Bias::Left,
+                    );
+
+                    if line_start < original_range.start {
+                        head = line_start
+                    } else {
+                        head = next_line_start
+                    }
+
+                    if head <= original_range.start {
+                        tail = original_range.end;
+                    } else {
+                        tail = original_range.start;
+                    }
+                }
+                SelectMode::All => {
+                    return;
+                }
+            };
+
+            if head < tail {
+                pending.start = buffer.anchor_before(head);
+                pending.end = buffer.anchor_before(tail);
+                pending.reversed = true;
+            } else {
+                pending.start = buffer.anchor_before(tail);
+                pending.end = buffer.anchor_before(head);
+                pending.reversed = false;
+            }
+
+            self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
+                s.set_pending(pending.clone(), mode);
+            });
+        } else {
+            log::error!("update_selection dispatched with no pending selection");
+            return;
+        }
+
+        self.apply_scroll_delta(scroll_delta, window, cx);
+        cx.notify();
+    }
+
+    pub(super) fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+        self.columnar_selection_state.take();
+        if let Some(pending_mode) = self.selections.pending_mode() {
+            let selections = self
+                .selections
+                .all::<MultiBufferOffset>(&self.display_snapshot(cx));
+            self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
+                s.select(selections);
+                s.clear_pending();
+                if s.is_extending() {
+                    s.set_is_extending(false);
+                } else {
+                    s.set_select_mode(pending_mode);
+                }
+            });
+        }
+    }
+
+    fn selections_did_change(
+        &mut self,
+        local: bool,
+        old_cursor_position: &Anchor,
+        effects: SelectionEffects,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.last_selection_from_search = effects.from_search;
+        window.invalidate_character_coordinates();
+
+        // Copy selections to primary selection buffer
+        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
+        if local {
+            let selections = self
+                .selections
+                .all::<MultiBufferOffset>(&self.display_snapshot(cx));
+            let buffer_handle = self.buffer.read(cx).read(cx);
+
+            let mut text = String::new();
+            for (index, selection) in selections.iter().enumerate() {
+                let text_for_selection = buffer_handle
+                    .text_for_range(selection.start..selection.end)
+                    .collect::<String>();
+
+                text.push_str(&text_for_selection);
+                if index != selections.len() - 1 {
+                    text.push('\n');
+                }
+            }
+
+            if !text.is_empty() {
+                cx.write_to_primary(ClipboardItem::new_string(text));
+            }
+        }
+
+        let selection_anchors = self.selections.disjoint_anchors_arc();
+
+        if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
+            self.buffer.update(cx, |buffer, cx| {
+                buffer.set_active_selections(
+                    &selection_anchors,
+                    self.selections.line_mode(),
+                    self.cursor_shape,
+                    cx,
+                )
+            });
+        }
+        let display_map = self
+            .display_map
+            .update(cx, |display_map, cx| display_map.snapshot(cx));
+        let buffer = display_map.buffer_snapshot();
+        if self.selections.count() == 1 {
+            self.add_selections_state = None;
+        }
+        self.select_next_state = None;
+        self.select_prev_state = None;
+        self.select_syntax_node_history.try_clear();
+        self.invalidate_autoclose_regions(&selection_anchors, buffer);
+        self.snippet_stack.invalidate(&selection_anchors, buffer);
+        self.take_rename(false, window, cx);
+
+        let newest_selection = self.selections.newest_anchor();
+        let new_cursor_position = newest_selection.head();
+        let selection_start = newest_selection.start;
+
+        if effects.nav_history.is_none() || effects.nav_history == Some(true) {
+            self.push_to_nav_history(
+                *old_cursor_position,
+                Some(new_cursor_position.to_point(buffer)),
+                false,
+                effects.nav_history == Some(true),
+                cx,
+            );
+        }
+
+        if local {
+            if let Some((anchor, _)) = buffer.anchor_to_buffer_anchor(new_cursor_position) {
+                self.register_buffer(anchor.buffer_id, cx);
+            }
+
+            let mut context_menu = self.context_menu.borrow_mut();
+            let completion_menu = match context_menu.as_ref() {
+                Some(CodeContextMenu::Completions(menu)) => Some(menu),
+                Some(CodeContextMenu::CodeActions(_)) => {
+                    *context_menu = None;
+                    None
+                }
+                None => None,
+            };
+            let completion_position = completion_menu.map(|menu| menu.initial_position);
+            drop(context_menu);
+
+            if effects.completions
+                && let Some(completion_position) = completion_position
+            {
+                let start_offset = selection_start.to_offset(buffer);
+                let position_matches = start_offset == completion_position.to_offset(buffer);
+                let continue_showing = if let Some((snap, ..)) =
+                    buffer.point_to_buffer_offset(completion_position)
+                    && !snap.capability.editable()
+                {
+                    false
+                } else if position_matches {
+                    if self.snippet_stack.is_empty() {
+                        buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
+                            == Some(CharKind::Word)
+                    } else {
+                        // Snippet choices can be shown even when the cursor is in whitespace.
+                        // Dismissing the menu with actions like backspace is handled by
+                        // invalidation regions.
+                        true
+                    }
+                } else {
+                    false
+                };
+
+                if continue_showing {
+                    self.open_or_update_completions_menu(None, None, false, window, cx);
+                } else {
+                    self.hide_context_menu(window, cx);
+                }
+            }
+
+            hide_hover(self, cx);
+
+            self.refresh_code_actions_for_selection(window, cx);
+            self.refresh_document_highlights(cx);
+            refresh_linked_ranges(self, window, cx);
+
+            self.refresh_selected_text_highlights(&display_map, false, window, cx);
+            self.refresh_matching_bracket_highlights(&display_map, cx);
+            self.refresh_outline_symbols_at_cursor(cx);
+            self.update_visible_edit_prediction(window, cx);
+            self.hide_blame_popover(true, cx);
+            if self.git_blame_inline_enabled {
+                self.start_inline_blame_timer(window, cx);
+            }
+        }
+
+        self.blink_manager.update(cx, BlinkManager::pause_blinking);
+
+        if local && !self.suppress_selection_callback {
+            if let Some(callback) = self.on_local_selections_changed.as_ref() {
+                let cursor_position = self.selections.newest::<Point>(&display_map).head();
+                callback(cursor_position, window, cx);
+            }
+        }
+
+        cx.emit(EditorEvent::SelectionsChanged { local });
+
+        let selections = &self.selections.disjoint_anchors_arc();
+        if local && let Some(buffer_snapshot) = buffer.as_singleton() {
+            let inmemory_selections = selections
+                .iter()
+                .map(|s| {
+                    let start = s.range().start.text_anchor_in(buffer_snapshot);
+                    let end = s.range().end.text_anchor_in(buffer_snapshot);
+                    (start..end).to_point(buffer_snapshot)
+                })
+                .collect();
+            self.update_restoration_data(cx, |data| {
+                data.selections = inmemory_selections;
+            });
+
+            if WorkspaceSettings::get(None, cx).restore_on_startup
+                != RestoreOnStartupBehavior::EmptyTab
+                && let Some(workspace_id) = self.workspace_serialization_id(cx)
+            {
+                let snapshot = self.buffer().read(cx).snapshot(cx);
+                let selections = selections.clone();
+                let background_executor = cx.background_executor().clone();
+                let editor_id = cx.entity().entity_id().as_u64() as ItemId;
+                let db = EditorDb::global(cx);
+                self.serialize_selections = cx.background_spawn(async move {
+                    background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
+                    let db_selections = selections
+                        .iter()
+                        .map(|selection| {
+                            (
+                                selection.start.to_offset(&snapshot).0,
+                                selection.end.to_offset(&snapshot).0,
+                            )
+                        })
+                        .collect();
+
+                    db.save_editor_selections(editor_id, workspace_id, db_selections)
+                        .await
+                        .with_context(|| {
+                            format!(
+                                "persisting editor selections for editor {editor_id}, \
+                                workspace {workspace_id:?}"
+                            )
+                        })
+                        .log_err();
+                });
+            }
+        }
+
+        cx.notify();
+    }
+
+    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.effects.scroll {
+                self.request_autoscroll(autoscroll, cx);
+            }
+
+            let old_cursor_position = &state.old_cursor_position;
+
+            self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
+
+            if self.should_open_signature_help_automatically(old_cursor_position, cx) {
+                self.show_signature_help_auto(window, cx);
+            }
+        }
+    }
+
+    fn select_columns(
+        &mut self,
+        head: DisplayPoint,
+        goal_column: u32,
+        display_map: &DisplaySnapshot,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
+            return;
+        };
+
+        let tail = match columnar_state {
+            ColumnarSelectionState::FromMouse {
+                selection_tail,
+                display_point,
+            } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
+            ColumnarSelectionState::FromSelection { selection_tail } => {
+                selection_tail.to_display_point(display_map)
+            }
+        };
+
+        let start_row = cmp::min(tail.row(), head.row());
+        let end_row = cmp::max(tail.row(), head.row());
+        let start_column = cmp::min(tail.column(), goal_column);
+        let end_column = cmp::max(tail.column(), goal_column);
+        let reversed = start_column < tail.column();
+
+        let selection_ranges = (start_row.0..=end_row.0)
+            .map(DisplayRow)
+            .filter_map(|row| {
+                if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
+                    || start_column <= display_map.line_len(row))
+                    && !display_map.is_block_line(row)
+                {
+                    let start = display_map
+                        .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
+                        .to_point(display_map);
+                    let end = display_map
+                        .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
+                        .to_point(display_map);
+                    if reversed {
+                        Some(end..start)
+                    } else {
+                        Some(start..end)
+                    }
+                } else {
+                    None
+                }
+            })
+            .collect::<Vec<_>>();
+        if selection_ranges.is_empty() {
+            return;
+        }
+
+        let ranges = match columnar_state {
+            ColumnarSelectionState::FromMouse { .. } => {
+                let mut non_empty_ranges = selection_ranges
+                    .iter()
+                    .filter(|selection_range| selection_range.start != selection_range.end)
+                    .peekable();
+                if non_empty_ranges.peek().is_some() {
+                    non_empty_ranges.cloned().collect()
+                } else {
+                    selection_ranges
+                }
+            }
+            _ => selection_ranges,
+        };
+
+        self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
+            s.select_ranges(ranges);
+        });
+        cx.notify();
+    }
+
+    fn pending_selection_and_mode(&self) -> Option<(Selection<Anchor>, SelectMode)> {
+        Some((
+            self.selections.pending_anchor()?.clone(),
+            self.selections.pending_mode()?,
+        ))
+    }
+}