Take a mutable context when resolving selections (#19948)

Antonio Scandurra created

This is a behavior-preserving change, but lays the groundwork for
expanding selections when the cursor lands inside of a "replace" block.

Release Notes:

- N/A

Change summary

crates/assistant/src/assistant_panel.rs              |  71 ++-
crates/assistant/src/inline_assistant.rs             |  31 +
crates/collab/src/tests/following_tests.rs           |   7 
crates/diagnostics/src/items.rs                      |   9 
crates/editor/src/editor.rs                          |  19 
crates/editor/src/element.rs                         | 250 +++++++------
crates/editor/src/linked_editing_ranges.rs           |   2 
crates/editor/src/selections_collection.rs           |  37 -
crates/go_to_line/src/cursor_position.rs             |  50 +-
crates/go_to_line/src/go_to_line.rs                  |   4 
crates/language_tools/src/syntax_tree_view.rs        |  14 
crates/markdown_preview/src/markdown_preview_view.rs |   4 
crates/outline/src/outline.rs                        |   8 
crates/outline_panel/src/outline_panel.rs            |   8 
crates/repl/src/repl_editor.rs                       |  20 
crates/vim/src/state.rs                              |   2 
crates/vim/src/vim.rs                                |  15 
17 files changed, 291 insertions(+), 260 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs 🔗

@@ -1677,8 +1677,10 @@ impl ContextEditor {
         });
     }
 
-    fn cursors(&self, cx: &AppContext) -> Vec<usize> {
-        let selections = self.editor.read(cx).selections.all::<usize>(cx);
+    fn cursors(&self, cx: &mut WindowContext) -> Vec<usize> {
+        let selections = self
+            .editor
+            .update(cx, |editor, cx| editor.selections.all::<usize>(cx));
         selections
             .into_iter()
             .map(|selection| selection.head())
@@ -2385,7 +2387,9 @@ impl ContextEditor {
     }
 
     fn update_active_patch(&mut self, cx: &mut ViewContext<Self>) {
-        let newest_cursor = self.editor.read(cx).selections.newest::<Point>(cx).head();
+        let newest_cursor = self.editor.update(cx, |editor, cx| {
+            editor.selections.newest::<Point>(cx).head()
+        });
         let context = self.context.read(cx);
 
         let new_patch = context.patch_containing(newest_cursor, cx).cloned();
@@ -2792,39 +2796,40 @@ impl ContextEditor {
     ) -> Option<(String, bool)> {
         const CODE_FENCE_DELIMITER: &'static str = "```";
 
-        let context_editor = context_editor_view.read(cx).editor.read(cx);
-
-        if context_editor.selections.newest::<Point>(cx).is_empty() {
-            let snapshot = context_editor.buffer().read(cx).snapshot(cx);
-            let (_, _, snapshot) = snapshot.as_singleton()?;
-
-            let head = context_editor.selections.newest::<Point>(cx).head();
-            let offset = snapshot.point_to_offset(head);
+        let context_editor = context_editor_view.read(cx).editor.clone();
+        context_editor.update(cx, |context_editor, cx| {
+            if context_editor.selections.newest::<Point>(cx).is_empty() {
+                let snapshot = context_editor.buffer().read(cx).snapshot(cx);
+                let (_, _, snapshot) = snapshot.as_singleton()?;
+
+                let head = context_editor.selections.newest::<Point>(cx).head();
+                let offset = snapshot.point_to_offset(head);
+
+                let surrounding_code_block_range = find_surrounding_code_block(snapshot, offset)?;
+                let mut text = snapshot
+                    .text_for_range(surrounding_code_block_range)
+                    .collect::<String>();
+
+                // If there is no newline trailing the closing three-backticks, then
+                // tree-sitter-md extends the range of the content node to include
+                // the backticks.
+                if text.ends_with(CODE_FENCE_DELIMITER) {
+                    text.drain((text.len() - CODE_FENCE_DELIMITER.len())..);
+                }
 
-            let surrounding_code_block_range = find_surrounding_code_block(snapshot, offset)?;
-            let mut text = snapshot
-                .text_for_range(surrounding_code_block_range)
-                .collect::<String>();
+                (!text.is_empty()).then_some((text, true))
+            } else {
+                let anchor = context_editor.selections.newest_anchor();
+                let text = context_editor
+                    .buffer()
+                    .read(cx)
+                    .read(cx)
+                    .text_for_range(anchor.range())
+                    .collect::<String>();
 
-            // If there is no newline trailing the closing three-backticks, then
-            // tree-sitter-md extends the range of the content node to include
-            // the backticks.
-            if text.ends_with(CODE_FENCE_DELIMITER) {
-                text.drain((text.len() - CODE_FENCE_DELIMITER.len())..);
+                (!text.is_empty()).then_some((text, false))
             }
-
-            (!text.is_empty()).then_some((text, true))
-        } else {
-            let anchor = context_editor.selections.newest_anchor();
-            let text = context_editor
-                .buffer()
-                .read(cx)
-                .read(cx)
-                .text_for_range(anchor.range())
-                .collect::<String>();
-
-            (!text.is_empty()).then_some((text, false))
-        }
+        })
     }
 
     fn insert_selection(

crates/assistant/src/inline_assistant.rs 🔗

@@ -189,11 +189,16 @@ impl InlineAssistant {
         initial_prompt: Option<String>,
         cx: &mut WindowContext,
     ) {
-        let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
+        let (snapshot, initial_selections) = editor.update(cx, |editor, cx| {
+            (
+                editor.buffer().read(cx).snapshot(cx),
+                editor.selections.all::<Point>(cx),
+            )
+        });
 
         let mut selections = Vec::<Selection<Point>>::new();
         let mut newest_selection = None;
-        for mut selection in editor.read(cx).selections.all::<Point>(cx) {
+        for mut selection in initial_selections {
             if selection.end > selection.start {
                 selection.start.column = 0;
                 // If the selection ends at the start of the line, we don't want to include it.
@@ -566,10 +571,13 @@ impl InlineAssistant {
             return;
         };
 
-        let editor = editor.read(cx);
-        if editor.selections.count() == 1 {
-            let selection = editor.selections.newest::<usize>(cx);
-            let buffer = editor.buffer().read(cx).snapshot(cx);
+        if editor.read(cx).selections.count() == 1 {
+            let (selection, buffer) = editor.update(cx, |editor, cx| {
+                (
+                    editor.selections.newest::<usize>(cx),
+                    editor.buffer().read(cx).snapshot(cx),
+                )
+            });
             for assist_id in &editor_assists.assist_ids {
                 let assist = &self.assists[assist_id];
                 let assist_range = assist.range.to_offset(&buffer);
@@ -594,10 +602,13 @@ impl InlineAssistant {
             return;
         };
 
-        let editor = editor.read(cx);
-        if editor.selections.count() == 1 {
-            let selection = editor.selections.newest::<usize>(cx);
-            let buffer = editor.buffer().read(cx).snapshot(cx);
+        if editor.read(cx).selections.count() == 1 {
+            let (selection, buffer) = editor.update(cx, |editor, cx| {
+                (
+                    editor.selections.newest::<usize>(cx),
+                    editor.buffer().read(cx).snapshot(cx),
+                )
+            });
             let mut closest_assist_fallback = None;
             for assist_id in &editor_assists.assist_ids {
                 let assist = &self.assists[assist_id];

crates/collab/src/tests/following_tests.rs 🔗

@@ -1957,9 +1957,10 @@ async fn test_following_to_channel_notes_without_a_shared_project(
     });
     channel_notes_1_b.update(cx_b, |notes, cx| {
         assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
-        let editor = notes.editor.read(cx);
-        assert_eq!(editor.text(cx), "Hello from A.");
-        assert_eq!(editor.selections.ranges::<usize>(cx), &[3..4]);
+        notes.editor.update(cx, |editor, cx| {
+            assert_eq!(editor.text(cx), "Hello from A.");
+            assert_eq!(editor.selections.ranges::<usize>(cx), &[3..4]);
+        })
     });
 
     //  Client A opens the notes for channel 2.

crates/diagnostics/src/items.rs 🔗

@@ -136,11 +136,12 @@ impl DiagnosticIndicator {
     }
 
     fn update(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
-        let editor = editor.read(cx);
-        let buffer = editor.buffer().read(cx);
-        let cursor_position = editor.selections.newest::<usize>(cx).head();
+        let (buffer, cursor_position) = editor.update(cx, |editor, cx| {
+            let buffer = editor.buffer().read(cx).snapshot(cx);
+            let cursor_position = editor.selections.newest::<usize>(cx).head();
+            (buffer, cursor_position)
+        });
         let new_diagnostic = buffer
-            .snapshot(cx)
             .diagnostics_in_range::<_, usize>(cursor_position..cursor_position, false)
             .filter(|entry| !entry.range.is_empty())
             .min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))

crates/editor/src/editor.rs 🔗

@@ -9629,8 +9629,8 @@ impl Editor {
         let Some(provider) = self.semantics_provider.clone() else {
             return Task::ready(Ok(Navigated::No));
         };
-        let buffer = self.buffer.read(cx);
         let head = self.selections.newest::<usize>(cx).head();
+        let buffer = self.buffer.read(cx);
         let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
             text_anchor
         } else {
@@ -9937,8 +9937,8 @@ impl Editor {
         _: &FindAllReferences,
         cx: &mut ViewContext<Self>,
     ) -> Option<Task<Result<Navigated>>> {
-        let multi_buffer = self.buffer.read(cx);
         let selection = self.selections.newest::<usize>(cx);
+        let multi_buffer = self.buffer.read(cx);
         let head = selection.head();
 
         let multi_buffer_snapshot = multi_buffer.snapshot(cx);
@@ -10345,8 +10345,9 @@ impl Editor {
         self.show_local_selections = true;
 
         if moving_cursor {
-            let rename_editor = rename.editor.read(cx);
-            let cursor_in_rename_editor = rename_editor.selections.newest::<usize>(cx).head();
+            let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
+                editor.selections.newest::<usize>(cx).head()
+            });
 
             // Update the selection to match the position of the selection inside
             // the rename editor.
@@ -11592,9 +11593,9 @@ impl Editor {
     }
 
     pub fn copy_file_location(&mut self, _: &CopyFileLocation, cx: &mut ViewContext<Self>) {
+        let selection = self.selections.newest::<Point>(cx).start.row + 1;
         if let Some(file) = self.target_file(cx) {
             if let Some(path) = file.path().to_str() {
-                let selection = self.selections.newest::<Point>(cx).start.row + 1;
                 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
             }
         }
@@ -12370,9 +12371,10 @@ impl Editor {
             return;
         };
 
+        let selections = self.selections.all::<usize>(cx);
         let buffer = self.buffer.read(cx);
         let mut new_selections_by_buffer = HashMap::default();
-        for selection in self.selections.all::<usize>(cx) {
+        for selection in selections {
             for (buffer, range, _) in
                 buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
             {
@@ -12417,6 +12419,7 @@ impl Editor {
     }
 
     fn open_excerpts_common(&mut self, split: bool, cx: &mut ViewContext<Self>) {
+        let selections = self.selections.all::<usize>(cx);
         let buffer = self.buffer.read(cx);
         if buffer.is_singleton() {
             cx.propagate();
@@ -12429,7 +12432,7 @@ impl Editor {
         };
 
         let mut new_selections_by_buffer = HashMap::default();
-        for selection in self.selections.all::<usize>(cx) {
+        for selection in selections {
             for (mut buffer_handle, mut range, _) in
                 buffer.range_to_buffer_ranges(selection.range(), cx)
             {
@@ -12545,7 +12548,7 @@ impl Editor {
     fn selection_replacement_ranges(
         &self,
         range: Range<OffsetUtf16>,
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Vec<Range<OffsetUtf16>> {
         let selections = self.selections.all::<OffsetUtf16>(cx);
         let newest_selection = selections

crates/editor/src/element.rs 🔗

@@ -824,129 +824,131 @@ impl EditorElement {
         let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
         let mut active_rows = BTreeMap::new();
         let mut newest_selection_head = None;
-        let editor = self.editor.read(cx);
-
-        if editor.show_local_selections {
-            let mut local_selections: Vec<Selection<Point>> = editor
-                .selections
-                .disjoint_in_range(start_anchor..end_anchor, cx);
-            local_selections.extend(editor.selections.pending(cx));
-            let mut layouts = Vec::new();
-            let newest = editor.selections.newest(cx);
-            for selection in local_selections.drain(..) {
-                let is_empty = selection.start == selection.end;
-                let is_newest = selection == newest;
-
-                let layout = SelectionLayout::new(
-                    selection,
-                    editor.selections.line_mode,
-                    editor.cursor_shape,
-                    &snapshot.display_snapshot,
-                    is_newest,
-                    editor.leader_peer_id.is_none(),
-                    None,
-                );
-                if is_newest {
-                    newest_selection_head = Some(layout.head);
-                }
+        self.editor.update(cx, |editor, cx| {
+            if editor.show_local_selections {
+                let mut local_selections: Vec<Selection<Point>> = editor
+                    .selections
+                    .disjoint_in_range(start_anchor..end_anchor, cx);
+                local_selections.extend(editor.selections.pending(cx));
+                let mut layouts = Vec::new();
+                let newest = editor.selections.newest(cx);
+                for selection in local_selections.drain(..) {
+                    let is_empty = selection.start == selection.end;
+                    let is_newest = selection == newest;
+
+                    let layout = SelectionLayout::new(
+                        selection,
+                        editor.selections.line_mode,
+                        editor.cursor_shape,
+                        &snapshot.display_snapshot,
+                        is_newest,
+                        editor.leader_peer_id.is_none(),
+                        None,
+                    );
+                    if is_newest {
+                        newest_selection_head = Some(layout.head);
+                    }
 
-                for row in cmp::max(layout.active_rows.start.0, start_row.0)
-                    ..=cmp::min(layout.active_rows.end.0, end_row.0)
-                {
-                    let contains_non_empty_selection =
-                        active_rows.entry(DisplayRow(row)).or_insert(!is_empty);
-                    *contains_non_empty_selection |= !is_empty;
+                    for row in cmp::max(layout.active_rows.start.0, start_row.0)
+                        ..=cmp::min(layout.active_rows.end.0, end_row.0)
+                    {
+                        let contains_non_empty_selection =
+                            active_rows.entry(DisplayRow(row)).or_insert(!is_empty);
+                        *contains_non_empty_selection |= !is_empty;
+                    }
+                    layouts.push(layout);
                 }
-                layouts.push(layout);
-            }
 
-            let player = if editor.read_only(cx) {
-                cx.theme().players().read_only()
-            } else {
-                self.style.local_player
-            };
+                let player = if editor.read_only(cx) {
+                    cx.theme().players().read_only()
+                } else {
+                    self.style.local_player
+                };
 
-            selections.push((player, layouts));
-        }
+                selections.push((player, layouts));
+            }
 
-        if let Some(collaboration_hub) = &editor.collaboration_hub {
-            // When following someone, render the local selections in their color.
-            if let Some(leader_id) = editor.leader_peer_id {
-                if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
-                    if let Some(participant_index) = collaboration_hub
-                        .user_participant_indices(cx)
-                        .get(&collaborator.user_id)
+            if let Some(collaboration_hub) = &editor.collaboration_hub {
+                // When following someone, render the local selections in their color.
+                if let Some(leader_id) = editor.leader_peer_id {
+                    if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id)
                     {
-                        if let Some((local_selection_style, _)) = selections.first_mut() {
-                            *local_selection_style = cx
-                                .theme()
-                                .players()
-                                .color_for_participant(participant_index.0);
+                        if let Some(participant_index) = collaboration_hub
+                            .user_participant_indices(cx)
+                            .get(&collaborator.user_id)
+                        {
+                            if let Some((local_selection_style, _)) = selections.first_mut() {
+                                *local_selection_style = cx
+                                    .theme()
+                                    .players()
+                                    .color_for_participant(participant_index.0);
+                            }
                         }
                     }
                 }
-            }
 
-            let mut remote_selections = HashMap::default();
-            for selection in snapshot.remote_selections_in_range(
-                &(start_anchor..end_anchor),
-                collaboration_hub.as_ref(),
-                cx,
-            ) {
-                let selection_style = Self::get_participant_color(selection.participant_index, cx);
+                let mut remote_selections = HashMap::default();
+                for selection in snapshot.remote_selections_in_range(
+                    &(start_anchor..end_anchor),
+                    collaboration_hub.as_ref(),
+                    cx,
+                ) {
+                    let selection_style =
+                        Self::get_participant_color(selection.participant_index, cx);
+
+                    // Don't re-render the leader's selections, since the local selections
+                    // match theirs.
+                    if Some(selection.peer_id) == editor.leader_peer_id {
+                        continue;
+                    }
+                    let key = HoveredCursor {
+                        replica_id: selection.replica_id,
+                        selection_id: selection.selection.id,
+                    };
 
-                // Don't re-render the leader's selections, since the local selections
-                // match theirs.
-                if Some(selection.peer_id) == editor.leader_peer_id {
-                    continue;
+                    let is_shown =
+                        editor.show_cursor_names || editor.hovered_cursors.contains_key(&key);
+
+                    remote_selections
+                        .entry(selection.replica_id)
+                        .or_insert((selection_style, Vec::new()))
+                        .1
+                        .push(SelectionLayout::new(
+                            selection.selection,
+                            selection.line_mode,
+                            selection.cursor_shape,
+                            &snapshot.display_snapshot,
+                            false,
+                            false,
+                            if is_shown { selection.user_name } else { None },
+                        ));
                 }
-                let key = HoveredCursor {
-                    replica_id: selection.replica_id,
-                    selection_id: selection.selection.id,
-                };
 
-                let is_shown =
-                    editor.show_cursor_names || editor.hovered_cursors.contains_key(&key);
-
-                remote_selections
-                    .entry(selection.replica_id)
-                    .or_insert((selection_style, Vec::new()))
-                    .1
-                    .push(SelectionLayout::new(
-                        selection.selection,
-                        selection.line_mode,
-                        selection.cursor_shape,
-                        &snapshot.display_snapshot,
-                        false,
-                        false,
-                        if is_shown { selection.user_name } else { None },
-                    ));
+                selections.extend(remote_selections.into_values());
+            } else if !editor.is_focused(cx) && editor.show_cursor_when_unfocused {
+                let player = if editor.read_only(cx) {
+                    cx.theme().players().read_only()
+                } else {
+                    self.style.local_player
+                };
+                let layouts = snapshot
+                    .buffer_snapshot
+                    .selections_in_range(&(start_anchor..end_anchor), true)
+                    .map(move |(_, line_mode, cursor_shape, selection)| {
+                        SelectionLayout::new(
+                            selection,
+                            line_mode,
+                            cursor_shape,
+                            &snapshot.display_snapshot,
+                            false,
+                            false,
+                            None,
+                        )
+                    })
+                    .collect::<Vec<_>>();
+                selections.push((player, layouts));
             }
-
-            selections.extend(remote_selections.into_values());
-        } else if !editor.is_focused(cx) && editor.show_cursor_when_unfocused {
-            let player = if editor.read_only(cx) {
-                cx.theme().players().read_only()
-            } else {
-                self.style.local_player
-            };
-            let layouts = snapshot
-                .buffer_snapshot
-                .selections_in_range(&(start_anchor..end_anchor), true)
-                .map(move |(_, line_mode, cursor_shape, selection)| {
-                    SelectionLayout::new(
-                        selection,
-                        line_mode,
-                        cursor_shape,
-                        &snapshot.display_snapshot,
-                        false,
-                        false,
-                        None,
-                    )
-                })
-                .collect::<Vec<_>>();
-            selections.push((player, layouts));
-        }
+        });
         (selections, active_rows, newest_selection_head)
     }
 
@@ -1848,23 +1850,25 @@ impl EditorElement {
             return Vec::new();
         }
 
-        let editor = self.editor.read(cx);
-        let newest_selection_head = newest_selection_head.unwrap_or_else(|| {
-            let newest = editor.selections.newest::<Point>(cx);
-            SelectionLayout::new(
-                newest,
-                editor.selections.line_mode,
-                editor.cursor_shape,
-                &snapshot.display_snapshot,
-                true,
-                true,
-                None,
-            )
-            .head
+        let (newest_selection_head, is_relative) = self.editor.update(cx, |editor, cx| {
+            let newest_selection_head = newest_selection_head.unwrap_or_else(|| {
+                let newest = editor.selections.newest::<Point>(cx);
+                SelectionLayout::new(
+                    newest,
+                    editor.selections.line_mode,
+                    editor.cursor_shape,
+                    &snapshot.display_snapshot,
+                    true,
+                    true,
+                    None,
+                )
+                .head
+            });
+            let is_relative = editor.should_use_relative_line_numbers(cx);
+            (newest_selection_head, is_relative)
         });
         let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
 
-        let is_relative = editor.should_use_relative_line_numbers(cx);
         let relative_to = if is_relative {
             Some(newest_selection_head.row())
         } else {

crates/editor/src/linked_editing_ranges.rs 🔗

@@ -41,9 +41,9 @@ pub(super) fn refresh_linked_ranges(this: &mut Editor, cx: &mut ViewContext<Edit
         return None;
     }
     let project = this.project.clone()?;
+    let selections = this.selections.all::<usize>(cx);
     let buffer = this.buffer.read(cx);
     let mut applicable_selections = vec![];
-    let selections = this.selections.all::<usize>(cx);
     let snapshot = buffer.snapshot(cx);
     for selection in selections {
         let cursor_position = selection.head();

crates/editor/src/selections_collection.rs 🔗

@@ -8,14 +8,14 @@ use std::{
 use collections::HashMap;
 use gpui::{AppContext, Model, Pixels};
 use itertools::Itertools;
-use language::{Bias, Point, Selection, SelectionGoal, TextDimension, ToPoint};
+use language::{Bias, Point, Selection, SelectionGoal, TextDimension};
 use util::post_inc;
 
 use crate::{
     display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
     movement::TextLayoutDetails,
     Anchor, DisplayPoint, DisplayRow, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode,
-    ToOffset,
+    ToOffset, ToPoint,
 };
 
 #[derive(Debug, Clone)]
@@ -96,7 +96,7 @@ impl SelectionsCollection {
 
     pub fn pending<D: TextDimension + Ord + Sub<D, Output = D>>(
         &self,
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Option<Selection<D>> {
         self.pending_anchor()
             .as_ref()
@@ -107,7 +107,7 @@ impl SelectionsCollection {
         self.pending.as_ref().map(|pending| pending.mode.clone())
     }
 
-    pub fn all<'a, D>(&self, cx: &AppContext) -> Vec<Selection<D>>
+    pub fn all<'a, D>(&self, cx: &mut AppContext) -> Vec<Selection<D>>
     where
         D: 'a + TextDimension + Ord + Sub<D, Output = D>,
     {
@@ -194,7 +194,7 @@ impl SelectionsCollection {
     pub fn disjoint_in_range<'a, D>(
         &self,
         range: Range<Anchor>,
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Vec<Selection<D>>
     where
         D: 'a + TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug,
@@ -239,9 +239,10 @@ impl SelectionsCollection {
 
     pub fn newest<D: TextDimension + Ord + Sub<D, Output = D>>(
         &self,
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Selection<D> {
-        resolve(self.newest_anchor(), &self.buffer(cx))
+        let buffer = self.buffer(cx);
+        self.newest_anchor().map(|p| p.summary::<D>(&buffer))
     }
 
     pub fn newest_display(&self, cx: &mut AppContext) -> Selection<DisplayPoint> {
@@ -262,9 +263,10 @@ impl SelectionsCollection {
 
     pub fn oldest<D: TextDimension + Ord + Sub<D, Output = D>>(
         &self,
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Selection<D> {
-        resolve(self.oldest_anchor(), &self.buffer(cx))
+        let buffer = self.buffer(cx);
+        self.oldest_anchor().map(|p| p.summary::<D>(&buffer))
     }
 
     pub fn first_anchor(&self) -> Selection<Anchor> {
@@ -276,14 +278,14 @@ impl SelectionsCollection {
 
     pub fn first<D: TextDimension + Ord + Sub<D, Output = D>>(
         &self,
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Selection<D> {
         self.all(cx).first().unwrap().clone()
     }
 
     pub fn last<D: TextDimension + Ord + Sub<D, Output = D>>(
         &self,
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Selection<D> {
         self.all(cx).last().unwrap().clone()
     }
@@ -298,7 +300,7 @@ impl SelectionsCollection {
     #[cfg(any(test, feature = "test-support"))]
     pub fn ranges<D: TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug>(
         &self,
-        cx: &AppContext,
+        cx: &mut AppContext,
     ) -> Vec<Range<D>> {
         self.all::<D>(cx)
             .iter()
@@ -475,7 +477,7 @@ impl<'a> MutableSelectionsCollection<'a> {
     where
         T: 'a + ToOffset + ToPoint + TextDimension + Ord + Sub<T, Output = T> + std::marker::Copy,
     {
-        let mut selections = self.all(self.cx);
+        let mut selections = self.collection.all(self.cx);
         let mut start = range.start.to_offset(&self.buffer());
         let mut end = range.end.to_offset(&self.buffer());
         let reversed = if start > end {
@@ -649,6 +651,7 @@ impl<'a> MutableSelectionsCollection<'a> {
         let mut changed = false;
         let display_map = self.display_map();
         let selections = self
+            .collection
             .all::<Point>(self.cx)
             .into_iter()
             .map(|selection| {
@@ -676,6 +679,7 @@ impl<'a> MutableSelectionsCollection<'a> {
         let mut changed = false;
         let snapshot = self.buffer().clone();
         let selections = self
+            .collection
             .all::<usize>(self.cx)
             .into_iter()
             .map(|selection| {
@@ -869,10 +873,3 @@ where
         goal: s.goal,
     })
 }
-
-fn resolve<D: TextDimension + Ord + Sub<D, Output = D>>(
-    selection: &Selection<Anchor>,
-    buffer: &MultiBufferSnapshot,
-) -> Selection<D> {
-    selection.map(|p| p.summary::<D>(buffer))
-}

crates/go_to_line/src/cursor_position.rs 🔗

@@ -37,34 +37,34 @@ impl CursorPosition {
     }
 
     fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
-        let editor = editor.read(cx);
-        let buffer = editor.buffer().read(cx).snapshot(cx);
-
-        self.selected_count = Default::default();
-        self.selected_count.selections = editor.selections.count();
-        let mut last_selection: Option<Selection<usize>> = None;
-        for selection in editor.selections.all::<usize>(cx) {
-            self.selected_count.characters += buffer
-                .text_for_range(selection.start..selection.end)
-                .map(|t| t.chars().count())
-                .sum::<usize>();
-            if last_selection
-                .as_ref()
-                .map_or(true, |last_selection| selection.id > last_selection.id)
-            {
-                last_selection = Some(selection);
+        editor.update(cx, |editor, cx| {
+            let buffer = editor.buffer().read(cx).snapshot(cx);
+
+            self.selected_count = Default::default();
+            self.selected_count.selections = editor.selections.count();
+            let mut last_selection: Option<Selection<usize>> = None;
+            for selection in editor.selections.all::<usize>(cx) {
+                self.selected_count.characters += buffer
+                    .text_for_range(selection.start..selection.end)
+                    .map(|t| t.chars().count())
+                    .sum::<usize>();
+                if last_selection
+                    .as_ref()
+                    .map_or(true, |last_selection| selection.id > last_selection.id)
+                {
+                    last_selection = Some(selection);
+                }
             }
-        }
-        for selection in editor.selections.all::<Point>(cx) {
-            if selection.end != selection.start {
-                self.selected_count.lines += (selection.end.row - selection.start.row) as usize;
-                if selection.end.column != 0 {
-                    self.selected_count.lines += 1;
+            for selection in editor.selections.all::<Point>(cx) {
+                if selection.end != selection.start {
+                    self.selected_count.lines += (selection.end.row - selection.start.row) as usize;
+                    if selection.end.column != 0 {
+                        self.selected_count.lines += 1;
+                    }
                 }
             }
-        }
-        self.position = last_selection.map(|s| s.head().to_point(&buffer));
-
+            self.position = last_selection.map(|s| s.head().to_point(&buffer));
+        });
         cx.notify();
     }
 

crates/go_to_line/src/go_to_line.rs 🔗

@@ -56,8 +56,8 @@ impl GoToLine {
     }
 
     pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
-        let editor = active_editor.read(cx);
-        let cursor = editor.selections.last::<Point>(cx).head();
+        let cursor =
+            active_editor.update(cx, |editor, cx| editor.selections.last::<Point>(cx).head());
 
         let line = cursor.row + 1;
         let column = cursor.column + 1;

crates/language_tools/src/syntax_tree_view.rs 🔗

@@ -128,12 +128,14 @@ impl SyntaxTreeView {
     fn editor_updated(&mut self, did_reparse: bool, cx: &mut ViewContext<Self>) -> Option<()> {
         // Find which excerpt the cursor is in, and the position within that excerpted buffer.
         let editor_state = self.editor.as_mut()?;
-        let editor = &editor_state.editor.read(cx);
-        let selection_range = editor.selections.last::<usize>(cx).range();
-        let multibuffer = editor.buffer().read(cx);
-        let (buffer, range, excerpt_id) = multibuffer
-            .range_to_buffer_ranges(selection_range, cx)
-            .pop()?;
+        let (buffer, range, excerpt_id) = editor_state.editor.update(cx, |editor, cx| {
+            let selection_range = editor.selections.last::<usize>(cx).range();
+            editor
+                .buffer()
+                .read(cx)
+                .range_to_buffer_ranges(selection_range, cx)
+                .pop()
+        })?;
 
         // If the cursor has moved into a different excerpt, retrieve a new syntax layer
         // from that buffer.

crates/markdown_preview/src/markdown_preview_view.rs 🔗

@@ -301,8 +301,8 @@ impl MarkdownPreviewView {
                     this.parse_markdown_from_active_editor(true, cx);
                 }
                 EditorEvent::SelectionsChanged { .. } => {
-                    let editor = editor.read(cx);
-                    let selection_range = editor.selections.last::<usize>(cx).range();
+                    let selection_range =
+                        editor.update(cx, |editor, cx| editor.selections.last::<usize>(cx).range());
                     this.selected_block = this.get_block_index_under_cursor(selection_range);
                     this.list_state.scroll_to_reveal_item(this.selected_block);
                     cx.notify();

crates/outline/src/outline.rs 🔗

@@ -194,9 +194,11 @@ impl PickerDelegate for OutlineViewDelegate {
                 })
                 .collect();
 
-            let editor = self.active_editor.read(cx);
-            let cursor_offset = editor.selections.newest::<usize>(cx).head();
-            let buffer = editor.buffer().read(cx).snapshot(cx);
+            let (buffer, cursor_offset) = self.active_editor.update(cx, |editor, cx| {
+                let buffer = editor.buffer().read(cx).snapshot(cx);
+                let cursor_offset = editor.selections.newest::<usize>(cx).head();
+                (buffer, cursor_offset)
+            });
             selected_index = self
                 .outline
                 .items

crates/outline_panel/src/outline_panel.rs 🔗

@@ -2410,11 +2410,9 @@ impl OutlinePanel {
         editor: &View<Editor>,
         cx: &mut ViewContext<Self>,
     ) -> Option<PanelEntry> {
-        let selection = editor
-            .read(cx)
-            .selections
-            .newest::<language::Point>(cx)
-            .head();
+        let selection = editor.update(cx, |editor, cx| {
+            editor.selections.newest::<language::Point>(cx).head()
+        });
         let editor_snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx));
         let multi_buffer = editor.read(cx).buffer();
         let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);

crates/repl/src/repl_editor.rs 🔗

@@ -5,7 +5,7 @@ use std::sync::Arc;
 
 use anyhow::{Context, Result};
 use editor::Editor;
-use gpui::{prelude::*, AppContext, Entity, View, WeakView, WindowContext};
+use gpui::{prelude::*, Entity, View, WeakView, WindowContext};
 use language::{BufferSnapshot, Language, LanguageName, Point};
 
 use crate::repl_store::ReplStore;
@@ -103,7 +103,7 @@ pub enum SessionSupport {
     Unsupported,
 }
 
-pub fn session(editor: WeakView<Editor>, cx: &mut AppContext) -> SessionSupport {
+pub fn session(editor: WeakView<Editor>, cx: &mut WindowContext) -> SessionSupport {
     let store = ReplStore::global(cx);
     let entity_id = editor.entity_id();
 
@@ -311,17 +311,21 @@ fn language_supported(language: &Arc<Language>) -> bool {
     }
 }
 
-fn get_language(editor: WeakView<Editor>, cx: &mut AppContext) -> Option<Arc<Language>> {
-    let editor = editor.upgrade()?;
-    let selection = editor.read(cx).selections.newest::<usize>(cx);
-    let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
-    buffer.language_at(selection.head()).cloned()
+fn get_language(editor: WeakView<Editor>, cx: &mut WindowContext) -> Option<Arc<Language>> {
+    editor
+        .update(cx, |editor, cx| {
+            let selection = editor.selections.newest::<usize>(cx);
+            let buffer = editor.buffer().read(cx).snapshot(cx);
+            buffer.language_at(selection.head()).cloned()
+        })
+        .ok()
+        .flatten()
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
-    use gpui::Context;
+    use gpui::{AppContext, Context};
     use indoc::indoc;
     use language::{Buffer, Language, LanguageConfig, LanguageRegistry};
 

crates/vim/src/state.rs 🔗

@@ -281,7 +281,7 @@ impl VimGlobals {
         &mut self,
         register: Option<char>,
         editor: Option<&mut Editor>,
-        cx: &ViewContext<Editor>,
+        cx: &mut ViewContext<Editor>,
     ) -> Option<Register> {
         let Some(register) = register.filter(|reg| *reg != '"') else {
             let setting = VimSettings::get_global(cx).use_system_clipboard;

crates/vim/src/vim.rs 🔗

@@ -620,9 +620,11 @@ impl Vim {
         let Some(editor) = self.editor() else {
             return;
         };
+        let newest_selection_empty = editor.update(cx, |editor, cx| {
+            editor.selections.newest::<usize>(cx).is_empty()
+        });
         let editor = editor.read(cx);
         let editor_mode = editor.mode();
-        let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
 
         if editor_mode == EditorMode::Full
                 && !newest_selection_empty
@@ -717,11 +719,12 @@ impl Vim {
                 globals.recorded_count = None;
 
                 let selections = self.editor().map(|editor| {
-                    let editor = editor.read(cx);
-                    (
-                        editor.selections.oldest::<Point>(cx),
-                        editor.selections.newest::<Point>(cx),
-                    )
+                    editor.update(cx, |editor, cx| {
+                        (
+                            editor.selections.oldest::<Point>(cx),
+                            editor.selections.newest::<Point>(cx),
+                        )
+                    })
                 });
 
                 if let Some((oldest, newest)) = selections {