multi_buffer: Reduce `RefCell::borrow_mut` calls to the bare minimum (#40522)

Lukas Wirth created

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/buffer_diff/src/buffer_diff.rs         |  12 
crates/clock/src/clock.rs                     |  10 
crates/editor/src/editor.rs                   |   3 
crates/editor/src/element.rs                  |   2 
crates/multi_buffer/src/multi_buffer.rs       | 470 ++++++++++----------
crates/multi_buffer/src/multi_buffer_tests.rs |  16 
crates/project/src/git_store/conflict_set.rs  |   6 
crates/search/src/project_search.rs           |   4 
crates/text/src/anchor.rs                     |  12 
9 files changed, 269 insertions(+), 266 deletions(-)

Detailed changes

crates/buffer_diff/src/buffer_diff.rs 🔗

@@ -118,11 +118,11 @@ impl sum_tree::Summary for DiffHunkSummary {
     }
 
     fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
-        self.buffer_range.start = self
+        self.buffer_range.start = *self
             .buffer_range
             .start
             .min(&other.buffer_range.start, buffer);
-        self.buffer_range.end = self.buffer_range.end.max(&other.buffer_range.end, buffer);
+        self.buffer_range.end = *self.buffer_range.end.max(&other.buffer_range.end, buffer);
     }
 }
 
@@ -1068,8 +1068,8 @@ impl BufferDiff {
                 self.range_to_hunk_range(secondary_changed_range, buffer, cx)
         {
             if let Some(range) = &mut changed_range {
-                range.start = secondary_hunk_range.start.min(&range.start, buffer);
-                range.end = secondary_hunk_range.end.max(&range.end, buffer);
+                range.start = *secondary_hunk_range.start.min(&range.start, buffer);
+                range.end = *secondary_hunk_range.end.max(&range.end, buffer);
             } else {
                 changed_range = Some(secondary_hunk_range);
             }
@@ -1083,8 +1083,8 @@ impl BufferDiff {
             if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
             {
                 if let Some(range) = &mut changed_range {
-                    range.start = range.start.min(&first.buffer_range.start, buffer);
-                    range.end = range.end.max(&last.buffer_range.end, buffer);
+                    range.start = *range.start.min(&first.buffer_range.start, buffer);
+                    range.end = *range.end.max(&last.buffer_range.end, buffer);
                 } else {
                     changed_range = Some(first.buffer_range.start..last.buffer_range.end);
                 }

crates/clock/src/clock.rs 🔗

@@ -206,7 +206,13 @@ impl Lamport {
 
 impl fmt::Debug for Lamport {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "Lamport {{{}: {}}}", self.replica_id, self.value)
+        if *self == Self::MAX {
+            write!(f, "Lamport {{MAX}}")
+        } else if *self == Self::MIN {
+            write!(f, "Lamport {{MIN}}")
+        } else {
+            write!(f, "Lamport {{{}: {}}}", self.replica_id, self.value)
+        }
     }
 }
 
@@ -219,6 +225,8 @@ impl fmt::Debug for Global {
             }
             if timestamp.replica_id == LOCAL_BRANCH_REPLICA_ID {
                 write!(f, "<branch>: {}", timestamp.value)?;
+            } else if timestamp.replica_id == AGENT_REPLICA_ID {
+                write!(f, "<agent>: {}", timestamp.value)?;
             } else {
                 write!(f, "{}: {}", timestamp.replica_id, timestamp.value)?;
             }

crates/editor/src/editor.rs 🔗

@@ -6887,7 +6887,8 @@ impl Editor {
                                 continue;
                             }
 
-                            let range = Anchor::range_in_buffer(excerpt_id, buffer_id, start..end);
+                            let range =
+                                Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
                             if highlight.kind == lsp::DocumentHighlightKind::WRITE {
                                 write_ranges.push(range);
                             } else {

crates/editor/src/element.rs 🔗

@@ -7465,7 +7465,7 @@ impl EditorElement {
                         let clipped_start = range.start.max(&buffer_range.start, buffer);
                         let clipped_end = range.end.min(&buffer_range.end, buffer);
                         let range = buffer_snapshot
-                            .anchor_range_in_excerpt(excerpt_id, clipped_start..clipped_end)?;
+                            .anchor_range_in_excerpt(excerpt_id, *clipped_start..*clipped_end)?;
                         let start = range.start.to_display_point(display_snapshot);
                         let end = range.end.to_display_point(display_snapshot);
                         let selection_layout = SelectionLayout {

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -64,17 +64,22 @@ pub struct MultiBuffer {
     /// Use [`MultiBuffer::snapshot`] to get a up-to-date snapshot.
     snapshot: RefCell<MultiBufferSnapshot>,
     /// Contains the state of the buffers being edited
-    buffers: RefCell<HashMap<BufferId, BufferState>>,
-    // only used by consumers using `set_excerpts_for_buffer`
+    buffers: HashMap<BufferId, BufferState>,
+    /// Mapping from path keys to their excerpts.
     excerpts_by_path: BTreeMap<PathKey, Vec<ExcerptId>>,
+    /// Mapping from excerpt IDs to their path key.
     paths_by_excerpt: HashMap<ExcerptId, PathKey>,
+    /// Mapping from buffer IDs to their diff states
     diffs: HashMap<BufferId, DiffState>,
-    // all_diff_hunks_expanded: bool,
     subscriptions: Topic,
     /// If true, the multi-buffer only contains a single [`Buffer`] and a single [`Excerpt`]
     singleton: bool,
+    /// The history of the multi-buffer.
     history: History,
+    /// The explicit title of the multi-buffer.
+    /// If `None`, it will be derived from the underlying path or content.
     title: Option<String>,
+    /// The writing capability of the multi-buffer.
     capability: Capability,
     buffer_changed_since_sync: Rc<Cell<bool>>,
 }
@@ -249,8 +254,8 @@ pub trait ToPointUtf16: 'static + fmt::Debug {
 
 struct BufferState {
     buffer: Entity<Buffer>,
-    last_version: clock::Global,
-    last_non_text_state_update_count: usize,
+    last_version: RefCell<clock::Global>,
+    last_non_text_state_update_count: Cell<usize>,
     excerpts: Vec<Locator>,
     _subscriptions: [gpui::Subscription; 2],
 }
@@ -282,15 +287,20 @@ impl DiffState {
 #[derive(Clone, Default)]
 pub struct MultiBufferSnapshot {
     singleton: bool,
+    /* mut */
     excerpts: SumTree<Excerpt>,
+    /* mut */
     excerpt_ids: SumTree<ExcerptIdMapping>,
     diffs: TreeMap<BufferId, BufferDiffSnapshot>,
     diff_transforms: SumTree<DiffTransform>,
+    /* mut */
     replaced_excerpts: TreeMap<ExcerptId, ExcerptId>,
+    /* mut */
     trailing_excerpt_update_count: usize,
     all_diff_hunks_expanded: bool,
     non_text_state_update_count: usize,
     edit_count: usize,
+    /* mut */
     is_dirty: bool,
     has_deleted_file: bool,
     has_conflict: bool,
@@ -612,12 +622,41 @@ impl IndentGuide {
 
 impl MultiBuffer {
     pub fn new(capability: Capability) -> Self {
-        Self {
-            snapshot: RefCell::new(MultiBufferSnapshot {
+        Self::new_(
+            capability,
+            MultiBufferSnapshot {
                 show_headers: true,
                 ..MultiBufferSnapshot::default()
-            }),
-            buffers: RefCell::default(),
+            },
+        )
+    }
+
+    pub fn without_headers(capability: Capability) -> Self {
+        Self::new_(capability, Default::default())
+    }
+
+    pub fn singleton(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
+        let mut this = Self::new_(
+            buffer.read(cx).capability(),
+            MultiBufferSnapshot {
+                singleton: true,
+                ..MultiBufferSnapshot::default()
+            },
+        );
+        this.singleton = true;
+        this.push_excerpts(
+            buffer,
+            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
+            cx,
+        );
+        this
+    }
+
+    #[inline]
+    pub fn new_(capability: Capability, snapshot: MultiBufferSnapshot) -> Self {
+        Self {
+            snapshot: RefCell::new(snapshot),
+            buffers: Default::default(),
             diffs: HashMap::default(),
             subscriptions: Topic::default(),
             singleton: false,
@@ -636,32 +675,10 @@ impl MultiBuffer {
         }
     }
 
-    pub fn without_headers(capability: Capability) -> Self {
-        Self {
-            snapshot: Default::default(),
-            buffers: Default::default(),
-            excerpts_by_path: Default::default(),
-            paths_by_excerpt: Default::default(),
-            diffs: HashMap::default(),
-            subscriptions: Default::default(),
-            singleton: false,
-            capability,
-            buffer_changed_since_sync: Default::default(),
-            history: History {
-                next_transaction_id: Default::default(),
-                undo_stack: Default::default(),
-                redo_stack: Default::default(),
-                transaction_depth: 0,
-                group_interval: Duration::from_millis(300),
-            },
-            title: Default::default(),
-        }
-    }
-
     pub fn clone(&self, new_cx: &mut Context<Self>) -> Self {
         let mut buffers = HashMap::default();
         let buffer_changed_since_sync = Rc::new(Cell::new(false));
-        for (buffer_id, buffer_state) in self.buffers.borrow().iter() {
+        for (buffer_id, buffer_state) in self.buffers.iter() {
             buffer_state.buffer.update(new_cx, |buffer, _| {
                 buffer.record_changes(Rc::downgrade(&buffer_changed_since_sync));
             });
@@ -670,7 +687,9 @@ impl MultiBuffer {
                 BufferState {
                     buffer: buffer_state.buffer.clone(),
                     last_version: buffer_state.last_version.clone(),
-                    last_non_text_state_update_count: buffer_state.last_non_text_state_update_count,
+                    last_non_text_state_update_count: buffer_state
+                        .last_non_text_state_update_count
+                        .clone(),
                     excerpts: buffer_state.excerpts.clone(),
                     _subscriptions: [
                         new_cx.observe(&buffer_state.buffer, |_, _, cx| cx.notify()),
@@ -685,7 +704,7 @@ impl MultiBuffer {
         }
         Self {
             snapshot: RefCell::new(self.snapshot.borrow().clone()),
-            buffers: RefCell::new(buffers),
+            buffers: buffers,
             excerpts_by_path: Default::default(),
             paths_by_excerpt: Default::default(),
             diffs: diff_bases,
@@ -707,18 +726,6 @@ impl MultiBuffer {
         self.capability == Capability::ReadOnly
     }
 
-    pub fn singleton(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
-        let mut this = Self::new(buffer.read(cx).capability());
-        this.singleton = true;
-        this.push_excerpts(
-            buffer,
-            [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
-            cx,
-        );
-        this.snapshot.borrow_mut().singleton = true;
-        this
-    }
-
     /// Returns an up-to-date snapshot of the MultiBuffer.
     pub fn snapshot(&self, cx: &App) -> MultiBufferSnapshot {
         self.sync(cx);
@@ -732,15 +739,7 @@ impl MultiBuffer {
 
     pub fn as_singleton(&self) -> Option<Entity<Buffer>> {
         if self.singleton {
-            Some(
-                self.buffers
-                    .borrow()
-                    .values()
-                    .next()
-                    .unwrap()
-                    .buffer
-                    .clone(),
-            )
+            Some(self.buffers.values().next().unwrap().buffer.clone())
         } else {
             None
         }
@@ -773,7 +772,7 @@ impl MultiBuffer {
     }
 
     pub fn is_empty(&self) -> bool {
-        self.buffers.borrow().is_empty()
+        self.buffers.is_empty()
     }
 
     pub fn symbols_containing<T: ToOffset>(
@@ -817,7 +816,7 @@ impl MultiBuffer {
             mut autoindent_mode: Option<AutoindentMode>,
             cx: &mut Context<MultiBuffer>,
         ) {
-            if this.read_only() || this.buffers.borrow().is_empty() {
+            if this.read_only() || this.buffers.is_empty() {
                 return;
             }
 
@@ -836,78 +835,74 @@ impl MultiBuffer {
             for (buffer_id, mut edits) in buffer_edits {
                 buffer_ids.push(buffer_id);
                 edits.sort_by_key(|edit| edit.range.start);
-                this.buffers.borrow()[&buffer_id]
-                    .buffer
-                    .update(cx, |buffer, cx| {
-                        let mut edits = edits.into_iter().peekable();
-                        let mut insertions = Vec::new();
-                        let mut original_indent_columns = Vec::new();
-                        let mut deletions = Vec::new();
-                        let empty_str: Arc<str> = Arc::default();
+                this.buffers[&buffer_id].buffer.update(cx, |buffer, cx| {
+                    let mut edits = edits.into_iter().peekable();
+                    let mut insertions = Vec::new();
+                    let mut original_indent_columns = Vec::new();
+                    let mut deletions = Vec::new();
+                    let empty_str: Arc<str> = Arc::default();
+                    while let Some(BufferEdit {
+                        mut range,
+                        mut new_text,
+                        mut is_insertion,
+                        original_indent_column,
+                        excerpt_id,
+                    }) = edits.next()
+                    {
                         while let Some(BufferEdit {
-                            mut range,
-                            mut new_text,
-                            mut is_insertion,
-                            original_indent_column,
-                            excerpt_id,
-                        }) = edits.next()
+                            range: next_range,
+                            is_insertion: next_is_insertion,
+                            new_text: next_new_text,
+                            excerpt_id: next_excerpt_id,
+                            ..
+                        }) = edits.peek()
                         {
-                            while let Some(BufferEdit {
-                                range: next_range,
-                                is_insertion: next_is_insertion,
-                                new_text: next_new_text,
-                                excerpt_id: next_excerpt_id,
-                                ..
-                            }) = edits.peek()
-                            {
-                                if range.end >= next_range.start {
-                                    range.end = cmp::max(next_range.end, range.end);
-                                    is_insertion |= *next_is_insertion;
-                                    if excerpt_id == *next_excerpt_id {
-                                        new_text = format!("{new_text}{next_new_text}").into();
-                                    }
-                                    edits.next();
-                                } else {
-                                    break;
+                            if range.end >= next_range.start {
+                                range.end = cmp::max(next_range.end, range.end);
+                                is_insertion |= *next_is_insertion;
+                                if excerpt_id == *next_excerpt_id {
+                                    new_text = format!("{new_text}{next_new_text}").into();
                                 }
+                                edits.next();
+                            } else {
+                                break;
                             }
+                        }
 
-                            if is_insertion {
-                                original_indent_columns.push(original_indent_column);
-                                insertions.push((
-                                    buffer.anchor_before(range.start)
-                                        ..buffer.anchor_before(range.end),
-                                    new_text.clone(),
-                                ));
-                            } else if !range.is_empty() {
-                                deletions.push((
-                                    buffer.anchor_before(range.start)
-                                        ..buffer.anchor_before(range.end),
-                                    empty_str.clone(),
-                                ));
-                            }
+                        if is_insertion {
+                            original_indent_columns.push(original_indent_column);
+                            insertions.push((
+                                buffer.anchor_before(range.start)..buffer.anchor_before(range.end),
+                                new_text.clone(),
+                            ));
+                        } else if !range.is_empty() {
+                            deletions.push((
+                                buffer.anchor_before(range.start)..buffer.anchor_before(range.end),
+                                empty_str.clone(),
+                            ));
                         }
+                    }
 
-                        let deletion_autoindent_mode =
-                            if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
-                                Some(AutoindentMode::Block {
-                                    original_indent_columns: Default::default(),
-                                })
-                            } else {
-                                autoindent_mode.clone()
-                            };
-                        let insertion_autoindent_mode =
-                            if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
-                                Some(AutoindentMode::Block {
-                                    original_indent_columns,
-                                })
-                            } else {
-                                autoindent_mode.clone()
-                            };
+                    let deletion_autoindent_mode =
+                        if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
+                            Some(AutoindentMode::Block {
+                                original_indent_columns: Default::default(),
+                            })
+                        } else {
+                            autoindent_mode.clone()
+                        };
+                    let insertion_autoindent_mode =
+                        if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
+                            Some(AutoindentMode::Block {
+                                original_indent_columns,
+                            })
+                        } else {
+                            autoindent_mode.clone()
+                        };
 
-                        buffer.edit(deletions, deletion_autoindent_mode, cx);
-                        buffer.edit(insertions, insertion_autoindent_mode, cx);
-                    })
+                    buffer.edit(deletions, deletion_autoindent_mode, cx);
+                    buffer.edit(insertions, insertion_autoindent_mode, cx);
+                })
             }
 
             cx.emit(Event::ExcerptsEdited {
@@ -1064,7 +1059,7 @@ impl MultiBuffer {
             edits: Vec<(Range<usize>, Arc<str>)>,
             cx: &mut Context<MultiBuffer>,
         ) {
-            if this.read_only() || this.buffers.borrow().is_empty() {
+            if this.read_only() || this.buffers.is_empty() {
                 return;
             }
 
@@ -1088,11 +1083,9 @@ impl MultiBuffer {
                     ranges.push(edit.range);
                 }
 
-                this.buffers.borrow()[&buffer_id]
-                    .buffer
-                    .update(cx, |buffer, cx| {
-                        buffer.autoindent_ranges(ranges, cx);
-                    })
+                this.buffers[&buffer_id].buffer.update(cx, |buffer, cx| {
+                    buffer.autoindent_ranges(ranges, cx);
+                })
             }
 
             cx.emit(Event::ExcerptsEdited {
@@ -1135,7 +1128,7 @@ impl MultiBuffer {
             return buffer.update(cx, |buffer, _| buffer.start_transaction_at(now));
         }
 
-        for BufferState { buffer, .. } in self.buffers.borrow().values() {
+        for BufferState { buffer, .. } in self.buffers.values() {
             buffer.update(cx, |buffer, _| buffer.start_transaction_at(now));
         }
         self.history.start_transaction(now)
@@ -1167,7 +1160,7 @@ impl MultiBuffer {
         }
 
         let mut buffer_transactions = HashMap::default();
-        for BufferState { buffer, .. } in self.buffers.borrow().values() {
+        for BufferState { buffer, .. } in self.buffers.values() {
             if let Some(transaction_id) =
                 buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
             {
@@ -1197,11 +1190,10 @@ impl MultiBuffer {
 
         let mut ranges = Vec::new();
         let snapshot = self.read(cx);
-        let buffers = self.buffers.borrow();
         let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
 
         for (buffer_id, buffer_transaction) in &transaction.buffer_transactions {
-            let Some(buffer_state) = buffers.get(buffer_id) else {
+            let Some(buffer_state) = self.buffers.get(buffer_id) else {
                 continue;
             };
 
@@ -1254,7 +1246,7 @@ impl MultiBuffer {
                 if let Some(destination_buffer_transaction_id) =
                     destination.buffer_transactions.get(&buffer_id)
                 {
-                    if let Some(state) = self.buffers.borrow().get(&buffer_id) {
+                    if let Some(state) = self.buffers.get(&buffer_id) {
                         state.buffer.update(cx, |buffer, _| {
                             buffer.merge_transactions(
                                 buffer_transaction_id,
@@ -1273,7 +1265,7 @@ impl MultiBuffer {
 
     pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
         self.history.finalize_last_transaction();
-        for BufferState { buffer, .. } in self.buffers.borrow().values() {
+        for BufferState { buffer, .. } in self.buffers.values() {
             buffer.update(cx, |buffer, _| {
                 buffer.finalize_last_transaction();
             });
@@ -1345,7 +1337,7 @@ impl MultiBuffer {
             }
         }
 
-        for (buffer_id, buffer_state) in self.buffers.borrow().iter() {
+        for (buffer_id, buffer_state) in self.buffers.iter() {
             if !selections_by_buffer.contains_key(buffer_id) {
                 buffer_state
                     .buffer
@@ -1354,32 +1346,30 @@ impl MultiBuffer {
         }
 
         for (buffer_id, mut selections) in selections_by_buffer {
-            self.buffers.borrow()[&buffer_id]
-                .buffer
-                .update(cx, |buffer, cx| {
-                    selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer));
-                    let mut selections = selections.into_iter().peekable();
-                    let merged_selections = Arc::from_iter(iter::from_fn(|| {
-                        let mut selection = selections.next()?;
-                        while let Some(next_selection) = selections.peek() {
-                            if selection.end.cmp(&next_selection.start, buffer).is_ge() {
-                                let next_selection = selections.next().unwrap();
-                                if next_selection.end.cmp(&selection.end, buffer).is_ge() {
-                                    selection.end = next_selection.end;
-                                }
-                            } else {
-                                break;
+            self.buffers[&buffer_id].buffer.update(cx, |buffer, cx| {
+                selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer));
+                let mut selections = selections.into_iter().peekable();
+                let merged_selections = Arc::from_iter(iter::from_fn(|| {
+                    let mut selection = selections.next()?;
+                    while let Some(next_selection) = selections.peek() {
+                        if selection.end.cmp(&next_selection.start, buffer).is_ge() {
+                            let next_selection = selections.next().unwrap();
+                            if next_selection.end.cmp(&selection.end, buffer).is_ge() {
+                                selection.end = next_selection.end;
                             }
+                        } else {
+                            break;
                         }
-                        Some(selection)
-                    }));
-                    buffer.set_active_selections(merged_selections, line_mode, cursor_shape, cx);
-                });
+                    }
+                    Some(selection)
+                }));
+                buffer.set_active_selections(merged_selections, line_mode, cursor_shape, cx);
+            });
         }
     }
 
     pub fn remove_active_selections(&self, cx: &mut Context<Self>) {
-        for buffer in self.buffers.borrow().values() {
+        for buffer in self.buffers.values() {
             buffer
                 .buffer
                 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
@@ -1394,7 +1384,7 @@ impl MultiBuffer {
             while let Some(transaction) = self.history.pop_undo() {
                 let mut undone = false;
                 for (buffer_id, buffer_transaction_id) in &mut transaction.buffer_transactions {
-                    if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(buffer_id) {
+                    if let Some(BufferState { buffer, .. }) = self.buffers.get(buffer_id) {
                         undone |= buffer.update(cx, |buffer, cx| {
                             let undo_to = *buffer_transaction_id;
                             if let Some(entry) = buffer.peek_undo_stack() {
@@ -1427,7 +1417,7 @@ impl MultiBuffer {
         while let Some(transaction) = self.history.pop_redo() {
             let mut redone = false;
             for (buffer_id, buffer_transaction_id) in &mut transaction.buffer_transactions {
-                if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(buffer_id) {
+                if let Some(BufferState { buffer, .. }) = self.buffers.get(buffer_id) {
                     redone |= buffer.update(cx, |buffer, cx| {
                         let redo_to = *buffer_transaction_id;
                         if let Some(entry) = buffer.peek_redo_stack() {
@@ -1451,7 +1441,7 @@ impl MultiBuffer {
             buffer.update(cx, |buffer, cx| buffer.undo_transaction(transaction_id, cx));
         } else if let Some(transaction) = self.history.remove_from_undo(transaction_id) {
             for (buffer_id, transaction_id) in &transaction.buffer_transactions {
-                if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(buffer_id) {
+                if let Some(BufferState { buffer, .. }) = self.buffers.get(buffer_id) {
                     buffer.update(cx, |buffer, cx| {
                         buffer.undo_transaction(*transaction_id, cx)
                     });
@@ -1467,7 +1457,7 @@ impl MultiBuffer {
             });
         } else if let Some(transaction) = self.history.forget(transaction_id) {
             for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions {
-                if let Some(state) = self.buffers.borrow_mut().get_mut(&buffer_id) {
+                if let Some(state) = self.buffers.get_mut(&buffer_id) {
                     state.buffer.update(cx, |buffer, _| {
                         buffer.forget_transaction(buffer_transaction_id);
                     });
@@ -1571,12 +1561,7 @@ impl MultiBuffer {
                 continue;
             };
 
-            let Some(buffer) = self
-                .buffers
-                .borrow()
-                .get(buffer_id)
-                .map(|b| b.buffer.clone())
-            else {
+            let Some(buffer) = self.buffers.get(buffer_id).map(|b| b.buffer.clone()) else {
                 continue;
             };
 
@@ -1631,13 +1616,13 @@ impl MultiBuffer {
 
     pub fn set_anchored_excerpts_for_path(
         &self,
+        path_key: PathKey,
         buffer: Entity<Buffer>,
         ranges: Vec<Range<text::Anchor>>,
         context_line_count: u32,
         cx: &mut Context<Self>,
     ) -> Task<Vec<Range<Anchor>>> {
         let buffer_snapshot = buffer.read(cx).snapshot();
-        let path_key = PathKey::for_buffer(&buffer, cx);
         cx.spawn(async move |multi_buffer, cx| {
             let snapshot = buffer_snapshot.clone();
             let (excerpt_ranges, new, counts) = cx
@@ -1802,7 +1787,7 @@ impl MultiBuffer {
                     last.context.end = last.context.end.max(existing_range.end);
                     to_remove.push(*existing_id);
                     self.snapshot
-                        .borrow_mut()
+                        .get_mut()
                         .replaced_excerpts
                         .insert(*existing_id, *last_id);
                     existing_iter.next();
@@ -1852,7 +1837,7 @@ impl MultiBuffer {
                         let existing_id = existing_iter.next().unwrap();
                         let new_id = next_excerpt_id();
                         self.snapshot
-                            .borrow_mut()
+                            .get_mut()
                             .replaced_excerpts
                             .insert(existing_id, new_id);
                         to_remove.push(existing_id);
@@ -1941,15 +1926,16 @@ impl MultiBuffer {
         let buffer_snapshot = buffer.read(cx).snapshot();
         let buffer_id = buffer_snapshot.remote_id();
 
-        let mut buffers = self.buffers.borrow_mut();
-        let buffer_state = buffers.entry(buffer_id).or_insert_with(|| {
+        let buffer_state = self.buffers.entry(buffer_id).or_insert_with(|| {
             self.buffer_changed_since_sync.replace(true);
             buffer.update(cx, |buffer, _| {
                 buffer.record_changes(Rc::downgrade(&self.buffer_changed_since_sync));
             });
             BufferState {
-                last_version: buffer_snapshot.version().clone(),
-                last_non_text_state_update_count: buffer_snapshot.non_text_state_update_count(),
+                last_version: RefCell::new(buffer_snapshot.version().clone()),
+                last_non_text_state_update_count: Cell::new(
+                    buffer_snapshot.non_text_state_update_count(),
+                ),
                 excerpts: Default::default(),
                 _subscriptions: [
                     cx.observe(&buffer, |_, _, cx| cx.notify()),
@@ -1959,7 +1945,7 @@ impl MultiBuffer {
             }
         });
 
-        let mut snapshot = self.snapshot.borrow_mut();
+        let mut snapshot = self.snapshot.get_mut();
 
         let mut prev_locator = snapshot.excerpt_locator_for_id(prev_excerpt_id).clone();
         let mut new_excerpt_ids = mem::take(&mut snapshot.excerpt_ids);
@@ -2023,7 +2009,7 @@ impl MultiBuffer {
             snapshot.trailing_excerpt_update_count += 1;
         }
 
-        self.sync_diff_transforms(
+        let edits = Self::sync_diff_transforms(
             &mut snapshot,
             vec![Edit {
                 old: edit_start..edit_start,
@@ -2031,6 +2017,10 @@ impl MultiBuffer {
             }],
             DiffChangeKind::BufferEdited,
         );
+        if !edits.is_empty() {
+            self.subscriptions.publish(edits);
+        }
+
         cx.emit(Event::Edited {
             edited_buffer: None,
         });
@@ -2045,15 +2035,10 @@ impl MultiBuffer {
     pub fn clear(&mut self, cx: &mut Context<Self>) {
         self.sync(cx);
         let ids = self.excerpt_ids();
-        let removed_buffer_ids = self
-            .buffers
-            .borrow_mut()
-            .drain()
-            .map(|(id, _)| id)
-            .collect();
+        let removed_buffer_ids = self.buffers.drain().map(|(id, _)| id).collect();
         self.excerpts_by_path.clear();
         self.paths_by_excerpt.clear();
-        let mut snapshot = self.snapshot.borrow_mut();
+        let mut snapshot = self.snapshot.get_mut();
         let start = ExcerptOffset::new(0);
         let prev_len = ExcerptOffset::new(snapshot.excerpts.summary().text.len);
         snapshot.excerpts = Default::default();
@@ -2063,7 +2048,7 @@ impl MultiBuffer {
         snapshot.has_conflict = false;
         snapshot.replaced_excerpts.clear();
 
-        self.sync_diff_transforms(
+        let edits = Self::sync_diff_transforms(
             &mut snapshot,
             vec![Edit {
                 old: start..prev_len,
@@ -2071,6 +2056,9 @@ impl MultiBuffer {
             }],
             DiffChangeKind::BufferEdited,
         );
+        if !edits.is_empty() {
+            self.subscriptions.publish(edits);
+        }
         cx.emit(Event::Edited {
             edited_buffer: None,
         });
@@ -2088,9 +2076,8 @@ impl MultiBuffer {
     ) -> Vec<(ExcerptId, ExcerptRange<text::Anchor>)> {
         let mut excerpts = Vec::new();
         let snapshot = self.read(cx);
-        let buffers = self.buffers.borrow();
         let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(());
-        if let Some(locators) = buffers.get(&buffer_id).map(|state| &state.excerpts) {
+        if let Some(locators) = self.buffers.get(&buffer_id).map(|state| &state.excerpts) {
             for locator in locators {
                 cursor.seek_forward(&Some(locator), Bias::Left);
                 if let Some(excerpt) = cursor.item()
@@ -2106,7 +2093,6 @@ impl MultiBuffer {
 
     pub fn excerpt_ranges_for_buffer(&self, buffer_id: BufferId, cx: &App) -> Vec<Range<Point>> {
         let snapshot = self.read(cx);
-        let buffers = self.buffers.borrow();
         let mut excerpts = snapshot
             .excerpts
             .cursor::<Dimensions<Option<&Locator>, ExcerptDimension<Point>>>(());
@@ -2114,7 +2100,8 @@ impl MultiBuffer {
             .diff_transforms
             .cursor::<Dimensions<ExcerptDimension<Point>, OutputDimension<Point>>>(());
         diff_transforms.next();
-        let locators = buffers
+        let locators = self
+            .buffers
             .get(&buffer_id)
             .into_iter()
             .flat_map(|state| &state.excerpts);
@@ -2175,12 +2162,7 @@ impl MultiBuffer {
             .map(|excerpt| {
                 (
                     excerpt.id,
-                    self.buffers
-                        .borrow()
-                        .get(&excerpt.buffer_id)
-                        .unwrap()
-                        .buffer
-                        .clone(),
+                    self.buffers.get(&excerpt.buffer_id).unwrap().buffer.clone(),
                     excerpt.range.context.clone(),
                 )
             })
@@ -2204,11 +2186,7 @@ impl MultiBuffer {
         let snapshot = self.read(cx);
         let (buffer, offset) = snapshot.point_to_buffer_offset(point)?;
         Some((
-            self.buffers
-                .borrow()
-                .get(&buffer.remote_id())?
-                .buffer
-                .clone(),
+            self.buffers.get(&buffer.remote_id())?.buffer.clone(),
             offset,
         ))
     }
@@ -2223,11 +2201,7 @@ impl MultiBuffer {
         let (buffer, point, is_main_buffer) =
             snapshot.point_to_buffer_point(point.to_point(&snapshot))?;
         Some((
-            self.buffers
-                .borrow()
-                .get(&buffer.remote_id())?
-                .buffer
-                .clone(),
+            self.buffers.get(&buffer.remote_id())?.buffer.clone(),
             point,
             is_main_buffer,
         ))
@@ -2273,8 +2247,7 @@ impl MultiBuffer {
             return;
         }
 
-        let mut buffers = self.buffers.borrow_mut();
-        let mut snapshot = self.snapshot.borrow_mut();
+        let mut snapshot = self.snapshot.get_mut();
         let mut new_excerpts = SumTree::default();
         let mut cursor = snapshot
             .excerpts
@@ -2297,14 +2270,14 @@ impl MultiBuffer {
 
                 // Skip over the removed excerpt.
                 'remove_excerpts: loop {
-                    if let Some(buffer_state) = buffers.get_mut(&excerpt.buffer_id) {
+                    if let Some(buffer_state) = self.buffers.get_mut(&excerpt.buffer_id) {
                         buffer_state.excerpts.retain(|l| l != &excerpt.locator);
                         if buffer_state.excerpts.is_empty() {
                             log::debug!(
                                 "removing buffer and diff for buffer {}",
                                 excerpt.buffer_id
                             );
-                            buffers.remove(&excerpt.buffer_id);
+                            self.buffers.remove(&excerpt.buffer_id);
                             removed_buffer_ids.push(excerpt.buffer_id);
                         }
                     }
@@ -2355,7 +2328,10 @@ impl MultiBuffer {
             snapshot.trailing_excerpt_update_count += 1;
         }
 
-        self.sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited);
+        let edits = Self::sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited);
+        if !edits.is_empty() {
+            self.subscriptions.publish(edits);
+        }
         self.buffer_changed_since_sync.replace(true);
         cx.emit(Event::Edited {
             edited_buffer: None,
@@ -2372,12 +2348,11 @@ impl MultiBuffer {
         anchors: Anchors,
         cx: &mut Context<Self>,
     ) -> impl 'static + Future<Output = Result<()>> + use<Anchors> {
-        let borrow = self.buffers.borrow();
         let mut error = None;
         let mut futures = Vec::new();
         for anchor in anchors {
             if let Some(buffer_id) = anchor.buffer_id {
-                if let Some(buffer) = borrow.get(&buffer_id) {
+                if let Some(buffer) = self.buffers.get(&buffer_id) {
                     buffer.buffer.update(cx, |buffer, _| {
                         futures.push(buffer.wait_for_anchors([anchor.text_anchor]))
                     });
@@ -2407,12 +2382,7 @@ impl MultiBuffer {
     ) -> Option<(Entity<Buffer>, language::Anchor)> {
         let snapshot = self.read(cx);
         let anchor = snapshot.anchor_before(position);
-        let buffer = self
-            .buffers
-            .borrow()
-            .get(&anchor.buffer_id?)?
-            .buffer
-            .clone();
+        let buffer = self.buffers.get(&anchor.buffer_id?)?.buffer.clone();
         Some((buffer, anchor.text_anchor))
     }
 
@@ -2444,7 +2414,7 @@ impl MultiBuffer {
 
     fn buffer_diff_language_changed(&mut self, diff: Entity<BufferDiff>, cx: &mut Context<Self>) {
         self.sync(cx);
-        let mut snapshot = self.snapshot.borrow_mut();
+        let snapshot = self.snapshot.get_mut();
         let diff = diff.read(cx);
         let buffer_id = diff.buffer_id;
         let diff = diff.snapshot(cx);
@@ -2462,8 +2432,7 @@ impl MultiBuffer {
 
         let diff = diff.read(cx);
         let buffer_id = diff.buffer_id;
-        let buffers = self.buffers.borrow();
-        let Some(buffer_state) = buffers.get(&buffer_id) else {
+        let Some(buffer_state) = self.buffers.get(&buffer_id) else {
             return;
         };
 
@@ -2471,7 +2440,7 @@ impl MultiBuffer {
         let diff_change_range = range.to_offset(buffer);
 
         let new_diff = diff.snapshot(cx);
-        let mut snapshot = self.snapshot.borrow_mut();
+        let mut snapshot = self.snapshot.get_mut();
         let base_text_changed = snapshot
             .diffs
             .get(&buffer_id)
@@ -2515,13 +2484,16 @@ impl MultiBuffer {
             }
         }
 
-        self.sync_diff_transforms(
+        let edits = Self::sync_diff_transforms(
             &mut snapshot,
             excerpt_edits,
             DiffChangeKind::DiffUpdated {
                 base_changed: base_text_changed,
             },
         );
+        if !edits.is_empty() {
+            self.subscriptions.publish(edits);
+        }
         cx.emit(Event::Edited {
             edited_buffer: None,
         });
@@ -2529,19 +2501,17 @@ impl MultiBuffer {
 
     pub fn all_buffers(&self) -> HashSet<Entity<Buffer>> {
         self.buffers
-            .borrow()
             .values()
             .map(|state| state.buffer.clone())
             .collect()
     }
 
     pub fn all_buffer_ids(&self) -> Vec<BufferId> {
-        self.buffers.borrow().keys().copied().collect()
+        self.buffers.keys().copied().collect()
     }
 
     pub fn buffer(&self, buffer_id: BufferId) -> Option<Entity<Buffer>> {
         self.buffers
-            .borrow()
             .get(&buffer_id)
             .map(|state| state.buffer.clone())
     }
@@ -2583,10 +2553,7 @@ impl MultiBuffer {
     }
 
     pub fn for_each_buffer(&self, mut f: impl FnMut(&Entity<Buffer>)) {
-        self.buffers
-            .borrow()
-            .values()
-            .for_each(|state| f(&state.buffer))
+        self.buffers.values().for_each(|state| f(&state.buffer))
     }
 
     pub fn title<'a>(&'a self, cx: &'a App) -> Cow<'a, str> {
@@ -2655,7 +2622,7 @@ impl MultiBuffer {
 
     /// Preserve preview tabs containing this multibuffer until additional edits occur.
     pub fn refresh_preview(&self, cx: &mut Context<Self>) {
-        for buffer_state in self.buffers.borrow().values() {
+        for buffer_state in self.buffers.values() {
             buffer_state
                 .buffer
                 .update(cx, |buffer, _cx| buffer.refresh_preview());
@@ -2665,7 +2632,6 @@ impl MultiBuffer {
     /// Whether we should preserve the preview status of a tab containing this multi-buffer.
     pub fn preserve_preview(&self, cx: &App) -> bool {
         self.buffers
-            .borrow()
             .values()
             .all(|state| state.buffer.read(cx).preserve_preview())
     }
@@ -2694,7 +2660,7 @@ impl MultiBuffer {
     }
 
     pub fn set_all_diff_hunks_expanded(&mut self, cx: &mut Context<Self>) {
-        self.snapshot.borrow_mut().all_diff_hunks_expanded = true;
+        self.snapshot.get_mut().all_diff_hunks_expanded = true;
         self.expand_or_collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], true, cx);
     }
 
@@ -2703,7 +2669,7 @@ impl MultiBuffer {
     }
 
     pub fn set_all_diff_hunks_collapsed(&mut self, cx: &mut Context<Self>) {
-        self.snapshot.borrow_mut().all_diff_hunks_expanded = false;
+        self.snapshot.get_mut().all_diff_hunks_expanded = false;
         self.expand_or_collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], false, cx);
     }
 
@@ -2764,7 +2730,7 @@ impl MultiBuffer {
             return;
         }
         self.sync(cx);
-        let mut snapshot = self.snapshot.borrow_mut();
+        let mut snapshot = self.snapshot.get_mut();
         let mut excerpt_edits = Vec::new();
         let mut last_hunk_row = None;
         for (range, end_excerpt_id) in ranges {
@@ -2795,11 +2761,14 @@ impl MultiBuffer {
             }
         }
 
-        self.sync_diff_transforms(
+        let edits = Self::sync_diff_transforms(
             &mut snapshot,
             excerpt_edits,
             DiffChangeKind::ExpandOrCollapseHunks { expand },
         );
+        if !edits.is_empty() {
+            self.subscriptions.publish(edits);
+        }
         cx.emit(Event::DiffHunksToggled);
         cx.emit(Event::Edited {
             edited_buffer: None,
@@ -2833,7 +2802,7 @@ impl MultiBuffer {
     ) {
         self.sync(cx);
 
-        let mut snapshot = self.snapshot.borrow_mut();
+        let mut snapshot = self.snapshot.get_mut();
         let locator = snapshot.excerpt_locator_for_id(id);
         let mut new_excerpts = SumTree::default();
         let mut cursor = snapshot
@@ -2883,7 +2852,10 @@ impl MultiBuffer {
         drop(cursor);
         snapshot.excerpts = new_excerpts;
 
-        self.sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited);
+        let edits = Self::sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited);
+        if !edits.is_empty() {
+            self.subscriptions.publish(edits);
+        }
         cx.emit(Event::Edited {
             edited_buffer: None,
         });
@@ -2906,7 +2878,7 @@ impl MultiBuffer {
             self.expand_excerpts_with_paths(ids, line_count, direction, cx);
             return;
         }
-        let mut snapshot = self.snapshot.borrow_mut();
+        let mut snapshot = self.snapshot.get_mut();
 
         let ids = ids.into_iter().collect::<Vec<_>>();
         let locators = snapshot.excerpt_locators_for_ids(ids.iter().copied());
@@ -2987,7 +2959,10 @@ impl MultiBuffer {
         drop(cursor);
         snapshot.excerpts = new_excerpts;
 
-        self.sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited);
+        let edits = Self::sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited);
+        if !edits.is_empty() {
+            self.subscriptions.publish(edits);
+        }
         cx.emit(Event::Edited {
             edited_buffer: None,
         });
@@ -3008,18 +2983,19 @@ impl MultiBuffer {
         let mut has_deleted_file = false;
         let mut has_conflict = false;
         let mut edited = false;
-        let mut buffers = self.buffers.borrow_mut();
-        for buffer_state in buffers.values_mut() {
+        for buffer_state in self.buffers.values() {
             let buffer = buffer_state.buffer.read(cx);
             let version = buffer.version();
             let non_text_state_update_count = buffer.non_text_state_update_count();
 
-            let buffer_edited = version.changed_since(&buffer_state.last_version);
+            let buffer_edited = version.changed_since(&buffer_state.last_version.borrow());
             let buffer_non_text_state_updated =
-                non_text_state_update_count > buffer_state.last_non_text_state_update_count;
+                non_text_state_update_count > buffer_state.last_non_text_state_update_count.get();
             if buffer_edited || buffer_non_text_state_updated {
-                buffer_state.last_version = version;
-                buffer_state.last_non_text_state_update_count = non_text_state_update_count;
+                *buffer_state.last_version.borrow_mut() = version;
+                buffer_state
+                    .last_non_text_state_update_count
+                    .set(non_text_state_update_count);
                 excerpts_to_edit.extend(
                     buffer_state
                         .excerpts
@@ -3110,17 +3086,19 @@ impl MultiBuffer {
         drop(cursor);
         snapshot.excerpts = new_excerpts;
 
-        self.sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited);
+        let edits = Self::sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited);
+        if !edits.is_empty() {
+            self.subscriptions.publish(edits);
+        }
     }
 
     fn sync_diff_transforms(
-        &self,
         snapshot: &mut MultiBufferSnapshot,
         excerpt_edits: Vec<text::Edit<ExcerptOffset>>,
         change_kind: DiffChangeKind,
-    ) {
+    ) -> Vec<Edit<usize>> {
         if excerpt_edits.is_empty() {
-            return;
+            return vec![];
         }
 
         let mut excerpts = snapshot.excerpts.cursor::<ExcerptOffset>(());

crates/multi_buffer/src/multi_buffer_tests.rs 🔗

@@ -797,7 +797,13 @@ async fn test_set_anchored_excerpts_for_path(cx: &mut TestAppContext) {
     let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
     let anchor_ranges_1 = multibuffer
         .update(cx, |multibuffer, cx| {
-            multibuffer.set_anchored_excerpts_for_path(buffer_1.clone(), ranges_1, 2, cx)
+            multibuffer.set_anchored_excerpts_for_path(
+                PathKey::for_buffer(&buffer_1, cx),
+                buffer_1.clone(),
+                ranges_1,
+                2,
+                cx,
+            )
         })
         .await;
     let snapshot_1 = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
@@ -814,7 +820,13 @@ async fn test_set_anchored_excerpts_for_path(cx: &mut TestAppContext) {
     );
     let anchor_ranges_2 = multibuffer
         .update(cx, |multibuffer, cx| {
-            multibuffer.set_anchored_excerpts_for_path(buffer_2.clone(), ranges_2, 2, cx)
+            multibuffer.set_anchored_excerpts_for_path(
+                PathKey::for_buffer(&buffer_2, cx),
+                buffer_2.clone(),
+                ranges_2,
+                2,
+                cx,
+            )
         })
         .await;
     let snapshot_2 = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));

crates/project/src/git_store/conflict_set.rs 🔗

@@ -72,13 +72,15 @@ impl ConflictSetSnapshot {
             (None, None) => None,
             (None, Some(conflict)) => Some(conflict.range.start),
             (Some(conflict), None) => Some(conflict.range.start),
-            (Some(first), Some(second)) => Some(first.range.start.min(&second.range.start, buffer)),
+            (Some(first), Some(second)) => {
+                Some(*first.range.start.min(&second.range.start, buffer))
+            }
         };
         let end = match (old_conflicts.last(), new_conflicts.last()) {
             (None, None) => None,
             (None, Some(conflict)) => Some(conflict.range.end),
             (Some(first), None) => Some(first.range.end),
-            (Some(first), Some(second)) => Some(first.range.end.max(&second.range.end, buffer)),
+            (Some(first), Some(second)) => Some(*first.range.end.max(&second.range.end, buffer)),
         };
         ConflictSetUpdate {
             buffer_range: start.zip(end).map(|(start, end)| start..end),

crates/search/src/project_search.rs 🔗

@@ -8,7 +8,8 @@ use crate::{
 use anyhow::Context as _;
 use collections::HashMap;
 use editor::{
-    Anchor, Editor, EditorEvent, EditorSettings, MAX_TAB_TITLE_LEN, MultiBuffer, SelectionEffects,
+    Anchor, Editor, EditorEvent, EditorSettings, MAX_TAB_TITLE_LEN, MultiBuffer, PathKey,
+    SelectionEffects,
     actions::{Backtab, SelectAll, Tab},
     items::active_match_index,
     multibuffer_context_lines,
@@ -340,6 +341,7 @@ impl ProjectSearch {
                                 .into_iter()
                                 .map(|(buffer, ranges)| {
                                     excerpts.set_anchored_excerpts_for_path(
+                                        PathKey::for_buffer(&buffer, cx),
                                         buffer,
                                         ranges,
                                         multibuffer_context_lines(cx),

crates/text/src/anchor.rs 🔗

@@ -45,19 +45,19 @@ impl Anchor {
             .then_with(|| self.bias.cmp(&other.bias))
     }
 
-    pub fn min(&self, other: &Self, buffer: &BufferSnapshot) -> Self {
+    pub fn min<'a>(&'a self, other: &'a Self, buffer: &BufferSnapshot) -> &'a Self {
         if self.cmp(other, buffer).is_le() {
-            *self
+            self
         } else {
-            *other
+            other
         }
     }
 
-    pub fn max(&self, other: &Self, buffer: &BufferSnapshot) -> Self {
+    pub fn max<'a>(&'a self, other: &'a Self, buffer: &BufferSnapshot) -> &'a Self {
         if self.cmp(other, buffer).is_ge() {
-            *self
+            self
         } else {
-            *other
+            other
         }
     }