multi_buffer: Typed `MultiBufferOffset` (#42707)

Lukas Wirth created

This PR introduces a new `MultiBufferOffset` new type wrapping size. The
goal of this is to make it clear at the type level when we are
interacting with offsets of a multi buffer versus offsets of a language
/ text buffer. This improves readability of things quite a bit by making
it clear what kind of offsets one is working with while also reducing
accidental bugs by using the wrong kin of offset for the wrong API.

This PR also uncovered two minor bugs due to that.

Does not yet introduce the MultiBufferPoint equivalent, that is for a
follow up PR.

Release Notes:

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

Change summary

crates/agent_ui/src/acp/message_editor.rs                   |  26 
crates/agent_ui/src/acp/thread_view.rs                      |   5 
crates/agent_ui/src/agent_configuration.rs                  |  16 
crates/agent_ui/src/buffer_codegen.rs                       |   7 
crates/agent_ui/src/context_picker/completion_provider.rs   |   4 
crates/agent_ui/src/inline_assistant.rs                     |  17 
crates/agent_ui/src/inline_prompt_editor.rs                 |   4 
crates/agent_ui/src/text_thread_editor.rs                   |  58 
crates/assistant_text_thread/src/text_thread.rs             |   2 
crates/collab/src/tests/channel_buffer_tests.rs             |  14 
crates/collab/src/tests/editor_tests.rs                     |  61 
crates/collab/src/tests/following_tests.rs                  |  44 
crates/collab_ui/src/collab_panel.rs                        |   2 
crates/copilot/src/copilot_completion_provider.rs           |   5 
crates/debugger_ui/src/debugger_panel.rs                    |  30 
crates/debugger_ui/src/debugger_ui.rs                       |  13 
crates/debugger_ui/src/session/running/console.rs           |  20 
crates/diagnostics/src/diagnostics_tests.rs                 |   5 
crates/diagnostics/src/items.rs                             |  13 
crates/edit_prediction_button/src/edit_prediction_button.rs |  12 
crates/editor/benches/display_map.rs                        |   9 
crates/editor/src/display_map.rs                            |  50 
crates/editor/src/display_map/block_map.rs                  |  16 
crates/editor/src/display_map/custom_highlights.rs          |  38 
crates/editor/src/display_map/fold_map.rs                   | 247 +-
crates/editor/src/display_map/inlay_map.rs                  | 223 +
crates/editor/src/display_map/tab_map.rs                    |   5 
crates/editor/src/editor.rs                                 | 353 ++-
crates/editor/src/editor_tests.rs                           | 321 ++-
crates/editor/src/git/blame.rs                              |   2 
crates/editor/src/highlight_matching_bracket.rs             |   3 
crates/editor/src/hover_links.rs                            |  13 
crates/editor/src/hover_popover.rs                          |  24 
crates/editor/src/inlays/inlay_hints.rs                     |  18 
crates/editor/src/items.rs                                  |   8 
crates/editor/src/jsx_tag_auto_close.rs                     |  17 
crates/editor/src/linked_editing_ranges.rs                  |   5 
crates/editor/src/movement.rs                               |  33 
crates/editor/src/selections_collection.rs                  |  85 
crates/editor/src/signature_help.rs                         |  16 
crates/editor/src/test.rs                                   |  17 
crates/editor/src/test/editor_lsp_test_context.rs           |  39 
crates/editor/src/test/editor_test_context.rs               |  31 
crates/git_ui/src/commit_view.rs                            |   7 
crates/git_ui/src/git_panel.rs                              |   8 
crates/git_ui/src/project_diff.rs                           |   3 
crates/git_ui/src/text_diff_view.rs                         |   8 
crates/go_to_line/src/cursor_position.rs                    |   6 
crates/journal/src/journal.rs                               |   2 
crates/language/src/buffer.rs                               |   2 
crates/language_tools/src/lsp_log_view.rs                   |   4 
crates/language_tools/src/syntax_tree_view.rs               |  10 
crates/markdown_preview/src/markdown_preview_view.rs        |  25 
crates/multi_buffer/src/anchor.rs                           |  23 
crates/multi_buffer/src/multi_buffer.rs                     | 609 ++++++
crates/multi_buffer/src/multi_buffer_tests.rs               | 334 ++-
crates/multi_buffer/src/transaction.rs                      |  34 
crates/outline/src/outline.rs                               |   8 
crates/project_panel/src/project_panel.rs                   |   6 
crates/project_panel/src/project_panel_tests.rs             |  24 
crates/repl/src/repl_editor.rs                              |   6 
crates/rope/src/offset_utf16.rs                             |   1 
crates/rope/src/rope.rs                                     |  52 
crates/search/src/buffer_search.rs                          |  10 
crates/tasks_ui/src/tasks_ui.rs                             |   4 
crates/text/src/selection.rs                                |  12 
crates/text/src/subscription.rs                             |  43 
crates/text/src/text.rs                                     |   4 
crates/vim/src/helix.rs                                     |   8 
crates/vim/src/helix/boundary.rs                            |  81 
crates/vim/src/helix/duplicate.rs                           |   4 
crates/vim/src/helix/paste.rs                               |   2 
crates/vim/src/motion.rs                                    |  39 
crates/vim/src/normal/increment.rs                          |   9 
crates/vim/src/normal/paste.rs                              |  10 
crates/vim/src/object.rs                                    |  66 
crates/vim/src/surrounds.rs                                 |  12 
crates/vim/src/test.rs                                      |   7 
crates/vim/src/vim.rs                                       |   8 
crates/vim/src/visual.rs                                    |  12 
crates/zed/src/zed.rs                                       |  30 
81 files changed, 2,324 insertions(+), 1,140 deletions(-)

Detailed changes

crates/agent_ui/src/acp/message_editor.rs 🔗

@@ -13,7 +13,7 @@ use collections::{HashMap, HashSet};
 use editor::{
     Addon, Anchor, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement,
     EditorEvent, EditorMode, EditorSnapshot, EditorStyle, ExcerptId, FoldPlaceholder, Inlay,
-    MultiBuffer, ToOffset,
+    MultiBuffer, MultiBufferOffset, ToOffset,
     actions::Paste,
     code_context_menus::CodeContextMenu,
     display_map::{Crease, CreaseId, FoldId},
@@ -209,7 +209,7 @@ impl MessageEditor {
         let acp::AvailableCommandInput::Unstructured { mut hint } =
             available_command.input.clone()?;
 
-        let mut hint_pos = parsed_command.source_range.end + 1;
+        let mut hint_pos = MultiBufferOffset(parsed_command.source_range.end) + 1usize;
         if hint_pos > snapshot.len() {
             hint_pos = snapshot.len();
             hint.insert(0, ' ');
@@ -307,9 +307,9 @@ impl MessageEditor {
             return Task::ready(());
         };
         let excerpt_id = start_anchor.excerpt_id;
-        let end_anchor = snapshot
-            .buffer_snapshot()
-            .anchor_before(start_anchor.to_offset(&snapshot.buffer_snapshot()) + content_len + 1);
+        let end_anchor = snapshot.buffer_snapshot().anchor_before(
+            start_anchor.to_offset(&snapshot.buffer_snapshot()) + content_len + 1usize,
+        );
 
         let crease = if let MentionUri::File { abs_path } = &mention_uri
             && let Some(extension) = abs_path.extension()
@@ -739,8 +739,8 @@ impl MessageEditor {
                         };
 
                         let crease_range = crease.range().to_offset(&snapshot.buffer_snapshot());
-                        if crease_range.start > ix {
-                            let chunk = text[ix..crease_range.start].into();
+                        if crease_range.start.0 > ix {
+                            let chunk = text[ix..crease_range.start.0].into();
                             chunks.push(chunk);
                         }
                         let chunk = match mention {
@@ -808,7 +808,7 @@ impl MessageEditor {
                             }),
                         };
                         chunks.push(chunk);
-                        ix = crease_range.end;
+                        ix = crease_range.end.0;
                     }
 
                     if ix < text.len() {
@@ -862,7 +862,7 @@ impl MessageEditor {
                         let snapshot = editor.display_snapshot(cx);
                         let cursor = editor.selections.newest::<text::Point>(&snapshot).head();
                         let offset = cursor.to_offset(&snapshot);
-                        if offset > 0 {
+                        if offset.0 > 0 {
                             snapshot
                                 .buffer_snapshot()
                                 .reversed_chars_at(offset)
@@ -1132,7 +1132,7 @@ impl MessageEditor {
         let cursor_anchor = editor.selections.newest_anchor().head();
         let cursor_offset = cursor_anchor.to_offset(&editor_buffer.snapshot(cx));
         let anchor = buffer.update(cx, |buffer, _cx| {
-            buffer.anchor_before(cursor_offset.min(buffer.len()))
+            buffer.anchor_before(cursor_offset.0.min(buffer.len()))
         });
         let Some(workspace) = self.workspace.upgrade() else {
             return;
@@ -1258,7 +1258,7 @@ impl MessageEditor {
         });
 
         for (range, mention_uri, mention) in mentions {
-            let anchor = snapshot.anchor_before(range.start);
+            let anchor = snapshot.anchor_before(MultiBufferOffset(range.start));
             let Some((crease_id, tx)) = insert_crease_for_mention(
                 anchor.excerpt_id,
                 anchor.text_anchor,
@@ -1713,7 +1713,7 @@ mod tests {
     use agent::{HistoryStore, outline};
     use agent_client_protocol as acp;
     use assistant_text_thread::TextThreadStore;
-    use editor::{AnchorRangeExt as _, Editor, EditorMode};
+    use editor::{AnchorRangeExt as _, Editor, EditorMode, MultiBufferOffset};
     use fs::FakeFs;
     use futures::StreamExt as _;
     use gpui::{
@@ -2682,7 +2682,7 @@ mod tests {
         editor.display_map.update(cx, |display_map, cx| {
             display_map
                 .snapshot(cx)
-                .folds_in_range(0..snapshot.len())
+                .folds_in_range(MultiBufferOffset(0)..snapshot.len())
                 .map(|fold| fold.range.to_point(&snapshot))
                 .collect()
         })

crates/agent_ui/src/acp/thread_view.rs 🔗

@@ -6066,6 +6066,7 @@ pub(crate) mod tests {
     use acp_thread::StubAgentConnection;
     use agent_client_protocol::SessionId;
     use assistant_text_thread::TextThreadStore;
+    use editor::MultiBufferOffset;
     use fs::FakeFs;
     use gpui::{EventEmitter, SemanticVersion, TestAppContext, VisualTestContext};
     use project::Project;
@@ -7234,7 +7235,7 @@ pub(crate) mod tests {
                         Editor::for_buffer(buffer.clone(), Some(project.clone()), window, cx);
 
                     editor.change_selections(Default::default(), window, cx, |selections| {
-                        selections.select_ranges([8..15]);
+                        selections.select_ranges([MultiBufferOffset(8)..MultiBufferOffset(15)]);
                     });
 
                     editor
@@ -7296,7 +7297,7 @@ pub(crate) mod tests {
                         Editor::for_buffer(buffer.clone(), Some(project.clone()), window, cx);
 
                     editor.change_selections(Default::default(), window, cx, |selections| {
-                        selections.select_ranges([8..15]);
+                        selections.select_ranges([MultiBufferOffset(8)..MultiBufferOffset(15)]);
                     });
 
                     editor

crates/agent_ui/src/agent_configuration.rs 🔗

@@ -12,7 +12,7 @@ use client::zed_urls;
 use cloud_llm_client::{Plan, PlanV1, PlanV2};
 use collections::HashMap;
 use context_server::ContextServerId;
-use editor::{Editor, SelectionEffects, scroll::Autoscroll};
+use editor::{Editor, MultiBufferOffset, SelectionEffects, scroll::Autoscroll};
 use extension::ExtensionManifest;
 use extension_host::ExtensionStore;
 use fs::Fs;
@@ -1362,7 +1362,15 @@ async fn open_new_agent_servers_entry_in_settings_editor(
                 .map(|(range, _)| range.clone())
                 .collect::<Vec<_>>();
 
-            item.edit(edits, cx);
+            item.edit(
+                edits.into_iter().map(|(range, s)| {
+                    (
+                        MultiBufferOffset(range.start)..MultiBufferOffset(range.end),
+                        s,
+                    )
+                }),
+                cx,
+            );
             if let Some((unique_server_name, buffer)) =
                 unique_server_name.zip(item.buffer().read(cx).as_singleton())
             {
@@ -1375,7 +1383,9 @@ async fn open_new_agent_servers_entry_in_settings_editor(
                         window,
                         cx,
                         |selections| {
-                            selections.select_ranges(vec![range]);
+                            selections.select_ranges(vec![
+                                MultiBufferOffset(range.start)..MultiBufferOffset(range.end),
+                            ]);
                         },
                     );
                 }

crates/agent_ui/src/buffer_codegen.rs 🔗

@@ -429,7 +429,12 @@ impl CodegenAlternative {
 
         let prompt = self
             .builder
-            .generate_inline_transformation_prompt(user_prompt, language_name, buffer, range)
+            .generate_inline_transformation_prompt(
+                user_prompt,
+                language_name,
+                buffer,
+                range.start.0..range.end.0,
+            )
             .context("generating content prompt")?;
 
         let context_task = self.context_store.as_ref().and_then(|context_store| {

crates/agent_ui/src/context_picker/completion_provider.rs 🔗

@@ -1082,7 +1082,7 @@ impl MentionCompletion {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use editor::AnchorRangeExt;
+    use editor::{AnchorRangeExt, MultiBufferOffset};
     use gpui::{EventEmitter, FocusHandle, Focusable, TestAppContext, VisualTestContext};
     use project::{Project, ProjectPath};
     use serde_json::json;
@@ -1677,7 +1677,7 @@ mod tests {
         editor.display_map.update(cx, |display_map, cx| {
             display_map
                 .snapshot(cx)
-                .folds_in_range(0..snapshot.len())
+                .folds_in_range(MultiBufferOffset(0)..snapshot.len())
                 .map(|fold| fold.range.to_point(&snapshot))
                 .collect()
         })

crates/agent_ui/src/inline_assistant.rs 🔗

@@ -16,6 +16,7 @@ use agent_settings::AgentSettings;
 use anyhow::{Context as _, Result};
 use client::telemetry::Telemetry;
 use collections::{HashMap, HashSet, VecDeque, hash_map};
+use editor::MultiBufferOffset;
 use editor::RowExt;
 use editor::SelectionEffects;
 use editor::scroll::ScrollOffset;
@@ -803,7 +804,7 @@ impl InlineAssistant {
                 (
                     editor
                         .selections
-                        .newest::<usize>(&editor.display_snapshot(cx)),
+                        .newest::<MultiBufferOffset>(&editor.display_snapshot(cx)),
                     editor.buffer().read(cx).snapshot(cx),
                 )
             });
@@ -836,7 +837,7 @@ impl InlineAssistant {
                 (
                     editor
                         .selections
-                        .newest::<usize>(&editor.display_snapshot(cx)),
+                        .newest::<MultiBufferOffset>(&editor.display_snapshot(cx)),
                     editor.buffer().read(cx).snapshot(cx),
                 )
             });
@@ -853,12 +854,14 @@ impl InlineAssistant {
                     } else {
                         let distance_from_selection = assist_range
                             .start
-                            .abs_diff(selection.start)
-                            .min(assist_range.start.abs_diff(selection.end))
+                            .0
+                            .abs_diff(selection.start.0)
+                            .min(assist_range.start.0.abs_diff(selection.end.0))
                             + assist_range
                                 .end
-                                .abs_diff(selection.start)
-                                .min(assist_range.end.abs_diff(selection.end));
+                                .0
+                                .abs_diff(selection.start.0)
+                                .min(assist_range.end.0.abs_diff(selection.end.0));
                         match closest_assist_fallback {
                             Some((_, old_distance)) => {
                                 if distance_from_selection < old_distance {
@@ -935,7 +938,7 @@ impl InlineAssistant {
             EditorEvent::Edited { transaction_id } => {
                 let buffer = editor.read(cx).buffer().read(cx);
                 let edited_ranges =
-                    buffer.edited_ranges_for_transaction::<usize>(*transaction_id, cx);
+                    buffer.edited_ranges_for_transaction::<MultiBufferOffset>(*transaction_id, cx);
                 let snapshot = buffer.snapshot(cx);
 
                 for assist_id in editor_assists.assist_ids.clone() {

crates/agent_ui/src/inline_prompt_editor.rs 🔗

@@ -2,7 +2,7 @@ use agent::HistoryStore;
 use collections::{HashMap, VecDeque};
 use editor::actions::Paste;
 use editor::display_map::{CreaseId, EditorMargins};
-use editor::{Addon, AnchorRangeExt as _};
+use editor::{Addon, AnchorRangeExt as _, MultiBufferOffset};
 use editor::{
     ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
     actions::{MoveDown, MoveUp},
@@ -1165,7 +1165,7 @@ impl GenerationMode {
 /// Stored information that can be used to resurrect a context crease when creating an editor for a past message.
 #[derive(Clone, Debug)]
 pub struct MessageCrease {
-    pub range: Range<usize>,
+    pub range: Range<MultiBufferOffset>,
     pub icon_path: SharedString,
     pub label: SharedString,
     /// None for a deserialized message, Some otherwise.

crates/agent_ui/src/text_thread_editor.rs 🔗

@@ -9,8 +9,8 @@ use assistant_slash_commands::{DefaultSlashCommand, FileSlashCommand, selections
 use client::{proto, zed_urls};
 use collections::{BTreeSet, HashMap, HashSet, hash_map};
 use editor::{
-    Anchor, Editor, EditorEvent, MenuEditPredictionsPolicy, MultiBuffer, MultiBufferSnapshot,
-    RowExt, ToOffset as _, ToPoint,
+    Anchor, Editor, EditorEvent, MenuEditPredictionsPolicy, MultiBuffer, MultiBufferOffset,
+    MultiBufferSnapshot, RowExt, ToOffset as _, ToPoint,
     actions::{MoveToEndOfLine, Newline, ShowCompletions},
     display_map::{
         BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId,
@@ -390,7 +390,7 @@ impl TextThreadEditor {
                 let cursor = user_message
                     .start
                     .to_offset(self.text_thread.read(cx).buffer().read(cx));
-                cursor..cursor
+                MultiBufferOffset(cursor)..MultiBufferOffset(cursor)
             };
             self.editor.update(cx, |editor, cx| {
                 editor.change_selections(Default::default(), window, cx, |selections| {
@@ -431,7 +431,7 @@ impl TextThreadEditor {
         let cursors = self.cursors(cx);
         self.text_thread.update(cx, |text_thread, cx| {
             let messages = text_thread
-                .messages_for_offsets(cursors, cx)
+                .messages_for_offsets(cursors.into_iter().map(|cursor| cursor.0), cx)
                 .into_iter()
                 .map(|message| message.id)
                 .collect();
@@ -439,9 +439,11 @@ impl TextThreadEditor {
         });
     }
 
-    fn cursors(&self, cx: &mut App) -> Vec<usize> {
+    fn cursors(&self, cx: &mut App) -> Vec<MultiBufferOffset> {
         let selections = self.editor.update(cx, |editor, cx| {
-            editor.selections.all::<usize>(&editor.display_snapshot(cx))
+            editor
+                .selections
+                .all::<MultiBufferOffset>(&editor.display_snapshot(cx))
         });
         selections
             .into_iter()
@@ -1580,7 +1582,11 @@ impl TextThreadEditor {
     fn get_clipboard_contents(
         &mut self,
         cx: &mut Context<Self>,
-    ) -> (String, CopyMetadata, Vec<text::Selection<usize>>) {
+    ) -> (
+        String,
+        CopyMetadata,
+        Vec<text::Selection<MultiBufferOffset>>,
+    ) {
         let (mut selection, creases) = self.editor.update(cx, |editor, cx| {
             let mut selection = editor
                 .selections
@@ -1638,30 +1644,26 @@ impl TextThreadEditor {
 
         // If selection is empty, we want to copy the entire line
         if selection.range().is_empty() {
-            let snapshot = text_thread.buffer().read(cx).snapshot();
+            let snapshot = self.editor.read(cx).buffer().read(cx).snapshot(cx);
             let point = snapshot.offset_to_point(selection.range().start);
             selection.start = snapshot.point_to_offset(Point::new(point.row, 0));
             selection.end = snapshot
                 .point_to_offset(cmp::min(Point::new(point.row + 1, 0), snapshot.max_point()));
-            for chunk in text_thread
-                .buffer()
-                .read(cx)
-                .text_for_range(selection.range())
-            {
+            for chunk in snapshot.text_for_range(selection.range()) {
                 text.push_str(chunk);
             }
         } else {
             for message in text_thread.messages(cx) {
-                if message.offset_range.start >= selection.range().end {
+                if message.offset_range.start >= selection.range().end.0 {
                     break;
-                } else if message.offset_range.end >= selection.range().start {
-                    let range = cmp::max(message.offset_range.start, selection.range().start)
-                        ..cmp::min(message.offset_range.end, selection.range().end);
+                } else if message.offset_range.end >= selection.range().start.0 {
+                    let range = cmp::max(message.offset_range.start, selection.range().start.0)
+                        ..cmp::min(message.offset_range.end, selection.range().end.0);
                     if !range.is_empty() {
                         for chunk in text_thread.buffer().read(cx).text_for_range(range) {
                             text.push_str(chunk);
                         }
-                        if message.offset_range.end < selection.range().end {
+                        if message.offset_range.end < selection.range().end.0 {
                             text.push('\n');
                         }
                     }
@@ -1743,7 +1745,7 @@ impl TextThreadEditor {
             self.editor.update(cx, |editor, cx| {
                 let paste_position = editor
                     .selections
-                    .newest::<usize>(&editor.display_snapshot(cx))
+                    .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
                     .head();
                 editor.paste(action, window, cx);
 
@@ -1791,13 +1793,16 @@ impl TextThreadEditor {
                 editor.transact(window, cx, |editor, _window, cx| {
                     let edits = editor
                         .selections
-                        .all::<usize>(&editor.display_snapshot(cx))
+                        .all::<MultiBufferOffset>(&editor.display_snapshot(cx))
                         .into_iter()
                         .map(|selection| (selection.start..selection.end, "\n"));
                     editor.edit(edits, cx);
 
                     let snapshot = editor.buffer().read(cx).snapshot(cx);
-                    for selection in editor.selections.all::<usize>(&editor.display_snapshot(cx)) {
+                    for selection in editor
+                        .selections
+                        .all::<MultiBufferOffset>(&editor.display_snapshot(cx))
+                    {
                         image_positions.push(snapshot.anchor_before(selection.end));
                     }
                 });
@@ -1889,7 +1894,7 @@ impl TextThreadEditor {
                 let range = selection
                     .map(|endpoint| endpoint.to_offset(&buffer))
                     .range();
-                text_thread.split_message(range, cx);
+                text_thread.split_message(range.start.0..range.end.0, cx);
             }
         });
     }
@@ -2963,7 +2968,7 @@ pub fn make_lsp_adapter_delegate(
 #[cfg(test)]
 mod tests {
     use super::*;
-    use editor::SelectionEffects;
+    use editor::{MultiBufferOffset, SelectionEffects};
     use fs::FakeFs;
     use gpui::{App, TestAppContext, VisualTestContext};
     use indoc::indoc;
@@ -3169,15 +3174,16 @@ mod tests {
         text_thread: &Entity<TextThread>,
         message_ix: usize,
         cx: &mut TestAppContext,
-    ) -> Range<usize> {
-        text_thread.update(cx, |text_thread, cx| {
+    ) -> Range<MultiBufferOffset> {
+        let range = text_thread.update(cx, |text_thread, cx| {
             text_thread
                 .messages(cx)
                 .nth(message_ix)
                 .unwrap()
                 .anchor_range
                 .to_offset(&text_thread.buffer().read(cx).snapshot())
-        })
+        });
+        MultiBufferOffset(range.start)..MultiBufferOffset(range.end)
     }
 
     fn assert_copy_paste_text_thread_editor<T: editor::ToOffset>(

crates/assistant_text_thread/src/text_thread.rs 🔗

@@ -667,7 +667,7 @@ pub struct TextThread {
     buffer: Entity<Buffer>,
     pub(crate) parsed_slash_commands: Vec<ParsedSlashCommand>,
     invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
-    edits_since_last_parse: language::Subscription,
+    edits_since_last_parse: language::Subscription<usize>,
     slash_commands: Arc<SlashCommandWorkingSet>,
     pub(crate) slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
     thought_process_output_sections: Vec<ThoughtProcessOutputSection<language::Anchor>>,

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

@@ -7,7 +7,7 @@ use channel::ACKNOWLEDGE_DEBOUNCE_INTERVAL;
 use client::{Collaborator, ParticipantIndex, UserId};
 use collab_ui::channel_view::ChannelView;
 use collections::HashMap;
-use editor::{Anchor, Editor, ToOffset};
+use editor::{Anchor, Editor, MultiBufferOffset, ToOffset};
 use futures::future;
 use gpui::{BackgroundExecutor, Context, Entity, TestAppContext, Window};
 use rpc::{RECEIVE_TIMEOUT, proto::PeerId};
@@ -180,7 +180,7 @@ async fn test_channel_notes_participant_indices(
         notes.editor.update(cx, |editor, cx| {
             editor.insert("a", window, cx);
             editor.change_selections(Default::default(), window, cx, |selections| {
-                selections.select_ranges(vec![0..1]);
+                selections.select_ranges(vec![MultiBufferOffset(0)..MultiBufferOffset(1)]);
             });
         });
     });
@@ -190,7 +190,7 @@ async fn test_channel_notes_participant_indices(
             editor.move_down(&Default::default(), window, cx);
             editor.insert("b", window, cx);
             editor.change_selections(Default::default(), window, cx, |selections| {
-                selections.select_ranges(vec![1..2]);
+                selections.select_ranges(vec![MultiBufferOffset(1)..MultiBufferOffset(2)]);
             });
         });
     });
@@ -200,7 +200,7 @@ async fn test_channel_notes_participant_indices(
             editor.move_down(&Default::default(), window, cx);
             editor.insert("c", window, cx);
             editor.change_selections(Default::default(), window, cx, |selections| {
-                selections.select_ranges(vec![2..3]);
+                selections.select_ranges(vec![MultiBufferOffset(2)..MultiBufferOffset(3)]);
             });
         });
     });
@@ -287,12 +287,12 @@ async fn test_channel_notes_participant_indices(
 
     editor_a.update_in(cx_a, |editor, window, cx| {
         editor.change_selections(Default::default(), window, cx, |selections| {
-            selections.select_ranges(vec![0..1]);
+            selections.select_ranges(vec![MultiBufferOffset(0)..MultiBufferOffset(1)]);
         });
     });
     editor_b.update_in(cx_b, |editor, window, cx| {
         editor.change_selections(Default::default(), window, cx, |selections| {
-            selections.select_ranges(vec![2..3]);
+            selections.select_ranges(vec![MultiBufferOffset(2)..MultiBufferOffset(3)]);
         });
     });
     executor.run_until_parked();
@@ -327,7 +327,7 @@ fn assert_remote_selections(
             let end = s.selection.end.to_offset(snapshot.buffer_snapshot());
             let user_id = collaborators.get(&peer_id).unwrap().user_id;
             let participant_index = hub.user_participant_indices(cx).get(&user_id).copied();
-            (participant_index, start..end)
+            (participant_index, start.0..end.0)
         })
         .collect::<Vec<_>>();
     assert_eq!(

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

@@ -4,7 +4,8 @@ use crate::{
 };
 use call::ActiveCall;
 use editor::{
-    DocumentColorsRenderMode, Editor, FETCH_COLORS_DEBOUNCE_TIMEOUT, RowInfo, SelectionEffects,
+    DocumentColorsRenderMode, Editor, FETCH_COLORS_DEBOUNCE_TIMEOUT, MultiBufferOffset, RowInfo,
+    SelectionEffects,
     actions::{
         ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst,
         ExpandMacroRecursively, MoveToEnd, Redo, Rename, SelectAll, ToggleCodeActions, Undo,
@@ -381,7 +382,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
     // Type a completion trigger character as the guest.
     editor_b.update_in(cx_b, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([13..13])
+            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
         });
         editor.handle_input(".", window, cx);
     });
@@ -503,7 +504,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
     // resolved
     editor_b.update_in(cx_b, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([46..46])
+            s.select_ranges([MultiBufferOffset(46)..MultiBufferOffset(46)])
         });
         editor.handle_input("; a", window, cx);
         editor.handle_input(".", window, cx);
@@ -601,7 +602,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
     // Add another completion trigger to test the second language server
     editor_b.update_in(cx_b, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([68..68])
+            s.select_ranges([MultiBufferOffset(68)..MultiBufferOffset(68)])
         });
         editor.handle_input("; b", window, cx);
         editor.handle_input(".", window, cx);
@@ -950,7 +951,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
     // Move cursor to a location that can be renamed.
     let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([7..7])
+            s.select_ranges([MultiBufferOffset(7)..MultiBufferOffset(7)])
         });
         editor.rename(&Rename, window, cx).unwrap()
     });
@@ -977,17 +978,17 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
         let buffer = editor.buffer().read(cx).snapshot(cx);
         assert_eq!(
             rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
-            6..9
+            MultiBufferOffset(6)..MultiBufferOffset(9)
         );
         rename.editor.update(cx, |rename_editor, cx| {
-            let rename_selection = rename_editor.selections.newest::<usize>(&rename_editor.display_snapshot(cx));
+            let rename_selection = rename_editor.selections.newest::<MultiBufferOffset>(&rename_editor.display_snapshot(cx));
             assert_eq!(
                 rename_selection.range(),
-                0..3,
+                MultiBufferOffset(0)..MultiBufferOffset(3),
                 "Rename that was triggered from zero selection caret, should propose the whole word."
             );
             rename_editor.buffer().update(cx, |rename_buffer, cx| {
-                rename_buffer.edit([(0..3, "THREE")], None, cx);
+                rename_buffer.edit([(MultiBufferOffset(0)..MultiBufferOffset(3), "THREE")], None, cx);
             });
         });
     });
@@ -998,7 +999,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
     });
     let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([7..8])
+            s.select_ranges([MultiBufferOffset(7)..MultiBufferOffset(8)])
         });
         editor.rename(&Rename, window, cx).unwrap()
     });
@@ -1025,16 +1026,16 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
         let buffer = editor.buffer().read(cx).snapshot(cx);
         let lsp_rename_start = rename.range.start.to_offset(&buffer);
         let lsp_rename_end = rename.range.end.to_offset(&buffer);
-        assert_eq!(lsp_rename_start..lsp_rename_end, 6..9);
+        assert_eq!(lsp_rename_start..lsp_rename_end, MultiBufferOffset(6)..MultiBufferOffset(9));
         rename.editor.update(cx, |rename_editor, cx| {
-            let rename_selection = rename_editor.selections.newest::<usize>(&rename_editor.display_snapshot(cx));
+            let rename_selection = rename_editor.selections.newest::<MultiBufferOffset>(&rename_editor.display_snapshot(cx));
             assert_eq!(
                 rename_selection.range(),
-                1..2,
+                MultiBufferOffset(1)..MultiBufferOffset(2),
                 "Rename that was triggered from a selection, should have the same selection range in the rename proposal"
             );
             rename_editor.buffer().update(cx, |rename_buffer, cx| {
-                rename_buffer.edit([(0..lsp_rename_end - lsp_rename_start, "THREE")], None, cx);
+                rename_buffer.edit([(MultiBufferOffset(0)..MultiBufferOffset(lsp_rename_end - lsp_rename_start), "THREE")], None, cx);
             });
         });
     });
@@ -1237,7 +1238,7 @@ async fn test_slow_lsp_server(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
     // Move cursor to a location, this should trigger the code lens call.
     editor_b.update_in(cx_b, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([7..7])
+            s.select_ranges([MultiBufferOffset(7)..MultiBufferOffset(7)])
         });
     });
     let () = request_started_rx.next().await.unwrap();
@@ -1259,7 +1260,7 @@ async fn test_slow_lsp_server(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
 
     editor_b.update_in(cx_b, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([1..1])
+            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
         });
     });
     let () = request_started_rx.next().await.unwrap();
@@ -1281,7 +1282,7 @@ async fn test_slow_lsp_server(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
 
     editor_b.update_in(cx_b, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([2..2])
+            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
         });
     });
     let () = request_started_rx.next().await.unwrap();
@@ -1719,7 +1720,7 @@ async fn test_on_input_format_from_host_to_guest(
     cx_a.focus(&editor_a);
     editor_a.update_in(cx_a, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([13..13])
+            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
         });
         editor.handle_input(">", window, cx);
     });
@@ -1828,7 +1829,7 @@ async fn test_on_input_format_from_guest_to_host(
     cx_b.focus(&editor_b);
     editor_b.update_in(cx_b, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([13..13])
+            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
         });
         editor.handle_input(":", window, cx);
     });
@@ -2056,7 +2057,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
     let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
     editor_b.update_in(cx_b, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([13..13].clone())
+            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)].clone())
         });
         editor.handle_input(":", window, cx);
     });
@@ -2080,7 +2081,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
     let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
     editor_a.update_in(cx_a, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([13..13])
+            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
         });
         editor.handle_input("a change to increment both buffers' versions", window, cx);
     });
@@ -2520,7 +2521,7 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
 
     editor_a.update_in(cx_a, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([13..13].clone())
+            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)].clone())
         });
         editor.handle_input(":", window, cx);
     });
@@ -2957,7 +2958,7 @@ async fn test_lsp_pull_diagnostics(
     editor_a_main.update(cx_a, |editor, cx| {
         let snapshot = editor.buffer().read(cx).snapshot(cx);
         let all_diagnostics = snapshot
-            .diagnostics_in_range(0..snapshot.len())
+            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
             .collect::<Vec<_>>();
         assert_eq!(
             all_diagnostics.len(),
@@ -3086,7 +3087,7 @@ async fn test_lsp_pull_diagnostics(
     editor_a_main.update(cx_a, |editor, cx| {
         let snapshot = editor.buffer().read(cx).snapshot(cx);
         let all_diagnostics = snapshot
-            .diagnostics_in_range(0..snapshot.len())
+            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
             .collect::<Vec<_>>();
         assert_eq!(
             all_diagnostics.len(),
@@ -3133,7 +3134,7 @@ async fn test_lsp_pull_diagnostics(
     editor_b_main.update(cx_b, |editor, cx| {
         let snapshot = editor.buffer().read(cx).snapshot(cx);
         let all_diagnostics = snapshot
-            .diagnostics_in_range(0..snapshot.len())
+            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
             .collect::<Vec<_>>();
         assert_eq!(
             all_diagnostics.len(),
@@ -3180,7 +3181,7 @@ async fn test_lsp_pull_diagnostics(
     editor_b_lib.update(cx_b, |editor, cx| {
         let snapshot = editor.buffer().read(cx).snapshot(cx);
         let all_diagnostics = snapshot
-            .diagnostics_in_range(0..snapshot.len())
+            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
             .collect::<Vec<_>>();
         let expected_messages = [
             expected_pull_diagnostic_lib_message,
@@ -3247,7 +3248,7 @@ async fn test_lsp_pull_diagnostics(
         editor_b_lib.update(cx_b, |editor, cx| {
             let snapshot = editor.buffer().read(cx).snapshot(cx);
             let all_diagnostics = snapshot
-                .diagnostics_in_range(0..snapshot.len())
+                .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
                 .collect::<Vec<_>>();
             let expected_messages = [
                 expected_workspace_pull_diagnostics_lib_message,
@@ -3382,7 +3383,7 @@ async fn test_lsp_pull_diagnostics(
     editor_b_lib.update(cx_b, |editor, cx| {
         let snapshot = editor.buffer().read(cx).snapshot(cx);
         let all_diagnostics = snapshot
-            .diagnostics_in_range(0..snapshot.len())
+            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
             .collect::<Vec<_>>();
         let expected_messages = [
             expected_workspace_pull_diagnostics_lib_message,
@@ -3400,7 +3401,7 @@ async fn test_lsp_pull_diagnostics(
     editor_b_main.update(cx_b, |editor, cx| {
         let snapshot = editor.buffer().read(cx).snapshot(cx);
         let all_diagnostics = snapshot
-            .diagnostics_in_range(0..snapshot.len())
+            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
             .collect::<Vec<_>>();
         assert_eq!(all_diagnostics.len(), 2);
 
@@ -3419,7 +3420,7 @@ async fn test_lsp_pull_diagnostics(
     editor_a_main.update(cx_a, |editor, cx| {
         let snapshot = editor.buffer().read(cx).snapshot(cx);
         let all_diagnostics = snapshot
-            .diagnostics_in_range(0..snapshot.len())
+            .diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
             .collect::<Vec<_>>();
         assert_eq!(all_diagnostics.len(), 2);
         let expected_messages = [

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

@@ -6,7 +6,7 @@ use collab_ui::{
     channel_view::ChannelView,
     notifications::project_shared_notification::ProjectSharedNotification,
 };
-use editor::{Editor, MultiBuffer, PathKey, SelectionEffects};
+use editor::{Editor, MultiBuffer, MultiBufferOffset, PathKey, SelectionEffects};
 use gpui::{
     AppContext as _, BackgroundExecutor, BorrowAppContext, Entity, SharedString, TestAppContext,
     VisualContext, VisualTestContext, point,
@@ -124,7 +124,7 @@ async fn test_basic_following(
         editor.select_left(&Default::default(), window, cx);
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            vec![3..2]
+            vec![MultiBufferOffset(3)..MultiBufferOffset(2)]
         );
     });
     editor_a2.update_in(cx_a, |editor, window, cx| {
@@ -133,7 +133,7 @@ async fn test_basic_following(
         editor.select_left(&Default::default(), window, cx);
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            vec![2..1]
+            vec![MultiBufferOffset(2)..MultiBufferOffset(1)]
         );
     });
 
@@ -158,13 +158,13 @@ async fn test_basic_following(
         editor_b2.update(cx_b, |editor, cx| editor
             .selections
             .ranges(&editor.display_snapshot(cx))),
-        vec![2..1]
+        vec![MultiBufferOffset(2)..MultiBufferOffset(1)]
     );
     assert_eq!(
         editor_b1.update(cx_b, |editor, cx| editor
             .selections
             .ranges(&editor.display_snapshot(cx))),
-        vec![3..3]
+        vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
     );
 
     executor.run_until_parked();
@@ -386,7 +386,10 @@ async fn test_basic_following(
     // Changes to client A's editor are reflected on client B.
     editor_a1.update_in(cx_a, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([1..1, 2..2])
+            s.select_ranges([
+                MultiBufferOffset(1)..MultiBufferOffset(1),
+                MultiBufferOffset(2)..MultiBufferOffset(2),
+            ])
         });
     });
     executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
@@ -396,7 +399,10 @@ async fn test_basic_following(
     editor_b1.update(cx_b, |editor, cx| {
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            &[1..1, 2..2]
+            &[
+                MultiBufferOffset(1)..MultiBufferOffset(1),
+                MultiBufferOffset(2)..MultiBufferOffset(2)
+            ]
         );
     });
 
@@ -408,7 +414,7 @@ async fn test_basic_following(
 
     editor_a1.update_in(cx_a, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([3..3])
+            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
         });
         editor.set_scroll_position(point(0., 100.), window, cx);
     });
@@ -417,7 +423,7 @@ async fn test_basic_following(
     editor_b1.update(cx_b, |editor, cx| {
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            &[3..3]
+            &[MultiBufferOffset(3)..MultiBufferOffset(3)]
         );
     });
 
@@ -1694,7 +1700,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
     // b should follow a to position 1
     editor_a.update_in(cx_a, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([1..1])
+            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
         })
     });
     cx_a.executor()
@@ -1703,7 +1709,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
     editor_b.update(cx_b, |editor, cx| {
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            vec![1..1]
+            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
         )
     });
 
@@ -1719,7 +1725,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
     // b should not follow a to position 2
     editor_a.update_in(cx_a, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([2..2])
+            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
         })
     });
     cx_a.executor()
@@ -1728,7 +1734,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
     editor_b.update(cx_b, |editor, cx| {
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            vec![1..1]
+            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
         )
     });
     cx_b.update(|_, cx| {
@@ -1829,7 +1835,7 @@ async fn test_following_into_excluded_file(
         editor.select_left(&Default::default(), window, cx);
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            vec![3..2]
+            vec![MultiBufferOffset(3)..MultiBufferOffset(2)]
         );
     });
     editor_for_excluded_a.update_in(cx_a, |editor, window, cx| {
@@ -1838,7 +1844,7 @@ async fn test_following_into_excluded_file(
         editor.select_left(&Default::default(), window, cx);
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            vec![18..17]
+            vec![MultiBufferOffset(18)..MultiBufferOffset(17)]
         );
     });
 
@@ -1864,7 +1870,7 @@ async fn test_following_into_excluded_file(
         editor_for_excluded_b.update(cx_b, |editor, cx| editor
             .selections
             .ranges(&editor.display_snapshot(cx))),
-        vec![18..17]
+        vec![MultiBufferOffset(18)..MultiBufferOffset(17)]
     );
 
     editor_for_excluded_a.update_in(cx_a, |editor, window, cx| {
@@ -2040,7 +2046,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
         notes.editor.update(cx, |editor, cx| {
             editor.insert("Hello from A.", window, cx);
             editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
-                selections.select_ranges(vec![3..4]);
+                selections.select_ranges(vec![MultiBufferOffset(3)..MultiBufferOffset(4)]);
             });
         });
     });
@@ -2076,8 +2082,8 @@ async fn test_following_to_channel_notes_without_a_shared_project(
             assert_eq!(
                 editor
                     .selections
-                    .ranges::<usize>(&editor.display_snapshot(cx)),
-                &[3..4]
+                    .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx)),
+                &[MultiBufferOffset(3)..MultiBufferOffset(4)]
             );
         })
     });

crates/collab_ui/src/collab_panel.rs 🔗

@@ -1496,7 +1496,7 @@ impl CollabPanel {
 
     fn reset_filter_editor_text(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
         self.filter_editor.update(cx, |editor, cx| {
-            if editor.buffer().read(cx).len(cx) > 0 {
+            if editor.buffer().read(cx).len(cx).0 > 0 {
                 editor.set_text("", window, cx);
                 true
             } else {

crates/copilot/src/copilot_completion_provider.rs 🔗

@@ -270,7 +270,7 @@ fn common_prefix<T1: Iterator<Item = char>, T2: Iterator<Item = char>>(a: T1, b:
 mod tests {
     use super::*;
     use editor::{
-        Editor, ExcerptRange, MultiBuffer, SelectionEffects,
+        Editor, ExcerptRange, MultiBuffer, MultiBufferOffset, SelectionEffects,
         test::editor_lsp_test_context::EditorLspTestContext,
     };
     use fs::FakeFs;
@@ -1081,8 +1081,9 @@ mod tests {
             vec![complete_from_marker, replace_range_marker.clone()],
         );
 
+        let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
         let replace_range =
-            cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
+            cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
 
         let mut request =
             cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {

crates/debugger_ui/src/debugger_panel.rs 🔗

@@ -14,13 +14,12 @@ use collections::IndexMap;
 use dap::adapters::DebugAdapterName;
 use dap::{DapRegistry, StartDebuggingRequestArguments};
 use dap::{client::SessionId, debugger_settings::DebuggerSettings};
-use editor::Editor;
+use editor::{Editor, MultiBufferOffset, ToPoint};
 use gpui::{
     Action, App, AsyncWindowContext, ClipboardItem, Context, DismissEvent, Entity, EntityId,
     EventEmitter, FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task,
     WeakEntity, anchored, deferred,
 };
-use text::ToPoint as _;
 
 use itertools::Itertools as _;
 use language::Buffer;
@@ -1216,11 +1215,11 @@ impl DebugPanel {
         let mut last_offset = None;
         while let Some(mat) = matches.next() {
             if let Some(pos) = mat.captures.first().map(|m| m.node.byte_range().end) {
-                last_offset = Some(pos)
+                last_offset = Some(MultiBufferOffset(pos))
             }
         }
         let mut edits = Vec::new();
-        let mut cursor_position = 0;
+        let mut cursor_position = MultiBufferOffset(0);
 
         if let Some(pos) = last_offset {
             edits.push((pos..pos, format!(",\n{new_scenario}")));
@@ -1234,24 +1233,25 @@ impl DebugPanel {
 
             if let Some(mat) = matches.next() {
                 if let Some(pos) = mat.captures.first().map(|m| m.node.byte_range().end - 1) {
-                    edits.push((pos..pos, format!("\n{new_scenario}\n")));
-                    cursor_position = pos + "\n  ".len();
+                    edits.push((
+                        MultiBufferOffset(pos)..MultiBufferOffset(pos),
+                        format!("\n{new_scenario}\n"),
+                    ));
+                    cursor_position = MultiBufferOffset(pos) + "\n  ".len();
                 }
             } else {
-                edits.push((0..0, format!("[\n{}\n]", new_scenario)));
-                cursor_position = "[\n  ".len();
+                edits.push((
+                    MultiBufferOffset(0)..MultiBufferOffset(0),
+                    format!("[\n{}\n]", new_scenario),
+                ));
+                cursor_position = MultiBufferOffset("[\n  ".len());
             }
         }
         editor.transact(window, cx, |editor, window, cx| {
             editor.edit(edits, cx);
-            let snapshot = editor
-                .buffer()
-                .read(cx)
-                .as_singleton()
-                .unwrap()
-                .read(cx)
-                .snapshot();
+            let snapshot = editor.buffer().read(cx).read(cx);
             let point = cursor_position.to_point(&snapshot);
+            drop(snapshot);
             editor.go_to_singleton_buffer_point(point, window, cx);
         });
         Ok(editor.save(SaveOptions::default(), project, window, cx))

crates/debugger_ui/src/debugger_ui.rs 🔗

@@ -1,7 +1,7 @@
 use std::any::TypeId;
 
 use debugger_panel::DebugPanel;
-use editor::Editor;
+use editor::{Editor, MultiBufferOffsetUtf16};
 use gpui::{Action, App, DispatchPhase, EntityInputHandler, actions};
 use new_process_modal::{NewProcessModal, NewProcessMode};
 use onboarding_modal::DebuggerOnboardingModal;
@@ -390,11 +390,14 @@ pub fn init(cx: &mut App) {
                             maybe!({
                                 let text = editor
                                     .update(cx, |editor, cx| {
+                                        let range = editor
+                                            .selections
+                                            .newest::<MultiBufferOffsetUtf16>(
+                                                &editor.display_snapshot(cx),
+                                            )
+                                            .range();
                                         editor.text_for_range(
-                                            editor
-                                                .selections
-                                                .newest(&editor.display_snapshot(cx))
-                                                .range(),
+                                            range.start.0.0..range.end.0.0,
                                             &mut None,
                                             window,
                                             cx,

crates/debugger_ui/src/session/running/console.rs 🔗

@@ -8,7 +8,7 @@ use collections::HashMap;
 use dap::{CompletionItem, CompletionItemType, OutputEvent};
 use editor::{
     Bias, CompletionProvider, Editor, EditorElement, EditorMode, EditorStyle, ExcerptId,
-    SizingBehavior,
+    MultiBufferOffset, SizingBehavior,
 };
 use fuzzy::StringMatchCandidate;
 use gpui::{
@@ -161,7 +161,9 @@ impl Console {
     ) -> Task<Result<()>> {
         self.console.update(cx, |_, cx| {
             cx.spawn_in(window, async move |console, cx| {
-                let mut len = console.update(cx, |this, cx| this.buffer().read(cx).len(cx))?;
+                let mut len = console
+                    .update(cx, |this, cx| this.buffer().read(cx).len(cx))?
+                    .0;
                 let (output, spans, background_spans) = cx
                     .background_spawn(async move {
                         let mut all_spans = Vec::new();
@@ -227,8 +229,8 @@ impl Console {
                     for (range, color) in spans {
                         let Some(color) = color else { continue };
                         let start_offset = range.start;
-                        let range =
-                            buffer.anchor_after(range.start)..buffer.anchor_before(range.end);
+                        let range = buffer.anchor_after(MultiBufferOffset(range.start))
+                            ..buffer.anchor_before(MultiBufferOffset(range.end));
                         let style = HighlightStyle {
                             color: Some(terminal_view::terminal_element::convert_color(
                                 &color,
@@ -247,8 +249,8 @@ impl Console {
                     for (range, color) in background_spans {
                         let Some(color) = color else { continue };
                         let start_offset = range.start;
-                        let range =
-                            buffer.anchor_after(range.start)..buffer.anchor_before(range.end);
+                        let range = buffer.anchor_after(MultiBufferOffset(range.start))
+                            ..buffer.anchor_before(MultiBufferOffset(range.end));
                         console.highlight_background_key::<ConsoleAnsiHighlight>(
                             start_offset,
                             &[range],
@@ -961,7 +963,7 @@ fn color_fetcher(color: ansi::Color) -> fn(&Theme) -> Hsla {
 mod tests {
     use super::*;
     use crate::tests::init_test;
-    use editor::test::editor_test_context::EditorTestContext;
+    use editor::{MultiBufferOffset, test::editor_test_context::EditorTestContext};
     use gpui::TestAppContext;
     use language::Point;
 
@@ -993,8 +995,8 @@ mod tests {
         cx.update_editor(|editor, _, cx| {
             editor.edit(
                 vec![(
-                    snapshot.offset_for_anchor(&replace_range.start)
-                        ..snapshot.offset_for_anchor(&replace_range.end),
+                    MultiBufferOffset(snapshot.offset_for_anchor(&replace_range.start))
+                        ..MultiBufferOffset(snapshot.offset_for_anchor(&replace_range.end)),
                     replacement,
                 )],
                 cx,

crates/diagnostics/src/diagnostics_tests.rs 🔗

@@ -1,7 +1,7 @@
 use super::*;
 use collections::{HashMap, HashSet};
 use editor::{
-    DisplayPoint, EditorSettings, Inlay,
+    DisplayPoint, EditorSettings, Inlay, MultiBufferOffset,
     actions::{GoToDiagnostic, GoToPreviousDiagnostic, Hover, MoveToBeginning},
     display_map::DisplayRow,
     test::{
@@ -878,7 +878,8 @@ async fn test_random_diagnostics_with_inlays(cx: &mut TestAppContext, mut rng: S
                 diagnostics.editor.update(cx, |editor, cx| {
                     let snapshot = editor.snapshot(window, cx);
                     if !snapshot.buffer_snapshot().is_empty() {
-                        let position = rng.random_range(0..snapshot.buffer_snapshot().len());
+                        let position = rng
+                            .random_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len());
                         let position = snapshot.buffer_snapshot().clip_offset(position, Bias::Left);
                         log::info!(
                             "adding inlay at {position}/{}: {:?}",

crates/diagnostics/src/items.rs 🔗

@@ -1,6 +1,6 @@
 use std::time::Duration;
 
-use editor::Editor;
+use editor::{Editor, MultiBufferOffset};
 use gpui::{
     Context, Entity, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, Task,
     WeakEntity, Window,
@@ -171,14 +171,19 @@ impl DiagnosticIndicator {
             let buffer = editor.buffer().read(cx).snapshot(cx);
             let cursor_position = editor
                 .selections
-                .newest::<usize>(&editor.display_snapshot(cx))
+                .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
                 .head();
             (buffer, cursor_position)
         });
         let new_diagnostic = buffer
-            .diagnostics_in_range::<usize>(cursor_position..cursor_position)
+            .diagnostics_in_range::<MultiBufferOffset>(cursor_position..cursor_position)
             .filter(|entry| !entry.range.is_empty())
-            .min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
+            .min_by_key(|entry| {
+                (
+                    entry.diagnostic.severity,
+                    entry.range.end - entry.range.start,
+                )
+            })
             .map(|entry| entry.diagnostic);
         if new_diagnostic != self.current_diagnostic.as_ref() {
             let new_diagnostic = new_diagnostic.cloned();

crates/edit_prediction_button/src/edit_prediction_button.rs 🔗

@@ -3,7 +3,9 @@ use client::{Client, UserStore, zed_urls};
 use cloud_llm_client::UsageLimit;
 use codestral::CodestralCompletionProvider;
 use copilot::{Copilot, Status};
-use editor::{Editor, SelectionEffects, actions::ShowEditPrediction, scroll::Autoscroll};
+use editor::{
+    Editor, MultiBufferOffset, SelectionEffects, actions::ShowEditPrediction, scroll::Autoscroll,
+};
 use feature_flags::{FeatureFlagAppExt, PredictEditsRateCompletionsFeatureFlag};
 use fs::Fs;
 use gpui::{
@@ -1107,7 +1109,12 @@ async fn open_disabled_globs_setting_in_editor(
             });
 
             if !edits.is_empty() {
-                item.edit(edits, cx);
+                item.edit(
+                    edits
+                        .into_iter()
+                        .map(|(r, s)| (MultiBufferOffset(r.start)..MultiBufferOffset(r.end), s)),
+                    cx,
+                );
             }
 
             let text = item.buffer().read(cx).snapshot(cx).text();
@@ -1122,6 +1129,7 @@ async fn open_disabled_globs_setting_in_editor(
                     .map(|inner_match| inner_match.start()..inner_match.end())
             });
             if let Some(range) = range {
+                let range = MultiBufferOffset(range.start)..MultiBufferOffset(range.end);
                 item.change_selections(
                     SelectionEffects::scroll(Autoscroll::newest()),
                     window,

crates/editor/benches/display_map.rs 🔗

@@ -2,6 +2,7 @@ use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
 use editor::MultiBuffer;
 use gpui::TestDispatcher;
 use itertools::Itertools;
+use multi_buffer::MultiBufferOffset;
 use rand::{Rng, SeedableRng, rngs::StdRng};
 use std::num::NonZeroU32;
 use text::Bias;
@@ -24,7 +25,9 @@ fn to_tab_point_benchmark(c: &mut Criterion) {
         let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
         let (_, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
         let fold_point = fold_snapshot.to_fold_point(
-            inlay_snapshot.to_point(InlayOffset(rng.random_range(0..length))),
+            inlay_snapshot.to_point(InlayOffset(
+                rng.random_range(MultiBufferOffset(0)..MultiBufferOffset(length)),
+            )),
             Bias::Left,
         );
         let (_, snapshot) = TabMap::new(fold_snapshot, NonZeroU32::new(4).unwrap());
@@ -69,7 +72,9 @@ fn to_fold_point_benchmark(c: &mut Criterion) {
         let (_, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
 
         let fold_point = fold_snapshot.to_fold_point(
-            inlay_snapshot.to_point(InlayOffset(rng.random_range(0..length))),
+            inlay_snapshot.to_point(InlayOffset(
+                rng.random_range(MultiBufferOffset(0)..MultiBufferOffset(length)),
+            )),
             Bias::Left,
         );
 

crates/editor/src/display_map.rs 🔗

@@ -44,12 +44,10 @@ pub use invisibles::{is_invisible, replacement};
 
 use collections::{HashMap, HashSet};
 use gpui::{App, Context, Entity, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle};
-use language::{
-    OffsetUtf16, Point, Subscription as BufferSubscription, language_settings::language_settings,
-};
+use language::{Point, Subscription as BufferSubscription, language_settings::language_settings};
 use multi_buffer::{
-    Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
-    RowInfo, ToOffset, ToPoint,
+    Anchor, AnchorRangeExt, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16,
+    MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset, ToPoint,
 };
 use project::InlayId;
 use project::project_settings::DiagnosticSeverity;
@@ -104,7 +102,7 @@ type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHi
 pub struct DisplayMap {
     /// The buffer that we are displaying.
     buffer: Entity<MultiBuffer>,
-    buffer_subscription: BufferSubscription,
+    buffer_subscription: BufferSubscription<MultiBufferOffset>,
     /// Decides where the [`Inlay`]s should be displayed.
     inlay_map: InlayMap,
     /// Decides where the fold indicators should be and tracks parts of a source file that are currently folded.
@@ -198,7 +196,7 @@ impl DisplayMap {
     pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut Context<Self>) {
         self.fold(
             other
-                .folds_in_range(0..other.buffer_snapshot().len())
+                .folds_in_range(MultiBufferOffset(0)..other.buffer_snapshot().len())
                 .map(|fold| {
                     Crease::simple(
                         fold.range.to_offset(other.buffer_snapshot()),
@@ -794,7 +792,7 @@ impl DisplaySnapshot {
     }
 
     pub fn is_empty(&self) -> bool {
-        self.buffer_snapshot().len() == 0
+        self.buffer_snapshot().len() == MultiBufferOffset(0)
     }
 
     pub fn row_infos(&self, start_row: DisplayRow) -> impl Iterator<Item = RowInfo> + '_ {
@@ -1133,7 +1131,10 @@ impl DisplaySnapshot {
         })
     }
 
-    pub fn buffer_chars_at(&self, mut offset: usize) -> impl Iterator<Item = (char, usize)> + '_ {
+    pub fn buffer_chars_at(
+        &self,
+        mut offset: MultiBufferOffset,
+    ) -> impl Iterator<Item = (char, MultiBufferOffset)> + '_ {
         self.buffer_snapshot().chars_at(offset).map(move |ch| {
             let ret = (ch, offset);
             offset += ch.len_utf8();
@@ -1143,8 +1144,8 @@ impl DisplaySnapshot {
 
     pub fn reverse_buffer_chars_at(
         &self,
-        mut offset: usize,
-    ) -> impl Iterator<Item = (char, usize)> + '_ {
+        mut offset: MultiBufferOffset,
+    ) -> impl Iterator<Item = (char, MultiBufferOffset)> + '_ {
         self.buffer_snapshot()
             .reversed_chars_at(offset)
             .map(move |ch| {
@@ -1526,7 +1527,7 @@ impl DisplayPoint {
         map.display_point_to_point(self, Bias::Left)
     }
 
-    pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
+    pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> MultiBufferOffset {
         let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
         let tab_point = map.wrap_snapshot().to_tab_point(wrap_point);
         let fold_point = map.tab_snapshot().to_fold_point(tab_point, bias).0;
@@ -1536,13 +1537,13 @@ impl DisplayPoint {
     }
 }
 
-impl ToDisplayPoint for usize {
+impl ToDisplayPoint for MultiBufferOffset {
     fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
         map.point_to_display_point(self.to_point(map.buffer_snapshot()), Bias::Left)
     }
 }
 
-impl ToDisplayPoint for OffsetUtf16 {
+impl ToDisplayPoint for MultiBufferOffsetUtf16 {
     fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
         self.to_offset(map.buffer_snapshot()).to_display_point(map)
     }
@@ -1685,7 +1686,7 @@ pub mod tests {
                             let block_properties = (0..rng.random_range(1..=1))
                                 .map(|_| {
                                     let position = buffer.anchor_after(buffer.clip_offset(
-                                        rng.random_range(0..=buffer.len()),
+                                        rng.random_range(MultiBufferOffset(0)..=buffer.len()),
                                         Bias::Left,
                                     ));
 
@@ -1727,8 +1728,12 @@ pub mod tests {
                     for _ in 0..rng.random_range(1..=3) {
                         buffer.read_with(cx, |buffer, cx| {
                             let buffer = buffer.read(cx);
-                            let end = buffer.clip_offset(rng.random_range(0..=buffer.len()), Right);
-                            let start = buffer.clip_offset(rng.random_range(0..=end), Left);
+                            let end = buffer.clip_offset(
+                                rng.random_range(MultiBufferOffset(0)..=buffer.len()),
+                                Right,
+                            );
+                            let start = buffer
+                                .clip_offset(rng.random_range(MultiBufferOffset(0)..=end), Left);
                             ranges.push(start..end);
                         });
                     }
@@ -1954,7 +1959,7 @@ pub mod tests {
                 )
             );
 
-            let ix = snapshot.buffer_snapshot().text().find("seven").unwrap();
+            let ix = MultiBufferOffset(snapshot.buffer_snapshot().text().find("seven").unwrap());
             buffer.update(cx, |buffer, cx| {
                 buffer.edit([(ix..ix, "and ")], None, cx);
             });
@@ -2083,7 +2088,7 @@ pub mod tests {
                 &[],
                 vec![Inlay::edit_prediction(
                     0,
-                    buffer_snapshot.anchor_after(0),
+                    buffer_snapshot.anchor_after(MultiBufferOffset(0)),
                     "\n",
                 )],
                 cx,
@@ -2094,7 +2099,11 @@ pub mod tests {
         // Regression test: updating the display map does not crash when a
         // block is immediately followed by a multi-line inlay.
         buffer.update(cx, |buffer, cx| {
-            buffer.edit([(1..1, "b")], None, cx);
+            buffer.edit(
+                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
+                None,
+                cx,
+            );
         });
         map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\nab"));
     }
@@ -2694,6 +2703,7 @@ pub mod tests {
                 HighlightKey::Type(TypeId::of::<MyType>()),
                 highlighted_ranges
                     .into_iter()
+                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
                     .map(|range| {
                         buffer_snapshot.anchor_before(range.start)
                             ..buffer_snapshot.anchor_before(range.end)

crates/editor/src/display_map/block_map.rs 🔗

@@ -11,8 +11,8 @@ use collections::{Bound, HashMap, HashSet};
 use gpui::{AnyElement, App, EntityId, Pixels, Window};
 use language::{Patch, Point};
 use multi_buffer::{
-    Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferRow, MultiBufferSnapshot, RowInfo,
-    ToOffset, ToPoint as _,
+    Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferOffset, MultiBufferRow,
+    MultiBufferSnapshot, RowInfo, ToOffset, ToPoint as _,
 };
 use parking_lot::Mutex;
 use std::{
@@ -1208,7 +1208,7 @@ impl BlockMapWriter<'_> {
 
     pub fn remove_intersecting_replace_blocks(
         &mut self,
-        ranges: impl IntoIterator<Item = Range<usize>>,
+        ranges: impl IntoIterator<Item = Range<MultiBufferOffset>>,
         inclusive: bool,
     ) {
         let wrap_snapshot = self.0.wrap_snapshot.borrow();
@@ -1283,7 +1283,7 @@ impl BlockMapWriter<'_> {
 
     fn blocks_intersecting_buffer_range(
         &self,
-        range: Range<usize>,
+        range: Range<MultiBufferOffset>,
         inclusive: bool,
     ) -> &[Arc<CustomBlock>] {
         if range.is_empty() && !inclusive {
@@ -3043,8 +3043,10 @@ mod tests {
                     let block_properties = (0..block_count)
                         .map(|_| {
                             let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
-                            let offset =
-                                buffer.clip_offset(rng.random_range(0..=buffer.len()), Bias::Left);
+                            let offset = buffer.clip_offset(
+                                rng.random_range(MultiBufferOffset(0)..=buffer.len()),
+                                Bias::Left,
+                            );
                             let mut min_height = 0;
                             let placement = match rng.random_range(0..3) {
                                 0 => {
@@ -3244,7 +3246,7 @@ mod tests {
             // Note that this needs to be synced with the related section in BlockMap::sync
             expected_blocks.extend(block_map.header_and_footer_blocks(
                 &buffer_snapshot,
-                0..,
+                MultiBufferOffset(0)..,
                 &wraps_snapshot,
             ));
 

crates/editor/src/display_map/custom_highlights.rs 🔗

@@ -1,7 +1,7 @@
 use collections::BTreeMap;
 use gpui::HighlightStyle;
 use language::Chunk;
-use multi_buffer::{MultiBufferChunks, MultiBufferSnapshot, ToOffset as _};
+use multi_buffer::{MultiBufferChunks, MultiBufferOffset, MultiBufferSnapshot, ToOffset as _};
 use std::{
     cmp,
     iter::{self, Peekable},
@@ -14,7 +14,7 @@ use crate::display_map::{HighlightKey, TextHighlights};
 pub struct CustomHighlightsChunks<'a> {
     buffer_chunks: MultiBufferChunks<'a>,
     buffer_chunk: Option<Chunk<'a>>,
-    offset: usize,
+    offset: MultiBufferOffset,
     multibuffer_snapshot: &'a MultiBufferSnapshot,
 
     highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
@@ -24,14 +24,14 @@ pub struct CustomHighlightsChunks<'a> {
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 struct HighlightEndpoint {
-    offset: usize,
+    offset: MultiBufferOffset,
     tag: HighlightKey,
     style: Option<HighlightStyle>,
 }
 
 impl<'a> CustomHighlightsChunks<'a> {
     pub fn new(
-        range: Range<usize>,
+        range: Range<MultiBufferOffset>,
         language_aware: bool,
         text_highlights: Option<&'a TextHighlights>,
         multibuffer_snapshot: &'a MultiBufferSnapshot,
@@ -52,7 +52,7 @@ impl<'a> CustomHighlightsChunks<'a> {
         }
     }
 
-    pub fn seek(&mut self, new_range: Range<usize>) {
+    pub fn seek(&mut self, new_range: Range<MultiBufferOffset>) {
         self.highlight_endpoints =
             create_highlight_endpoints(&new_range, self.text_highlights, self.multibuffer_snapshot);
         self.offset = new_range.start;
@@ -63,7 +63,7 @@ impl<'a> CustomHighlightsChunks<'a> {
 }
 
 fn create_highlight_endpoints(
-    range: &Range<usize>,
+    range: &Range<MultiBufferOffset>,
     text_highlights: Option<&TextHighlights>,
     buffer: &MultiBufferSnapshot,
 ) -> iter::Peekable<vec::IntoIter<HighlightEndpoint>> {
@@ -117,7 +117,7 @@ impl<'a> Iterator for CustomHighlightsChunks<'a> {
     type Item = Chunk<'a>;
 
     fn next(&mut self) -> Option<Self::Item> {
-        let mut next_highlight_endpoint = usize::MAX;
+        let mut next_highlight_endpoint = MultiBufferOffset(usize::MAX);
         while let Some(endpoint) = self.highlight_endpoints.peek().copied() {
             if endpoint.offset <= self.offset {
                 if let Some(style) = endpoint.style {
@@ -224,20 +224,22 @@ mod tests {
             let range_count = rng.random_range(1..10);
             let text = buffer_snapshot.text();
             for _ in 0..range_count {
-                if buffer_snapshot.len() == 0 {
+                if buffer_snapshot.len() == MultiBufferOffset(0) {
                     continue;
                 }
 
-                let mut start = rng.random_range(0..=buffer_snapshot.len().saturating_sub(10));
+                let mut start = rng.random_range(
+                    MultiBufferOffset(0)..=buffer_snapshot.len().saturating_sub_usize(10),
+                );
 
-                while !text.is_char_boundary(start) {
-                    start = start.saturating_sub(1);
+                while !text.is_char_boundary(start.0) {
+                    start = start.saturating_sub_usize(1);
                 }
 
-                let end_end = buffer_snapshot.len().min(start + 100);
+                let end_end = buffer_snapshot.len().min(start + 100usize);
                 let mut end = rng.random_range(start..=end_end);
-                while !text.is_char_boundary(end) {
-                    end = end.saturating_sub(1);
+                while !text.is_char_boundary(end.0) {
+                    end = end.saturating_sub_usize(1);
                 }
 
                 if start < end {
@@ -253,8 +255,12 @@ mod tests {
         }
 
         // Get all chunks and verify their bitmaps
-        let chunks =
-            CustomHighlightsChunks::new(0..buffer_snapshot.len(), false, None, &buffer_snapshot);
+        let chunks = CustomHighlightsChunks::new(
+            MultiBufferOffset(0)..buffer_snapshot.len(),
+            false,
+            None,
+            &buffer_snapshot,
+        );
 
         for chunk in chunks {
             let chunk_text = chunk.text;

crates/editor/src/display_map/fold_map.rs 🔗

@@ -5,16 +5,17 @@ use super::{
     inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
 };
 use gpui::{AnyElement, App, ElementId, HighlightStyle, Pixels, Window};
-use language::{Edit, HighlightId, Point, TextSummary};
+use language::{Edit, HighlightId, Point};
 use multi_buffer::{
-    Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset,
+    Anchor, AnchorRangeExt, MBTextSummary, MultiBufferOffset, MultiBufferRow, MultiBufferSnapshot,
+    RowInfo, ToOffset,
 };
 use project::InlayId;
 use std::{
     any::TypeId,
     cmp::{self, Ordering},
     fmt, iter,
-    ops::{Add, AddAssign, Deref, DerefMut, Range, Sub},
+    ops::{Add, AddAssign, Deref, DerefMut, Range, Sub, SubAssign},
     sync::Arc,
     usize,
 };
@@ -261,7 +262,7 @@ impl FoldMapWriter<'_> {
         fold_ixs_to_delete.dedup();
 
         self.0.snapshot.folds = {
-            let mut cursor = self.0.snapshot.folds.cursor::<usize>(buffer);
+            let mut cursor = self.0.snapshot.folds.cursor::<MultiBufferOffset>(buffer);
             let mut folds = SumTree::new(buffer);
             for fold_ix in fold_ixs_to_delete {
                 folds.append(cursor.slice(&fold_ix, Bias::Right), buffer);
@@ -413,7 +414,7 @@ impl FoldMap {
 
             let mut new_transforms = SumTree::<Transform>::default();
             let mut cursor = self.snapshot.transforms.cursor::<InlayOffset>(());
-            cursor.seek(&InlayOffset(0), Bias::Right);
+            cursor.seek(&InlayOffset(MultiBufferOffset(0)), Bias::Right);
 
             while let Some(mut edit) = inlay_edits_iter.next() {
                 if let Some(item) = cursor.item()
@@ -436,7 +437,7 @@ impl FoldMap {
                 cursor.seek(&edit.old.end, Bias::Right);
                 cursor.next();
 
-                let mut delta = edit.new_len().0 as isize - edit.old_len().0 as isize;
+                let mut delta = edit.new_len() as isize - edit.old_len() as isize;
                 loop {
                     edit.old.end = *cursor.start();
 
@@ -446,7 +447,7 @@ impl FoldMap {
                         }
 
                         let next_edit = inlay_edits_iter.next().unwrap();
-                        delta += next_edit.new_len().0 as isize - next_edit.old_len().0 as isize;
+                        delta += next_edit.new_len() as isize - next_edit.old_len() as isize;
 
                         if next_edit.old.end >= edit.old.end {
                             edit.old.end = next_edit.old.end;
@@ -458,8 +459,9 @@ impl FoldMap {
                     }
                 }
 
-                edit.new.end =
-                    InlayOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize);
+                edit.new.end = InlayOffset(MultiBufferOffset(
+                    ((edit.new.start + edit.old_len()).0.0 as isize + delta) as usize,
+                ));
 
                 let anchor = inlay_snapshot
                     .buffer
@@ -522,7 +524,7 @@ impl FoldMap {
                         new_transforms.push(
                             Transform {
                                 summary: TransformSummary {
-                                    output: TextSummary::from(ELLIPSIS),
+                                    output: MBTextSummary::from(ELLIPSIS),
                                     input: inlay_snapshot
                                         .text_summary_for_range(fold_range.start..fold_range.end),
                                 },
@@ -579,7 +581,7 @@ impl FoldMap {
                         edit.old.start = old_transforms.start().0;
                     }
                     let old_start =
-                        old_transforms.start().1.0 + (edit.old.start - old_transforms.start().0).0;
+                        old_transforms.start().1.0 + (edit.old.start - old_transforms.start().0);
 
                     old_transforms.seek_forward(&edit.old.end, Bias::Right);
                     if old_transforms.item().is_some_and(|t| t.is_fold()) {
@@ -587,14 +589,14 @@ impl FoldMap {
                         edit.old.end = old_transforms.start().0;
                     }
                     let old_end =
-                        old_transforms.start().1.0 + (edit.old.end - old_transforms.start().0).0;
+                        old_transforms.start().1.0 + (edit.old.end - old_transforms.start().0);
 
                     new_transforms.seek(&edit.new.start, Bias::Left);
                     if new_transforms.item().is_some_and(|t| t.is_fold()) {
                         edit.new.start = new_transforms.start().0;
                     }
                     let new_start =
-                        new_transforms.start().1.0 + (edit.new.start - new_transforms.start().0).0;
+                        new_transforms.start().1.0 + (edit.new.start - new_transforms.start().0);
 
                     new_transforms.seek_forward(&edit.new.end, Bias::Right);
                     if new_transforms.item().is_some_and(|t| t.is_fold()) {
@@ -602,7 +604,7 @@ impl FoldMap {
                         edit.new.end = new_transforms.start().0;
                     }
                     let new_end =
-                        new_transforms.start().1.0 + (edit.new.end - new_transforms.start().0).0;
+                        new_transforms.start().1.0 + (edit.new.end - new_transforms.start().0);
 
                     fold_edits.push(FoldEdit {
                         old: FoldOffset(old_start)..FoldOffset(old_end),
@@ -649,9 +651,13 @@ impl FoldSnapshot {
 
     #[cfg(test)]
     pub fn text(&self) -> String {
-        self.chunks(FoldOffset(0)..self.len(), false, Highlights::default())
-            .map(|c| c.text)
-            .collect()
+        self.chunks(
+            FoldOffset(MultiBufferOffset(0))..self.len(),
+            false,
+            Highlights::default(),
+        )
+        .map(|c| c.text)
+        .collect()
     }
 
     #[cfg(test)]
@@ -659,8 +665,8 @@ impl FoldSnapshot {
         self.folds.items(&self.inlay_snapshot.buffer).len()
     }
 
-    pub fn text_summary_for_range(&self, range: Range<FoldPoint>) -> TextSummary {
-        let mut summary = TextSummary::default();
+    pub fn text_summary_for_range(&self, range: Range<FoldPoint>) -> MBTextSummary {
+        let mut summary = MBTextSummary::default();
 
         let mut cursor = self
             .transforms
@@ -670,7 +676,7 @@ impl FoldSnapshot {
             let start_in_transform = range.start.0 - cursor.start().0.0;
             let end_in_transform = cmp::min(range.end, cursor.end().0).0 - cursor.start().0.0;
             if let Some(placeholder) = transform.placeholder.as_ref() {
-                summary = TextSummary::from(
+                summary = MBTextSummary::from(
                     &placeholder.text
                         [start_in_transform.column as usize..end_in_transform.column as usize],
                 );
@@ -689,14 +695,14 @@ impl FoldSnapshot {
 
         if range.end > cursor.end().0 {
             cursor.next();
-            summary += &cursor
+            summary += cursor
                 .summary::<_, TransformSummary>(&range.end, Bias::Right)
                 .output;
             if let Some(transform) = cursor.item() {
                 let end_in_transform = range.end.0 - cursor.start().0.0;
                 if let Some(placeholder) = transform.placeholder.as_ref() {
                     summary +=
-                        TextSummary::from(&placeholder.text[..end_in_transform.column as usize]);
+                        MBTextSummary::from(&placeholder.text[..end_in_transform.column as usize]);
                 } else {
                     let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1);
                     let inlay_end = self
@@ -839,8 +845,8 @@ impl FoldSnapshot {
         transform_cursor.seek(&range.start, Bias::Right);
 
         let inlay_start = {
-            let overshoot = range.start.0 - transform_cursor.start().0.0;
-            transform_cursor.start().1 + InlayOffset(overshoot)
+            let overshoot = range.start - transform_cursor.start().0;
+            transform_cursor.start().1 + overshoot
         };
 
         let transform_end = transform_cursor.end();
@@ -851,8 +857,8 @@ impl FoldSnapshot {
         {
             inlay_start
         } else if range.end < transform_end.0 {
-            let overshoot = range.end.0 - transform_cursor.start().0.0;
-            transform_cursor.start().1 + InlayOffset(overshoot)
+            let overshoot = range.end - transform_cursor.start().0;
+            transform_cursor.start().1 + overshoot
         } else {
             transform_end.1
         };
@@ -921,7 +927,7 @@ impl FoldSnapshot {
     }
 }
 
-fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: TextSummary) {
+fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: MBTextSummary) {
     let mut did_merge = false;
     transforms.update_last(
         |last| {
@@ -950,13 +956,13 @@ fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: TextSummary) {
 fn intersecting_folds<'a>(
     inlay_snapshot: &'a InlaySnapshot,
     folds: &'a SumTree<Fold>,
-    range: Range<usize>,
+    range: Range<MultiBufferOffset>,
     inclusive: bool,
-) -> FilterCursor<'a, 'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize> {
+) -> FilterCursor<'a, 'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, MultiBufferOffset> {
     let buffer = &inlay_snapshot.buffer;
     let start = buffer.anchor_before(range.start.to_offset(buffer));
     let end = buffer.anchor_after(range.end.to_offset(buffer));
-    let mut cursor = folds.filter::<_, usize>(buffer, move |summary| {
+    let mut cursor = folds.filter::<_, MultiBufferOffset>(buffer, move |summary| {
         let start_cmp = start.cmp(&summary.max_end, buffer);
         let end_cmp = end.cmp(&summary.min_start, buffer);
 
@@ -1061,8 +1067,8 @@ impl Transform {
 
 #[derive(Clone, Debug, Default, Eq, PartialEq)]
 struct TransformSummary {
-    output: TextSummary,
-    input: TextSummary,
+    output: MBTextSummary,
+    input: MBTextSummary,
 }
 
 impl sum_tree::Item for Transform {
@@ -1079,8 +1085,8 @@ impl sum_tree::ContextLessSummary for TransformSummary {
     }
 
     fn add_summary(&mut self, other: &Self) {
-        self.input += &other.input;
-        self.output += &other.output;
+        self.input += other.input;
+        self.output += other.output;
     }
 }
 
@@ -1211,7 +1217,7 @@ impl sum_tree::SeekTarget<'_, FoldSummary, FoldRange> for FoldRange {
     }
 }
 
-impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
+impl<'a> sum_tree::Dimension<'a, FoldSummary> for MultiBufferOffset {
     fn zero(_cx: &MultiBufferSnapshot) -> Self {
         Default::default()
     }
@@ -1357,8 +1363,8 @@ impl FoldChunks<'_> {
         self.transform_cursor.seek(&range.start, Bias::Right);
 
         let inlay_start = {
-            let overshoot = range.start.0 - self.transform_cursor.start().0.0;
-            self.transform_cursor.start().1 + InlayOffset(overshoot)
+            let overshoot = range.start - self.transform_cursor.start().0;
+            self.transform_cursor.start().1 + overshoot
         };
 
         let transform_end = self.transform_cursor.end();
@@ -1370,8 +1376,8 @@ impl FoldChunks<'_> {
         {
             inlay_start
         } else if range.end < transform_end.0 {
-            let overshoot = range.end.0 - self.transform_cursor.start().0.0;
-            self.transform_cursor.start().1 + InlayOffset(overshoot)
+            let overshoot = range.end - self.transform_cursor.start().0;
+            self.transform_cursor.start().1 + overshoot
         } else {
             transform_end.1
         };
@@ -1423,8 +1429,8 @@ impl<'a> Iterator for FoldChunks<'a> {
             let transform_start = self.transform_cursor.start();
             let transform_end = self.transform_cursor.end();
             let inlay_end = if self.max_output_offset < transform_end.0 {
-                let overshoot = self.max_output_offset.0 - transform_start.0.0;
-                transform_start.1 + InlayOffset(overshoot)
+                let overshoot = self.max_output_offset - transform_start.0;
+                transform_start.1 + overshoot
             } else {
                 transform_end.1
             };
@@ -1441,15 +1447,15 @@ impl<'a> Iterator for FoldChunks<'a> {
         // Otherwise, take a chunk from the buffer's text.
         if let Some((buffer_chunk_start, mut inlay_chunk)) = self.inlay_chunk.clone() {
             let chunk = &mut inlay_chunk.chunk;
-            let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len());
+            let buffer_chunk_end = buffer_chunk_start + chunk.text.len();
             let transform_end = self.transform_cursor.end().1;
             let chunk_end = buffer_chunk_end.min(transform_end);
 
-            let bit_start = (self.inlay_offset - buffer_chunk_start).0;
-            let bit_end = (chunk_end - buffer_chunk_start).0;
+            let bit_start = self.inlay_offset - buffer_chunk_start;
+            let bit_end = chunk_end - buffer_chunk_start;
             chunk.text = &chunk.text[bit_start..bit_end];
 
-            let bit_end = (chunk_end - buffer_chunk_start).0;
+            let bit_end = chunk_end - buffer_chunk_start;
             let mask = 1u128.unbounded_shl(bit_end as u32).wrapping_sub(1);
 
             chunk.tabs = (chunk.tabs >> bit_start) & mask;
@@ -1483,7 +1489,7 @@ impl<'a> Iterator for FoldChunks<'a> {
 }
 
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct FoldOffset(pub usize);
+pub struct FoldOffset(pub MultiBufferOffset);
 
 impl FoldOffset {
     pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint {
@@ -1493,7 +1499,7 @@ impl FoldOffset {
         let overshoot = if item.is_none_or(|t| t.is_fold()) {
             Point::new(0, (self.0 - start.0.0) as u32)
         } else {
-            let inlay_offset = start.1.input.len + self.0 - start.0.0;
+            let inlay_offset = start.1.input.len + (self - start.0);
             let inlay_point = snapshot.inlay_snapshot.to_point(InlayOffset(inlay_offset));
             inlay_point.0 - start.1.input.lines
         };
@@ -1505,7 +1511,7 @@ impl FoldOffset {
         let (start, _, _) = snapshot
             .transforms
             .find::<Dimensions<FoldOffset, InlayOffset>, _>((), &self, Bias::Right);
-        let overshoot = self.0 - start.0.0;
+        let overshoot = self - start.0;
         InlayOffset(start.1.0 + overshoot)
     }
 }
@@ -1518,17 +1524,46 @@ impl Add for FoldOffset {
     }
 }
 
+impl Sub for FoldOffset {
+    type Output = <MultiBufferOffset as Sub>::Output;
+
+    fn sub(self, rhs: Self) -> Self::Output {
+        self.0 - rhs.0
+    }
+}
+
+impl<T> SubAssign<T> for FoldOffset
+where
+    MultiBufferOffset: SubAssign<T>,
+{
+    fn sub_assign(&mut self, rhs: T) {
+        self.0 -= rhs;
+    }
+}
+
+impl<T> Add<T> for FoldOffset
+where
+    MultiBufferOffset: Add<T, Output = MultiBufferOffset>,
+{
+    type Output = Self;
+
+    fn add(self, rhs: T) -> Self::Output {
+        Self(self.0 + rhs)
+    }
+}
+
 impl AddAssign for FoldOffset {
     fn add_assign(&mut self, rhs: Self) {
         self.0 += rhs.0;
     }
 }
 
-impl Sub for FoldOffset {
-    type Output = Self;
-
-    fn sub(self, rhs: Self) -> Self::Output {
-        Self(self.0 - rhs.0)
+impl<T> AddAssign<T> for FoldOffset
+where
+    MultiBufferOffset: AddAssign<T>,
+{
+    fn add_assign(&mut self, rhs: T) {
+        self.0 += rhs;
     }
 }
 
@@ -1538,7 +1573,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset {
     }
 
     fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
-        self.0 += &summary.output.len;
+        self.0 += summary.output.len;
     }
 }
 
@@ -1558,7 +1593,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
     }
 
     fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
-        self.0 += &summary.input.len;
+        self.0 += summary.input.len;
     }
 }
 
@@ -1596,12 +1631,12 @@ mod tests {
             edits,
             &[
                 FoldEdit {
-                    old: FoldOffset(2)..FoldOffset(16),
-                    new: FoldOffset(2)..FoldOffset(5),
+                    old: FoldOffset(MultiBufferOffset(2))..FoldOffset(MultiBufferOffset(16)),
+                    new: FoldOffset(MultiBufferOffset(2))..FoldOffset(MultiBufferOffset(5)),
                 },
                 FoldEdit {
-                    old: FoldOffset(18)..FoldOffset(29),
-                    new: FoldOffset(7)..FoldOffset(10)
+                    old: FoldOffset(MultiBufferOffset(18))..FoldOffset(MultiBufferOffset(29)),
+                    new: FoldOffset(MultiBufferOffset(7))..FoldOffset(MultiBufferOffset(10)),
                 },
             ]
         );
@@ -1626,12 +1661,12 @@ mod tests {
             edits,
             &[
                 FoldEdit {
-                    old: FoldOffset(0)..FoldOffset(1),
-                    new: FoldOffset(0)..FoldOffset(3),
+                    old: FoldOffset(MultiBufferOffset(0))..FoldOffset(MultiBufferOffset(1)),
+                    new: FoldOffset(MultiBufferOffset(0))..FoldOffset(MultiBufferOffset(3)),
                 },
                 FoldEdit {
-                    old: FoldOffset(6)..FoldOffset(6),
-                    new: FoldOffset(8)..FoldOffset(11),
+                    old: FoldOffset(MultiBufferOffset(6))..FoldOffset(MultiBufferOffset(6)),
+                    new: FoldOffset(MultiBufferOffset(8))..FoldOffset(MultiBufferOffset(11)),
                 },
             ]
         );
@@ -1668,15 +1703,24 @@ mod tests {
             let mut map = FoldMap::new(inlay_snapshot.clone()).0;
 
             let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
-            writer.fold(vec![(5..8, FoldPlaceholder::test())]);
+            writer.fold(vec![(
+                MultiBufferOffset(5)..MultiBufferOffset(8),
+                FoldPlaceholder::test(),
+            )]);
             let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
             assert_eq!(snapshot.text(), "abcde⋯ijkl");
 
             // Create an fold adjacent to the start of the first fold.
             let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
             writer.fold(vec![
-                (0..1, FoldPlaceholder::test()),
-                (2..5, FoldPlaceholder::test()),
+                (
+                    MultiBufferOffset(0)..MultiBufferOffset(1),
+                    FoldPlaceholder::test(),
+                ),
+                (
+                    MultiBufferOffset(2)..MultiBufferOffset(5),
+                    FoldPlaceholder::test(),
+                ),
             ]);
             let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
             assert_eq!(snapshot.text(), "⋯b⋯ijkl");
@@ -1684,8 +1728,14 @@ mod tests {
             // Create an fold adjacent to the end of the first fold.
             let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
             writer.fold(vec![
-                (11..11, FoldPlaceholder::test()),
-                (8..10, FoldPlaceholder::test()),
+                (
+                    MultiBufferOffset(11)..MultiBufferOffset(11),
+                    FoldPlaceholder::test(),
+                ),
+                (
+                    MultiBufferOffset(8)..MultiBufferOffset(10),
+                    FoldPlaceholder::test(),
+                ),
             ]);
             let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
             assert_eq!(snapshot.text(), "⋯b⋯kl");
@@ -1697,15 +1747,25 @@ mod tests {
             // Create two adjacent folds.
             let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
             writer.fold(vec![
-                (0..2, FoldPlaceholder::test()),
-                (2..5, FoldPlaceholder::test()),
+                (
+                    MultiBufferOffset(0)..MultiBufferOffset(2),
+                    FoldPlaceholder::test(),
+                ),
+                (
+                    MultiBufferOffset(2)..MultiBufferOffset(5),
+                    FoldPlaceholder::test(),
+                ),
             ]);
             let (snapshot, _) = map.read(inlay_snapshot, vec![]);
             assert_eq!(snapshot.text(), "⋯fghijkl");
 
             // Edit within one of the folds.
             let buffer_snapshot = buffer.update(cx, |buffer, cx| {
-                buffer.edit([(0..1, "12345")], None, cx);
+                buffer.edit(
+                    [(MultiBufferOffset(0)..MultiBufferOffset(1), "12345")],
+                    None,
+                    cx,
+                );
                 buffer.snapshot(cx)
             });
             let (inlay_snapshot, inlay_edits) =
@@ -1849,7 +1909,7 @@ mod tests {
             for fold_range in map.merged_folds().into_iter().rev() {
                 let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start);
                 let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end);
-                expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "⋯");
+                expected_text.replace_range(fold_inlay_start.0.0..fold_inlay_end.0.0, "⋯");
             }
 
             assert_eq!(snapshot.text(), expected_text);
@@ -1898,7 +1958,7 @@ mod tests {
                 .chars()
                 .count();
             let mut fold_point = FoldPoint::new(0, 0);
-            let mut fold_offset = FoldOffset(0);
+            let mut fold_offset = FoldOffset(MultiBufferOffset(0));
             let mut char_column = 0;
             for c in expected_text.chars() {
                 let inlay_point = fold_point.to_inlay_point(&snapshot);
@@ -1944,18 +2004,18 @@ mod tests {
 
             for _ in 0..5 {
                 let mut start = snapshot.clip_offset(
-                    FoldOffset(rng.random_range(0..=snapshot.len().0)),
+                    FoldOffset(rng.random_range(MultiBufferOffset(0)..=snapshot.len().0)),
                     Bias::Left,
                 );
                 let mut end = snapshot.clip_offset(
-                    FoldOffset(rng.random_range(0..=snapshot.len().0)),
+                    FoldOffset(rng.random_range(MultiBufferOffset(0)..=snapshot.len().0)),
                     Bias::Right,
                 );
                 if start > end {
                     mem::swap(&mut start, &mut end);
                 }
 
-                let text = &expected_text[start.0..end.0];
+                let text = &expected_text[start.0.0..end.0.0];
                 assert_eq!(
                     snapshot
                         .chunks(start..end, false, Highlights::default())
@@ -2004,9 +2064,12 @@ mod tests {
             }
 
             for _ in 0..5 {
-                let end =
-                    buffer_snapshot.clip_offset(rng.random_range(0..=buffer_snapshot.len()), Right);
-                let start = buffer_snapshot.clip_offset(rng.random_range(0..=end), Left);
+                let end = buffer_snapshot.clip_offset(
+                    rng.random_range(MultiBufferOffset(0)..=buffer_snapshot.len()),
+                    Right,
+                );
+                let start =
+                    buffer_snapshot.clip_offset(rng.random_range(MultiBufferOffset(0)..=end), Left);
                 let expected_folds = map
                     .snapshot
                     .folds
@@ -2046,7 +2109,7 @@ mod tests {
                 let bytes = start.to_offset(&snapshot)..end.to_offset(&snapshot);
                 assert_eq!(
                     snapshot.text_summary_for_range(lines),
-                    TextSummary::from(&text[bytes.start.0..bytes.end.0])
+                    MBTextSummary::from(&text[bytes.start.0.0..bytes.end.0.0])
                 )
             }
 
@@ -2054,8 +2117,8 @@ mod tests {
             for (snapshot, edits) in snapshot_edits.drain(..) {
                 let new_text = snapshot.text();
                 for edit in edits {
-                    let old_bytes = edit.new.start.0..edit.new.start.0 + edit.old_len().0;
-                    let new_bytes = edit.new.start.0..edit.new.end.0;
+                    let old_bytes = edit.new.start.0.0..edit.new.start.0.0 + edit.old_len();
+                    let new_bytes = edit.new.start.0.0..edit.new.end.0.0;
                     text.replace_range(old_bytes, &new_text[new_bytes]);
                 }
 
@@ -2126,7 +2189,7 @@ mod tests {
 
         // Get all chunks and verify their bitmaps
         let chunks = snapshot.chunks(
-            FoldOffset(0)..FoldOffset(snapshot.len().0),
+            FoldOffset(MultiBufferOffset(0))..FoldOffset(snapshot.len().0),
             false,
             Highlights::default(),
         );
@@ -2195,7 +2258,7 @@ mod tests {
     }
 
     impl FoldMap {
-        fn merged_folds(&self) -> Vec<Range<usize>> {
+        fn merged_folds(&self) -> Vec<Range<MultiBufferOffset>> {
             let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
             let buffer = &inlay_snapshot.buffer;
             let mut folds = self.snapshot.folds.items(buffer);
@@ -2236,8 +2299,12 @@ mod tests {
                     let buffer = &inlay_snapshot.buffer;
                     let mut to_unfold = Vec::new();
                     for _ in 0..rng.random_range(1..=3) {
-                        let end = buffer.clip_offset(rng.random_range(0..=buffer.len()), Right);
-                        let start = buffer.clip_offset(rng.random_range(0..=end), Left);
+                        let end = buffer.clip_offset(
+                            rng.random_range(MultiBufferOffset(0)..=buffer.len()),
+                            Right,
+                        );
+                        let start =
+                            buffer.clip_offset(rng.random_range(MultiBufferOffset(0)..=end), Left);
                         to_unfold.push(start..end);
                     }
                     let inclusive = rng.random();
@@ -2252,8 +2319,12 @@ mod tests {
                     let buffer = &inlay_snapshot.buffer;
                     let mut to_fold = Vec::new();
                     for _ in 0..rng.random_range(1..=2) {
-                        let end = buffer.clip_offset(rng.random_range(0..=buffer.len()), Right);
-                        let start = buffer.clip_offset(rng.random_range(0..=end), Left);
+                        let end = buffer.clip_offset(
+                            rng.random_range(MultiBufferOffset(0)..=buffer.len()),
+                            Right,
+                        );
+                        let start =
+                            buffer.clip_offset(rng.random_range(MultiBufferOffset(0)..=end), Left);
                         to_fold.push((start..end, FoldPlaceholder::test()));
                     }
                     log::info!("folding {:?}", to_fold);

crates/editor/src/display_map/inlay_map.rs 🔗

@@ -4,7 +4,10 @@ use crate::{
 };
 use collections::BTreeSet;
 use language::{Chunk, Edit, Point, TextSummary};
-use multi_buffer::{MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, RowInfo, ToOffset};
+use multi_buffer::{
+    MBTextSummary, MultiBufferOffset, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot,
+    RowInfo, ToOffset,
+};
 use project::InlayId;
 use std::{
     cmp,
@@ -42,7 +45,7 @@ impl std::ops::Deref for InlaySnapshot {
 
 #[derive(Clone, Debug)]
 enum Transform {
-    Isomorphic(TextSummary),
+    Isomorphic(MBTextSummary),
     Inlay(Inlay),
 }
 
@@ -56,8 +59,8 @@ impl sum_tree::Item for Transform {
                 output: *summary,
             },
             Transform::Inlay(inlay) => TransformSummary {
-                input: TextSummary::default(),
-                output: inlay.text().summary(),
+                input: MBTextSummary::default(),
+                output: MBTextSummary::from(inlay.text().summary()),
             },
         }
     }
@@ -65,8 +68,8 @@ impl sum_tree::Item for Transform {
 
 #[derive(Clone, Debug, Default)]
 struct TransformSummary {
-    input: TextSummary,
-    output: TextSummary,
+    input: MBTextSummary,
+    output: MBTextSummary,
 }
 
 impl sum_tree::ContextLessSummary for TransformSummary {
@@ -75,15 +78,15 @@ impl sum_tree::ContextLessSummary for TransformSummary {
     }
 
     fn add_summary(&mut self, other: &Self) {
-        self.input += &other.input;
-        self.output += &other.output;
+        self.input += other.input;
+        self.output += other.output;
     }
 }
 
 pub type InlayEdit = Edit<InlayOffset>;
 
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct InlayOffset(pub usize);
+pub struct InlayOffset(pub MultiBufferOffset);
 
 impl Add for InlayOffset {
     type Output = Self;
@@ -94,10 +97,30 @@ impl Add for InlayOffset {
 }
 
 impl Sub for InlayOffset {
-    type Output = Self;
+    type Output = <MultiBufferOffset as Sub>::Output;
 
     fn sub(self, rhs: Self) -> Self::Output {
-        Self(self.0 - rhs.0)
+        self.0 - rhs.0
+    }
+}
+
+impl<T> SubAssign<T> for InlayOffset
+where
+    MultiBufferOffset: SubAssign<T>,
+{
+    fn sub_assign(&mut self, rhs: T) {
+        self.0 -= rhs;
+    }
+}
+
+impl<T> Add<T> for InlayOffset
+where
+    MultiBufferOffset: Add<T, Output = MultiBufferOffset>,
+{
+    type Output = Self;
+
+    fn add(self, rhs: T) -> Self::Output {
+        Self(self.0 + rhs)
     }
 }
 
@@ -107,9 +130,12 @@ impl AddAssign for InlayOffset {
     }
 }
 
-impl SubAssign for InlayOffset {
-    fn sub_assign(&mut self, rhs: Self) {
-        self.0 -= rhs.0;
+impl<T> AddAssign<T> for InlayOffset
+where
+    MultiBufferOffset: AddAssign<T>,
+{
+    fn add_assign(&mut self, rhs: T) {
+        self.0 += rhs;
     }
 }
 
@@ -119,7 +145,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
     }
 
     fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
-        self.0 += &summary.output.len;
+        self.0 += summary.output.len;
     }
 }
 
@@ -152,13 +178,13 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
     }
 }
 
-impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
+impl<'a> sum_tree::Dimension<'a, TransformSummary> for MultiBufferOffset {
     fn zero(_cx: ()) -> Self {
         Default::default()
     }
 
     fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
-        *self += &summary.input.len;
+        *self += summary.input.len;
     }
 }
 
@@ -181,7 +207,7 @@ pub struct InlayBufferRows<'a> {
 }
 
 pub struct InlayChunks<'a> {
-    transforms: Cursor<'a, 'static, Transform, Dimensions<InlayOffset, usize>>,
+    transforms: Cursor<'a, 'static, Transform, Dimensions<InlayOffset, MultiBufferOffset>>,
     buffer_chunks: CustomHighlightsChunks<'a>,
     buffer_chunk: Option<Chunk<'a>>,
     inlay_chunks: Option<text::ChunkWithBitmaps<'a>>,
@@ -332,12 +358,12 @@ impl<'a> Iterator for InlayChunks<'a> {
                 let offset_in_inlay = self.output_offset - self.transforms.start().0;
                 if let Some((style, highlight)) = inlay_style_and_highlight {
                     let range = &highlight.range;
-                    if offset_in_inlay.0 < range.start {
-                        next_inlay_highlight_endpoint = range.start - offset_in_inlay.0;
-                    } else if offset_in_inlay.0 >= range.end {
+                    if offset_in_inlay < range.start {
+                        next_inlay_highlight_endpoint = range.start - offset_in_inlay;
+                    } else if offset_in_inlay >= range.end {
                         next_inlay_highlight_endpoint = usize::MAX;
                     } else {
-                        next_inlay_highlight_endpoint = range.end - offset_in_inlay.0;
+                        next_inlay_highlight_endpoint = range.end - offset_in_inlay;
                         highlight_style = highlight_style
                             .map(|highlight| highlight.highlight(*style))
                             .or_else(|| Some(*style));
@@ -350,7 +376,7 @@ impl<'a> Iterator for InlayChunks<'a> {
                     let start = offset_in_inlay;
                     let end = cmp::min(self.max_output_offset, self.transforms.end().0)
                         - self.transforms.start().0;
-                    let chunks = inlay.text().chunks_in_range(start.0..end.0);
+                    let chunks = inlay.text().chunks_in_range(start..end);
                     text::ChunkWithBitmaps(chunks)
                 });
                 let ChunkBitmaps {
@@ -488,7 +514,7 @@ impl InlayMap {
     pub fn sync(
         &mut self,
         buffer_snapshot: MultiBufferSnapshot,
-        mut buffer_edits: Vec<text::Edit<usize>>,
+        mut buffer_edits: Vec<text::Edit<MultiBufferOffset>>,
     ) -> (InlaySnapshot, Vec<InlayEdit>) {
         let snapshot = &mut self.snapshot;
 
@@ -519,7 +545,7 @@ impl InlayMap {
             let mut new_transforms = SumTree::default();
             let mut cursor = snapshot
                 .transforms
-                .cursor::<Dimensions<usize, InlayOffset>>(());
+                .cursor::<Dimensions<MultiBufferOffset, InlayOffset>>(());
             let mut buffer_edits_iter = buffer_edits.iter().peekable();
             while let Some(buffer_edit) = buffer_edits_iter.next() {
                 new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left), ());
@@ -531,11 +557,9 @@ impl InlayMap {
                 }
 
                 // Remove all the inlays and transforms contained by the edit.
-                let old_start =
-                    cursor.start().1 + InlayOffset(buffer_edit.old.start - cursor.start().0);
+                let old_start = cursor.start().1 + (buffer_edit.old.start - cursor.start().0);
                 cursor.seek(&buffer_edit.old.end, Bias::Right);
-                let old_end =
-                    cursor.start().1 + InlayOffset(buffer_edit.old.end - cursor.start().0);
+                let old_end = cursor.start().1 + (buffer_edit.old.end - cursor.start().0);
 
                 // Push the unchanged prefix.
                 let prefix_start = new_transforms.summary().input.len;
@@ -687,7 +711,10 @@ impl InlayMap {
         let snapshot = &mut self.snapshot;
         for i in 0..rng.random_range(1..=5) {
             if self.inlays.is_empty() || rng.random() {
-                let position = snapshot.buffer.random_byte_range(0, rng).start;
+                let position = snapshot
+                    .buffer
+                    .random_byte_range(MultiBufferOffset(0), rng)
+                    .start;
                 let bias = if rng.random() {
                     Bias::Left
                 } else {
@@ -740,9 +767,11 @@ impl InlayMap {
 
 impl InlaySnapshot {
     pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
-        let (start, _, item) = self
-            .transforms
-            .find::<Dimensions<InlayOffset, InlayPoint, usize>, _>((), &offset, Bias::Right);
+        let (start, _, item) = self.transforms.find::<Dimensions<
+            InlayOffset,
+            InlayPoint,
+            MultiBufferOffset,
+        >, _>((), &offset, Bias::Right);
         let overshoot = offset.0 - start.0.0;
         match item {
             Some(Transform::Isomorphic(_)) => {
@@ -801,22 +830,24 @@ impl InlaySnapshot {
             None => self.buffer.max_point(),
         }
     }
-    pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
-        let (start, _, item) =
-            self.transforms
-                .find::<Dimensions<InlayOffset, usize>, _>((), &offset, Bias::Right);
+    pub fn to_buffer_offset(&self, offset: InlayOffset) -> MultiBufferOffset {
+        let (start, _, item) = self
+            .transforms
+            .find::<Dimensions<InlayOffset, MultiBufferOffset>, _>((), &offset, Bias::Right);
         match item {
             Some(Transform::Isomorphic(_)) => {
                 let overshoot = offset - start.0;
-                start.1 + overshoot.0
+                start.1 + overshoot
             }
             Some(Transform::Inlay(_)) => start.1,
             None => self.buffer.len(),
         }
     }
 
-    pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
-        let mut cursor = self.transforms.cursor::<Dimensions<usize, InlayOffset>>(());
+    pub fn to_inlay_offset(&self, offset: MultiBufferOffset) -> InlayOffset {
+        let mut cursor = self
+            .transforms
+            .cursor::<Dimensions<MultiBufferOffset, InlayOffset>>(());
         cursor.seek(&offset, Bias::Left);
         loop {
             match cursor.item() {
@@ -973,14 +1004,16 @@ impl InlaySnapshot {
         }
     }
 
-    pub fn text_summary(&self) -> TextSummary {
+    pub fn text_summary(&self) -> MBTextSummary {
         self.transforms.summary().output
     }
 
-    pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
-        let mut summary = TextSummary::default();
+    pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> MBTextSummary {
+        let mut summary = MBTextSummary::default();
 
-        let mut cursor = self.transforms.cursor::<Dimensions<InlayOffset, usize>>(());
+        let mut cursor = self
+            .transforms
+            .cursor::<Dimensions<InlayOffset, MultiBufferOffset>>(());
         cursor.seek(&range.start, Bias::Right);
 
         let overshoot = range.start.0 - cursor.start().0.0;
@@ -996,7 +1029,12 @@ impl InlaySnapshot {
             Some(Transform::Inlay(inlay)) => {
                 let suffix_start = overshoot;
                 let suffix_end = cmp::min(cursor.end().0, range.end).0 - cursor.start().0.0;
-                summary = inlay.text().cursor(suffix_start).summary(suffix_end);
+                summary = MBTextSummary::from(
+                    inlay
+                        .text()
+                        .cursor(suffix_start)
+                        .summary::<TextSummary>(suffix_end),
+                );
                 cursor.next();
             }
             None => {}
@@ -1014,7 +1052,7 @@ impl InlaySnapshot {
                     let prefix_end = prefix_start + overshoot;
                     summary += self
                         .buffer
-                        .text_summary_for_range::<TextSummary, _>(prefix_start..prefix_end);
+                        .text_summary_for_range::<MBTextSummary, _>(prefix_start..prefix_end);
                 }
                 Some(Transform::Inlay(inlay)) => {
                     let prefix_end = overshoot;
@@ -1070,7 +1108,9 @@ impl InlaySnapshot {
         language_aware: bool,
         highlights: Highlights<'a>,
     ) -> InlayChunks<'a> {
-        let mut cursor = self.transforms.cursor::<Dimensions<InlayOffset, usize>>(());
+        let mut cursor = self
+            .transforms
+            .cursor::<Dimensions<InlayOffset, MultiBufferOffset>>(());
         cursor.seek(&range.start, Bias::Right);
 
         let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
@@ -1122,8 +1162,8 @@ impl InlaySnapshot {
     }
 }
 
-fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
-    if summary.len == 0 {
+fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: MBTextSummary) {
+    if summary.len == MultiBufferOffset(0) {
         return;
     }
 
@@ -1279,7 +1319,10 @@ mod tests {
             &[],
             vec![Inlay::mock_hint(
                 post_inc(&mut next_inlay_id),
-                buffer.read(cx).snapshot(cx).anchor_after(3),
+                buffer
+                    .read(cx)
+                    .snapshot(cx)
+                    .anchor_after(MultiBufferOffset(3)),
                 "|123|",
             )],
         );
@@ -1335,7 +1378,15 @@ mod tests {
 
         // Edits before or after the inlay should not affect it.
         buffer.update(cx, |buffer, cx| {
-            buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
+            buffer.edit(
+                [
+                    (MultiBufferOffset(2)..MultiBufferOffset(3), "x"),
+                    (MultiBufferOffset(3)..MultiBufferOffset(3), "y"),
+                    (MultiBufferOffset(4)..MultiBufferOffset(4), "z"),
+                ],
+                None,
+                cx,
+            )
         });
         let (inlay_snapshot, _) = inlay_map.sync(
             buffer.read(cx).snapshot(cx),
@@ -1344,7 +1395,13 @@ mod tests {
         assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
 
         // An edit surrounding the inlay should invalidate it.
-        buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
+        buffer.update(cx, |buffer, cx| {
+            buffer.edit(
+                [(MultiBufferOffset(4)..MultiBufferOffset(5), "D")],
+                None,
+                cx,
+            )
+        });
         let (inlay_snapshot, _) = inlay_map.sync(
             buffer.read(cx).snapshot(cx),
             buffer_edits.consume().into_inner(),
@@ -1356,12 +1413,18 @@ mod tests {
             vec![
                 Inlay::mock_hint(
                     post_inc(&mut next_inlay_id),
-                    buffer.read(cx).snapshot(cx).anchor_before(3),
+                    buffer
+                        .read(cx)
+                        .snapshot(cx)
+                        .anchor_before(MultiBufferOffset(3)),
                     "|123|",
                 ),
                 Inlay::edit_prediction(
                     post_inc(&mut next_inlay_id),
-                    buffer.read(cx).snapshot(cx).anchor_after(3),
+                    buffer
+                        .read(cx)
+                        .snapshot(cx)
+                        .anchor_after(MultiBufferOffset(3)),
                     "|456|",
                 ),
             ],
@@ -1369,7 +1432,13 @@ mod tests {
         assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
 
         // Edits ending where the inlay starts should not move it if it has a left bias.
-        buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
+        buffer.update(cx, |buffer, cx| {
+            buffer.edit(
+                [(MultiBufferOffset(3)..MultiBufferOffset(3), "JKL")],
+                None,
+                cx,
+            )
+        });
         let (inlay_snapshot, _) = inlay_map.sync(
             buffer.read(cx).snapshot(cx),
             buffer_edits.consume().into_inner(),
@@ -1571,17 +1640,26 @@ mod tests {
             vec![
                 Inlay::mock_hint(
                     post_inc(&mut next_inlay_id),
-                    buffer.read(cx).snapshot(cx).anchor_before(0),
+                    buffer
+                        .read(cx)
+                        .snapshot(cx)
+                        .anchor_before(MultiBufferOffset(0)),
                     "|123|\n",
                 ),
                 Inlay::mock_hint(
                     post_inc(&mut next_inlay_id),
-                    buffer.read(cx).snapshot(cx).anchor_before(4),
+                    buffer
+                        .read(cx)
+                        .snapshot(cx)
+                        .anchor_before(MultiBufferOffset(4)),
                     "|456|",
                 ),
                 Inlay::edit_prediction(
                     post_inc(&mut next_inlay_id),
-                    buffer.read(cx).snapshot(cx).anchor_before(7),
+                    buffer
+                        .read(cx)
+                        .snapshot(cx)
+                        .anchor_before(MultiBufferOffset(7)),
                     "\n|567|\n",
                 ),
             ],
@@ -1658,7 +1736,7 @@ mod tests {
                 .collect::<Vec<_>>();
             let mut expected_text = Rope::from(&buffer_snapshot.text());
             for (offset, inlay) in inlays.iter().rev() {
-                expected_text.replace(*offset..*offset, &inlay.text().to_string());
+                expected_text.replace(offset.0..offset.0, &inlay.text().to_string());
             }
             assert_eq!(inlay_snapshot.text(), expected_text.to_string());
 
@@ -1681,7 +1759,7 @@ mod tests {
             let mut text_highlights = TextHighlights::default();
             let text_highlight_count = rng.random_range(0_usize..10);
             let mut text_highlight_ranges = (0..text_highlight_count)
-                .map(|_| buffer_snapshot.random_byte_range(0, &mut rng))
+                .map(|_| buffer_snapshot.random_byte_range(MultiBufferOffset(0), &mut rng))
                 .collect::<Vec<_>>();
             text_highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
             log::info!("highlighting text ranges {text_highlight_ranges:?}");
@@ -1744,12 +1822,13 @@ mod tests {
             }
 
             for _ in 0..5 {
-                let mut end = rng.random_range(0..=inlay_snapshot.len().0);
+                let mut end = rng.random_range(0..=inlay_snapshot.len().0.0);
                 end = expected_text.clip_offset(end, Bias::Right);
                 let mut start = rng.random_range(0..=end);
                 start = expected_text.clip_offset(start, Bias::Right);
 
-                let range = InlayOffset(start)..InlayOffset(end);
+                let range =
+                    InlayOffset(MultiBufferOffset(start))..InlayOffset(MultiBufferOffset(end));
                 log::info!("calling inlay_snapshot.chunks({range:?})");
                 let actual_text = inlay_snapshot
                     .chunks(
@@ -1771,25 +1850,27 @@ mod tests {
                 );
 
                 assert_eq!(
-                    inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)),
-                    expected_text.slice(start..end).summary()
+                    inlay_snapshot.text_summary_for_range(
+                        InlayOffset(MultiBufferOffset(start))..InlayOffset(MultiBufferOffset(end))
+                    ),
+                    MBTextSummary::from(expected_text.slice(start..end).summary())
                 );
             }
 
             for edit in inlay_edits {
                 prev_inlay_text.replace_range(
-                    edit.new.start.0..edit.new.start.0 + edit.old_len().0,
-                    &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
+                    edit.new.start.0.0..edit.new.start.0.0 + edit.old_len(),
+                    &inlay_snapshot.text()[edit.new.start.0.0..edit.new.end.0.0],
                 );
             }
             assert_eq!(prev_inlay_text, inlay_snapshot.text());
 
             assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
-            assert_eq!(expected_text.len(), inlay_snapshot.len().0);
+            assert_eq!(expected_text.len(), inlay_snapshot.len().0.0);
 
             let mut buffer_point = Point::default();
             let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
-            let mut buffer_chars = buffer_snapshot.chars_at(0);
+            let mut buffer_chars = buffer_snapshot.chars_at(MultiBufferOffset(0));
             loop {
                 // Ensure conversion from buffer coordinates to inlay coordinates
                 // is consistent.
@@ -1930,7 +2011,7 @@ mod tests {
 
         // Get all chunks and verify their bitmaps
         let chunks = snapshot.chunks(
-            InlayOffset(0)..InlayOffset(snapshot.len().0),
+            InlayOffset(MultiBufferOffset(0))..snapshot.len(),
             false,
             Highlights::default(),
         );
@@ -2064,7 +2145,7 @@ mod tests {
         // Collect chunks - this previously would panic
         let chunks: Vec<_> = inlay_snapshot
             .chunks(
-                InlayOffset(0)..InlayOffset(inlay_snapshot.len().0),
+                InlayOffset(MultiBufferOffset(0))..inlay_snapshot.len(),
                 false,
                 highlights,
             )
@@ -2178,7 +2259,7 @@ mod tests {
 
             let chunks: Vec<_> = inlay_snapshot
                 .chunks(
-                    InlayOffset(0)..InlayOffset(inlay_snapshot.len().0),
+                    InlayOffset(MultiBufferOffset(0))..inlay_snapshot.len(),
                     false,
                     highlights,
                 )

crates/editor/src/display_map/tab_map.rs 🔗

@@ -648,6 +648,7 @@ mod tests {
             inlay_map::InlayMap,
         },
     };
+    use multi_buffer::MultiBufferOffset;
     use rand::{Rng, prelude::StdRng};
     use util;
 
@@ -1156,7 +1157,7 @@ mod tests {
         let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
         let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
         let chunks = fold_snapshot.chunks(
-            FoldOffset(0)..fold_snapshot.len(),
+            FoldOffset(MultiBufferOffset(0))..fold_snapshot.len(),
             false,
             Default::default(),
         );
@@ -1318,7 +1319,7 @@ mod tests {
         let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
         let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
         let chunks = fold_snapshot.chunks(
-            FoldOffset(0)..fold_snapshot.len(),
+            FoldOffset(MultiBufferOffset(0))..fold_snapshot.len(),
             false,
             Default::default(),
         );

crates/editor/src/editor.rs 🔗

@@ -64,8 +64,9 @@ pub use items::MAX_TAB_TITLE_LEN;
 pub use lsp::CompletionContext;
 pub use lsp_ext::lsp_tasks;
 pub use multi_buffer::{
-    Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
-    RowInfo, ToOffset, ToPoint,
+    Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
+    MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
+    ToPoint,
 };
 pub use text::Bias;
 
@@ -118,8 +119,7 @@ use language::{
     BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
     DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
     IndentSize, Language, LanguageRegistry, OffsetRangeExt, OutlineItem, Point, Runnable,
-    RunnableRange, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
-    WordsQuery,
+    Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
     language_settings::{
         self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
         language_settings,
@@ -856,9 +856,6 @@ pub struct ResolvedTasks {
     position: Anchor,
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
-struct BufferOffset(usize);
-
 /// Addons allow storing per-editor state in other crates (e.g. Vim)
 pub trait Addon: 'static {
     fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
@@ -1575,7 +1572,7 @@ pub struct ClipboardSelection {
 
 // selections, scroll behavior, was newest selection reversed
 type SelectSyntaxNodeHistoryState = (
-    Box<[Selection<usize>]>,
+    Box<[Selection<MultiBufferOffset>]>,
     SelectSyntaxNodeScrollBehavior,
     bool,
 );
@@ -1930,16 +1927,18 @@ impl Editor {
                         }
                     }
                     project::Event::SnippetEdit(id, snippet_edits) => {
-                        if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
+                        // todo(lw): Non singletons
+                        if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
+                            let snapshot = buffer.read(cx).snapshot();
                             let focus_handle = editor.focus_handle(cx);
-                            if focus_handle.is_focused(window) {
-                                let snapshot = buffer.read(cx).snapshot();
+                            if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
                                 for (range, snippet) in snippet_edits {
-                                    let editor_range =
+                                    let buffer_range =
                                         language::range_from_lsp(*range).to_offset(&snapshot);
                                     editor
                                         .insert_snippet(
-                                            &[editor_range],
+                                            &[MultiBufferOffset(buffer_range.start)
+                                                ..MultiBufferOffset(buffer_range.end)],
                                             snippet.clone(),
                                             window,
                                             cx,
@@ -2516,7 +2515,7 @@ impl Editor {
         }
 
         self.selections
-            .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
+            .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
             .into_iter()
             .any(|selection| {
                 // This is needed to cover a corner case, if we just check for an existing
@@ -3177,7 +3176,9 @@ impl Editor {
         // Copy selections to primary selection buffer
         #[cfg(any(target_os = "linux", target_os = "freebsd"))]
         if local {
-            let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
+            let selections = self
+                .selections
+                .all::<MultiBufferOffset>(&self.display_snapshot(cx));
             let buffer_handle = self.buffer.read(cx).read(cx);
 
             let mut text = String::new();
@@ -3333,8 +3334,8 @@ impl Editor {
                         .iter()
                         .map(|selection| {
                             (
-                                selection.start.to_offset(&snapshot),
-                                selection.end.to_offset(&snapshot),
+                                selection.start.to_offset(&snapshot).0,
+                                selection.end.to_offset(&snapshot).0,
                             )
                         })
                         .collect();
@@ -3376,7 +3377,7 @@ impl Editor {
             return;
         };
         let inmemory_folds = display_snapshot
-            .folds_in_range(0..display_snapshot.buffer_snapshot().len())
+            .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
             .map(|fold| {
                 fold.range.start.text_anchor.to_point(&snapshot)
                     ..fold.range.end.text_anchor.to_point(&snapshot)
@@ -3392,7 +3393,7 @@ impl Editor {
         let background_executor = cx.background_executor().clone();
         let editor_id = cx.entity().entity_id().as_u64() as ItemId;
         let db_folds = display_snapshot
-            .folds_in_range(0..display_snapshot.buffer_snapshot().len())
+            .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
             .map(|fold| {
                 (
                     fold.range.start.text_anchor.to_offset(&snapshot),
@@ -3649,7 +3650,10 @@ impl Editor {
         cx: &mut Context<Self>,
     ) {
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let tail = self.selections.newest::<usize>(&display_map).tail();
+        let tail = self
+            .selections
+            .newest::<MultiBufferOffset>(&display_map)
+            .tail();
         let click_count = click_count.max(match self.selections.select_mode() {
             SelectMode::Character => 1,
             SelectMode::Word(_) => 2,
@@ -3758,7 +3762,7 @@ impl Editor {
                 auto_scroll = true;
             }
             _ => {
-                start = buffer.anchor_before(0);
+                start = buffer.anchor_before(MultiBufferOffset(0));
                 end = buffer.anchor_before(buffer.len());
                 mode = SelectMode::All;
                 auto_scroll = false;
@@ -3971,7 +3975,9 @@ impl Editor {
     fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
         self.columnar_selection_state.take();
         if let Some(pending_mode) = self.selections.pending_mode() {
-            let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
+            let selections = self
+                .selections
+                .all::<MultiBufferOffset>(&self.display_snapshot(cx));
             self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
                 s.select(selections);
                 s.clear_pending();
@@ -4505,17 +4511,19 @@ impl Editor {
             let new_anchor_selections = new_selections.iter().map(|e| &e.0);
             let new_selection_deltas = new_selections.iter().map(|e| e.1);
             let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
-            let new_selections =
-                resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
-                    .zip(new_selection_deltas)
-                    .map(|(selection, delta)| Selection {
-                        id: selection.id,
-                        start: selection.start + delta,
-                        end: selection.end + delta,
-                        reversed: selection.reversed,
-                        goal: SelectionGoal::None,
-                    })
-                    .collect::<Vec<_>>();
+            let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
+                new_anchor_selections,
+                &map,
+            )
+            .zip(new_selection_deltas)
+            .map(|(selection, delta)| Selection {
+                id: selection.id,
+                start: selection.start + delta,
+                end: selection.end + delta,
+                reversed: selection.reversed,
+                goal: SelectionGoal::None,
+            })
+            .collect::<Vec<_>>();
 
             let mut i = 0;
             for (position, delta, selection_id, pair) in new_autoclose_regions {
@@ -4651,7 +4659,9 @@ impl Editor {
         self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
         self.transact(window, cx, |this, window, cx| {
             let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
-                let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
+                let selections = this
+                    .selections
+                    .all::<MultiBufferOffset>(&this.display_snapshot(cx));
                 let multi_buffer = this.buffer.read(cx);
                 let buffer = multi_buffer.snapshot(cx);
                 selections
@@ -5151,7 +5161,9 @@ impl Editor {
     /// If any empty selections is touching the start of its innermost containing autoclose
     /// region, expand it to select the brackets.
     fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
+        let selections = self
+            .selections
+            .all::<MultiBufferOffset>(&self.display_snapshot(cx));
         let buffer = self.buffer.read(cx).read(cx);
         let new_selections = self
             .selections_with_autoclose_regions(selections, &buffer)
@@ -5162,7 +5174,7 @@ impl Editor {
 
                 if let Some(region) = region {
                     let mut range = region.range.to_offset(&buffer);
-                    if selection.start == range.start && range.start >= region.pair.start.len() {
+                    if selection.start == range.start && range.start.0 >= region.pair.start.len() {
                         range.start -= region.pair.start.len();
                         if buffer.contains_str_at(range.start, &region.pair.start)
                             && buffer.contains_str_at(range.end, &region.pair.end)
@@ -5193,7 +5205,7 @@ impl Editor {
                         if buffer.contains_str_at(selection.start, &pair.end) {
                             let pair_start_len = pair.start.len();
                             if buffer.contains_str_at(
-                                selection.start.saturating_sub(pair_start_len),
+                                selection.start.saturating_sub_usize(pair_start_len),
                                 &pair.start,
                             ) {
                                 selection.start -= pair_start_len;
@@ -5330,7 +5342,7 @@ impl Editor {
                         (
                             multi_buffer.buffer(buffer.remote_id()).unwrap(),
                             buffer.version().clone(),
-                            excerpt_visible_range,
+                            excerpt_visible_range.start.0..excerpt_visible_range.end.0,
                         ),
                     ))
                 }
@@ -6013,14 +6025,17 @@ impl Editor {
             .start
             .text_anchor
             .to_offset(buffer)
-            .saturating_sub(replace_range.start);
+            .saturating_sub(replace_range.start.0);
         let lookahead = replace_range
             .end
+            .0
             .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
         let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
         let suffix = &old_text[lookbehind.min(old_text.len())..];
 
-        let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
+        let selections = self
+            .selections
+            .all::<MultiBufferOffset>(&self.display_snapshot(cx));
         let mut ranges = Vec::new();
         let mut linked_edits = HashMap::<_, Vec<_>>::default();
 
@@ -6031,8 +6046,8 @@ impl Editor {
                 let mut range = selection.range();
 
                 // if prefix is present, don't duplicate it
-                if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
-                    range.start = range.start.saturating_sub(lookbehind);
+                if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
+                    range.start = range.start.saturating_sub_usize(lookbehind);
 
                     // if suffix is also present, mimic the newest cursor and replace it
                     if selection.id != newest_anchor.id
@@ -7008,7 +7023,10 @@ impl Editor {
                 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
                     match_ranges.extend(
                         regex
-                            .search(buffer_snapshot, Some(search_range.clone()))
+                            .search(
+                                buffer_snapshot,
+                                Some(search_range.start.0..search_range.end.0),
+                            )
                             .await
                             .into_iter()
                             .filter_map(|match_range| {
@@ -7055,12 +7073,12 @@ impl Editor {
         }
         let task = cx.background_spawn(async move {
             let new_newlines = snapshot
-                .buffer_chars_at(0)
+                .buffer_chars_at(MultiBufferOffset(0))
                 .filter_map(|(c, i)| {
                     if c == '\n' {
                         Some(
                             snapshot.buffer_snapshot().anchor_after(i)
-                                ..snapshot.buffer_snapshot().anchor_before(i + 1),
+                                ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
                         )
                     } else {
                         None
@@ -7068,7 +7086,7 @@ impl Editor {
                 })
                 .collect::<Vec<_>>();
             let existing_newlines = snapshot
-                .folds_in_range(0..snapshot.buffer_snapshot().len())
+                .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
                 .filter_map(|fold| {
                     if fold.placeholder.type_tag == Some(type_id) {
                         Some(fold.range.start..fold.range.end)
@@ -7165,7 +7183,7 @@ impl Editor {
                 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
         {
             let multi_buffer_start = multi_buffer_snapshot
-                .anchor_before(0)
+                .anchor_before(MultiBufferOffset(0))
                 .to_point(&multi_buffer_snapshot);
             let multi_buffer_end = multi_buffer_snapshot
                 .anchor_after(multi_buffer_snapshot.len())
@@ -7597,7 +7615,7 @@ impl Editor {
                 let snapshot = self.buffer.read(cx).snapshot(cx);
                 let cursor_offset = self
                     .selections
-                    .newest::<usize>(&self.display_snapshot(cx))
+                    .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
                     .head();
                 let insertion = edits.iter().find_map(|(range, text)| {
                     let range = range.to_offset(&snapshot);
@@ -8589,16 +8607,17 @@ impl Editor {
         let snapshot = self.buffer.read(cx).snapshot(cx);
         let offset = self
             .selections
-            .newest::<usize>(&self.display_snapshot(cx))
+            .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
             .head();
-        let excerpt = snapshot.excerpt_containing(offset..offset)?;
+        let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
+        let offset = excerpt.map_offset_to_buffer(offset);
         let buffer_id = excerpt.buffer().remote_id();
 
         let layer = excerpt.buffer().syntax_layer_at(offset)?;
         let mut cursor = layer.node().walk();
 
-        while cursor.goto_first_child_for_byte(offset).is_some() {
-            if cursor.node().end_byte() == offset {
+        while cursor.goto_first_child_for_byte(offset.0).is_some() {
+            if cursor.node().end_byte() == offset.0 {
                 cursor.goto_next_sibling();
             }
         }
@@ -8610,7 +8629,7 @@ impl Editor {
             let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
 
             // Check if this node contains our offset
-            if node_range.start <= offset && node_range.end >= offset {
+            if node_range.start <= offset.0 && node_range.end >= offset.0 {
                 // If it contains offset, check for task
                 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
                     let buffer = self.buffer.read(cx).buffer(buffer_id)?;
@@ -9812,7 +9831,7 @@ impl Editor {
 
     pub fn insert_snippet(
         &mut self,
-        insertion_ranges: &[Range<usize>],
+        insertion_ranges: &[Range<MultiBufferOffset>],
         snippet: Snippet,
         window: &mut Window,
         cx: &mut Context<Self>,
@@ -9849,14 +9868,13 @@ impl Editor {
                         .flat_map(|tabstop_range| {
                             let mut delta = 0_isize;
                             insertion_ranges.iter().map(move |insertion_range| {
-                                let insertion_start = insertion_range.start as isize + delta;
-                                delta +=
-                                    snippet.text.len() as isize - insertion_range.len() as isize;
-
-                                let start = ((insertion_start + tabstop_range.start) as usize)
-                                    .min(snapshot.len());
-                                let end = ((insertion_start + tabstop_range.end) as usize)
-                                    .min(snapshot.len());
+                                let insertion_start = insertion_range.start + delta;
+                                delta += snippet.text.len() as isize
+                                    - (insertion_range.end - insertion_range.start) as isize;
+
+                                let start =
+                                    (insertion_start + tabstop_range.start).min(snapshot.len());
+                                let end = (insertion_start + tabstop_range.end).min(snapshot.len());
                                 snapshot.anchor_before(start)..snapshot.anchor_after(end)
                             })
                         })
@@ -10506,7 +10524,9 @@ impl Editor {
                     cx,
                 );
             });
-            let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
+            let selections = this
+                .selections
+                .all::<MultiBufferOffset>(&this.display_snapshot(cx));
             this.change_selections(Default::default(), window, cx, |s| s.select(selections));
         });
     }
@@ -10523,7 +10543,7 @@ impl Editor {
         self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
         let selections = self
             .selections
-            .all::<usize>(&self.display_snapshot(cx))
+            .all::<MultiBufferOffset>(&self.display_snapshot(cx))
             .into_iter()
             .map(|s| s.range());
 
@@ -10531,7 +10551,9 @@ impl Editor {
             this.buffer.update(cx, |buffer, cx| {
                 buffer.autoindent_ranges(selections, cx);
             });
-            let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
+            let selections = this
+                .selections
+                .all::<MultiBufferOffset>(&this.display_snapshot(cx));
             this.change_selections(Default::default(), window, cx, |s| s.select(selections));
         });
     }
@@ -10569,7 +10591,7 @@ impl Editor {
             } else {
                 // If there isn't a line after the range, delete the \n from the line before the
                 // start of the row range
-                edit_start = edit_start.saturating_sub(1);
+                edit_start = edit_start.saturating_sub_usize(1);
                 (buffer.len(), rows.start.previous_row())
             };
 
@@ -10820,7 +10842,9 @@ impl Editor {
                 boundaries.into_iter()
             {
                 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
-                let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
+                let close_offset = end_after
+                    .to_offset(&buffer)
+                    .saturating_sub_usize(end_suffix_len);
                 new_selections.push(open_offset..open_offset);
                 new_selections.push(close_offset..close_offset);
             }
@@ -10850,7 +10874,10 @@ impl Editor {
         self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
         let mut buffer_ids = HashSet::default();
         let snapshot = self.buffer().read(cx).snapshot(cx);
-        for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
+        for selection in self
+            .selections
+            .all::<MultiBufferOffset>(&self.display_snapshot(cx))
+        {
             buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
         }
 
@@ -11265,7 +11292,7 @@ impl Editor {
             .read(cx)
             .base_text()
             .as_rope()
-            .slice(hunk.diff_base_byte_range.clone());
+            .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
         let buffer_snapshot = buffer.snapshot();
         let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
         if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
@@ -11705,7 +11732,7 @@ impl Editor {
 
         let mut new_selections = Vec::new();
         let mut edits = Vec::new();
-        let mut selection_adjustment = 0i32;
+        let mut selection_adjustment = 0isize;
 
         for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
             let selection_is_empty = selection.is_empty();
@@ -11721,18 +11748,20 @@ impl Editor {
             };
 
             let text = buffer.text_for_range(start..end).collect::<String>();
-            let old_length = text.len() as i32;
+            let old_length = text.len() as isize;
             let text = callback(&text);
 
             new_selections.push(Selection {
-                start: (start as i32 - selection_adjustment) as usize,
-                end: ((start + text.len()) as i32 - selection_adjustment) as usize,
+                start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
+                end: MultiBufferOffset(
+                    ((start.0 + text.len()) as isize - selection_adjustment) as usize,
+                ),
                 goal: SelectionGoal::None,
                 id: selection.id,
                 reversed: selection.reversed,
             });
 
-            selection_adjustment += old_length - text.len() as i32;
+            selection_adjustment += old_length - text.len() as isize;
 
             edits.push((start..end, text));
         }
@@ -12149,7 +12178,7 @@ impl Editor {
         let text_layout_details = &self.text_layout_details(window);
         self.transact(window, cx, |this, window, cx| {
             let edits = this.change_selections(Default::default(), window, cx, |s| {
-                let mut edits: Vec<(Range<usize>, String)> = Default::default();
+                let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
                 s.move_with(|display_map, selection| {
                     if !selection.is_empty() {
                         return;
@@ -12160,10 +12189,10 @@ impl Editor {
                     if head.column() == display_map.line_len(head.row()) {
                         transpose_offset = display_map
                             .buffer_snapshot()
-                            .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
+                            .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
                     }
 
-                    if transpose_offset == 0 {
+                    if transpose_offset == MultiBufferOffset(0) {
                         return;
                     }
 
@@ -12178,11 +12207,11 @@ impl Editor {
 
                     let transpose_start = display_map
                         .buffer_snapshot()
-                        .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
+                        .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
                     if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
                         let transpose_end = display_map
                             .buffer_snapshot()
-                            .clip_offset(transpose_offset + 1, Bias::Right);
+                            .clip_offset(transpose_offset + 1usize, Bias::Right);
                         if let Some(ch) = display_map
                             .buffer_snapshot()
                             .chars_at(transpose_start)
@@ -12197,7 +12226,9 @@ impl Editor {
             });
             this.buffer
                 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
-            let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
+            let selections = this
+                .selections
+                .all::<MultiBufferOffset>(&this.display_snapshot(cx));
             this.change_selections(Default::default(), window, cx, |s| {
                 s.select(selections);
             });
@@ -12804,8 +12835,11 @@ impl Editor {
         self.transact(window, cx, |this, window, cx| {
             let had_active_edit_prediction = this.has_active_edit_prediction();
             let display_map = this.display_snapshot(cx);
-            let old_selections = this.selections.all::<usize>(&display_map);
-            let cursor_offset = this.selections.last::<usize>(&display_map).head();
+            let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
+            let cursor_offset = this
+                .selections
+                .last::<MultiBufferOffset>(&display_map)
+                .head();
 
             if let Some(mut clipboard_selections) = clipboard_selections {
                 let all_selections_were_entire_line =
@@ -12890,7 +12924,9 @@ impl Editor {
                     );
                 });
 
-                let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
+                let selections = this
+                    .selections
+                    .all::<MultiBufferOffset>(&this.display_snapshot(cx));
                 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
             } else {
                 let url = url::Url::parse(&clipboard_text).ok();
@@ -12959,7 +12995,9 @@ impl Editor {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
-        let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
+        let selections = self
+            .selections
+            .all::<MultiBufferOffset>(&self.display_snapshot(cx));
 
         if selections.is_empty() {
             log::warn!("There should always be at least one selection in Zed. This is a bug.");
@@ -14212,7 +14250,7 @@ impl Editor {
         }
         self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
         self.change_selections(Default::default(), window, cx, |s| {
-            s.select_ranges(vec![0..0]);
+            s.select_ranges(vec![Anchor::min()..Anchor::min()]);
         });
     }
 
@@ -14301,7 +14339,9 @@ impl Editor {
     pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
         self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
         let buffer = self.buffer.read(cx).snapshot(cx);
-        let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
+        let mut selection = self
+            .selections
+            .first::<MultiBufferOffset>(&self.display_snapshot(cx));
         selection.set_head(buffer.len(), SelectionGoal::None);
         self.change_selections(Default::default(), window, cx, |s| {
             s.select(vec![selection]);
@@ -14310,9 +14350,8 @@ impl Editor {
 
     pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
         self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
-        let end = self.buffer.read(cx).read(cx).len();
         self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges(vec![0..end]);
+            s.select_ranges(vec![Anchor::min()..Anchor::max()]);
         });
     }
 
@@ -14572,7 +14611,7 @@ impl Editor {
 
     fn select_match_ranges(
         &mut self,
-        range: Range<usize>,
+        range: Range<MultiBufferOffset>,
         reversed: bool,
         replace_newest: bool,
         auto_scroll: Option<Autoscroll>,
@@ -14611,7 +14650,7 @@ impl Editor {
         cx: &mut Context<Self>,
     ) -> Result<()> {
         let buffer = display_map.buffer_snapshot();
-        let mut selections = self.selections.all::<usize>(&display_map);
+        let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
         if let Some(mut select_next_state) = self.select_next_state.take() {
             let query = &select_next_state.query;
             if !select_next_state.done {
@@ -14621,14 +14660,15 @@ impl Editor {
 
                 let bytes_after_last_selection =
                     buffer.bytes_in_range(last_selection.end..buffer.len());
-                let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
+                let bytes_before_first_selection =
+                    buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
                 let query_matches = query
                     .stream_find_iter(bytes_after_last_selection)
                     .map(|result| (last_selection.end, result))
                     .chain(
                         query
                             .stream_find_iter(bytes_before_first_selection)
-                            .map(|result| (0, result)),
+                            .map(|result| (MultiBufferOffset(0), result)),
                     );
 
                 for (start_offset, query_match) in query_matches {
@@ -14686,7 +14726,7 @@ impl Editor {
                     }
 
                     if let Some(next_selection) = selections_iter.peek() {
-                        if next_selection.range().len() == selection.range().len() {
+                        if next_selection.len() == selection.len() {
                             let next_selected_text = buffer
                                 .text_for_range(next_selection.range())
                                 .collect::<String>();
@@ -14774,18 +14814,21 @@ impl Editor {
 
         let mut new_selections = Vec::new();
 
-        let reversed = self.selections.oldest::<usize>(&display_map).reversed;
+        let reversed = self
+            .selections
+            .oldest::<MultiBufferOffset>(&display_map)
+            .reversed;
         let buffer = display_map.buffer_snapshot();
         let query_matches = select_next_state
             .query
-            .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
+            .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
 
         for query_match in query_matches.into_iter() {
             let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
             let offset_range = if reversed {
-                query_match.end()..query_match.start()
+                MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
             } else {
-                query_match.start()..query_match.end()
+                MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
             };
 
             if !select_next_state.wordwise
@@ -14838,7 +14881,7 @@ impl Editor {
         self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let buffer = display_map.buffer_snapshot();
-        let mut selections = self.selections.all::<usize>(&display_map);
+        let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
         if let Some(mut select_prev_state) = self.select_prev_state.take() {
             let query = &select_prev_state.query;
             if !select_prev_state.done {
@@ -14847,7 +14890,7 @@ impl Editor {
                 let mut next_selected_range = None;
                 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
                 let bytes_before_last_selection =
-                    buffer.reversed_bytes_in_range(0..last_selection.start);
+                    buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
                 let bytes_after_first_selection =
                     buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
                 let query_matches = query
@@ -14905,7 +14948,7 @@ impl Editor {
                     }
 
                     if let Some(next_selection) = selections_iter.peek() {
-                        if next_selection.range().len() == selection.range().len() {
+                        if next_selection.len() == selection.len() {
                             let next_selected_text = buffer
                                 .text_for_range(next_selection.range())
                                 .collect::<String>();
@@ -15351,13 +15394,13 @@ impl Editor {
         let buffer = self.buffer.read(cx).snapshot(cx);
         let old_selections = self
             .selections
-            .all::<usize>(&self.display_snapshot(cx))
+            .all::<MultiBufferOffset>(&self.display_snapshot(cx))
             .into_boxed_slice();
 
         fn update_selection(
-            selection: &Selection<usize>,
+            selection: &Selection<MultiBufferOffset>,
             buffer_snap: &MultiBufferSnapshot,
-        ) -> Option<Selection<usize>> {
+        ) -> Option<Selection<MultiBufferOffset>> {
             let cursor = selection.head();
             let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
             for symbol in symbols.iter().rev() {
@@ -15409,7 +15452,7 @@ impl Editor {
         };
         let old_selections: Box<[_]> = self
             .selections
-            .all::<usize>(&self.display_snapshot(cx))
+            .all::<MultiBufferOffset>(&self.display_snapshot(cx))
             .into();
         if old_selections.is_empty() {
             return;
@@ -15568,7 +15611,7 @@ impl Editor {
         let buffer = self.buffer.read(cx).snapshot(cx);
         let selections = self
             .selections
-            .all::<usize>(&self.display_snapshot(cx))
+            .all::<MultiBufferOffset>(&self.display_snapshot(cx))
             .into_iter()
             // subtracting the offset requires sorting
             .sorted_by_key(|i| i.start);
@@ -15620,7 +15663,7 @@ impl Editor {
                 let mut selections = vec![];
                 for (id, parent, text) in full_edits {
                     let start = parent.start - offset;
-                    offset += parent.len() - text.len();
+                    offset += (parent.end - parent.start) - text.len();
                     selections.push(Selection {
                         id,
                         start,
@@ -15642,7 +15685,7 @@ impl Editor {
     ) {
         let old_selections: Box<[_]> = self
             .selections
-            .all::<usize>(&self.display_snapshot(cx))
+            .all::<MultiBufferOffset>(&self.display_snapshot(cx))
             .into();
         if old_selections.is_empty() {
             return;
@@ -15658,8 +15701,18 @@ impl Editor {
             .map(|selection| {
                 let old_range = selection.start..selection.end;
 
-                if let Some(node) = buffer.syntax_next_sibling(old_range) {
-                    let new_range = node.byte_range();
+                let old_range =
+                    old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
+                let excerpt = buffer.excerpt_containing(old_range.clone());
+
+                if let Some(mut excerpt) = excerpt
+                    && let Some(node) = excerpt
+                        .buffer()
+                        .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
+                {
+                    let new_range = excerpt.map_range_from_buffer(
+                        BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
+                    );
                     selected_sibling = true;
                     Selection {
                         id: selection.id,
@@ -15694,7 +15747,7 @@ impl Editor {
     ) {
         let old_selections: Box<[_]> = self
             .selections
-            .all::<usize>(&self.display_snapshot(cx))
+            .all::<MultiBufferOffset>(&self.display_snapshot(cx))
             .into();
         if old_selections.is_empty() {
             return;
@@ -15709,9 +15762,18 @@ impl Editor {
             .iter()
             .map(|selection| {
                 let old_range = selection.start..selection.end;
+                let old_range =
+                    old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
+                let excerpt = buffer.excerpt_containing(old_range.clone());
 
-                if let Some(node) = buffer.syntax_prev_sibling(old_range) {
-                    let new_range = node.byte_range();
+                if let Some(mut excerpt) = excerpt
+                    && let Some(node) = excerpt
+                        .buffer()
+                        .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
+                {
+                    let new_range = excerpt.map_range_from_buffer(
+                        BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
+                    );
                     selected_sibling = true;
                     Selection {
                         id: selection.id,
@@ -15860,7 +15922,7 @@ impl Editor {
     fn fetch_runnable_ranges(
         snapshot: &DisplaySnapshot,
         range: Range<Anchor>,
-    ) -> Vec<language::RunnableRange> {
+    ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
         snapshot.buffer_snapshot().runnable_ranges(range).collect()
     }
 
@@ -15868,12 +15930,12 @@ impl Editor {
         project: Entity<Project>,
         snapshot: DisplaySnapshot,
         prefer_lsp: bool,
-        runnable_ranges: Vec<RunnableRange>,
+        runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
         cx: AsyncWindowContext,
     ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
         cx.spawn(async move |cx| {
             let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
-            for mut runnable in runnable_ranges {
+            for (run_range, mut runnable) in runnable_ranges {
                 let Some(tasks) = cx
                     .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
                     .ok()
@@ -15891,10 +15953,7 @@ impl Editor {
                     continue;
                 }
 
-                let point = runnable
-                    .run_range
-                    .start
-                    .to_point(&snapshot.buffer_snapshot());
+                let point = run_range.start.to_point(&snapshot.buffer_snapshot());
                 let Some(row) = snapshot
                     .buffer_snapshot()
                     .buffer_line_for_row(MultiBufferRow(point.row))
@@ -15909,9 +15968,7 @@ impl Editor {
                     (runnable.buffer_id, row),
                     RunnableTasks {
                         templates: tasks,
-                        offset: snapshot
-                            .buffer_snapshot()
-                            .anchor_before(runnable.run_range.start),
+                        offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
                         context_range,
                         column: point.column,
                         extra_variables: runnable.extra_captures,
@@ -15998,7 +16055,7 @@ impl Editor {
                 let mut best_destination = None;
                 for (open, close) in enclosing_bracket_ranges {
                     let close = close.to_inclusive();
-                    let length = close.end() - open.start;
+                    let length = *close.end() - open.start;
                     let inside = selection.start >= open.end && selection.end <= *close.start();
                     let in_bracket_range = open.to_inclusive().contains(&selection.head())
                         || close.contains(&selection.head());
@@ -16257,7 +16314,9 @@ impl Editor {
         cx: &mut Context<Self>,
     ) {
         let buffer = self.buffer.read(cx).snapshot(cx);
-        let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
+        let selection = self
+            .selections
+            .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
 
         let mut active_group_id = None;
         if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
@@ -16268,8 +16327,8 @@ impl Editor {
 
         fn filtered<'a>(
             severity: GoToDiagnosticSeverityFilter,
-            diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
-        ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
+            diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
+        ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
             diagnostics
                 .filter(move |entry| severity.matches(entry.diagnostic.severity))
                 .filter(|entry| entry.range.start != entry.range.end)
@@ -16279,7 +16338,7 @@ impl Editor {
         let before = filtered(
             severity,
             buffer
-                .diagnostics_in_range(0..selection.start)
+                .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
                 .filter(|entry| entry.range.start <= selection.start),
         );
         let after = filtered(
@@ -16289,7 +16348,7 @@ impl Editor {
                 .filter(|entry| entry.range.start >= selection.start),
         );
 
-        let mut found: Option<DiagnosticEntryRef<usize>> = None;
+        let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
         if direction == Direction::Prev {
             'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
             {
@@ -16681,7 +16740,7 @@ impl Editor {
         };
         let head = self
             .selections
-            .newest::<usize>(&self.display_snapshot(cx))
+            .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
             .head();
         let buffer = self.buffer.read(cx);
         let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
@@ -17146,7 +17205,9 @@ impl Editor {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Option<Task<Result<Navigated>>> {
-        let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
+        let selection = self
+            .selections
+            .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
         let multi_buffer = self.buffer.read(cx);
         let head = selection.head();
 
@@ -17420,7 +17481,8 @@ impl Editor {
                     this.take_rename(false, window, cx);
                     let buffer = this.buffer.read(cx).read(cx);
                     let cursor_offset = selection.head().to_offset(&buffer);
-                    let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
+                    let rename_start =
+                        cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
                     let rename_end = rename_start + rename_buffer_range.len();
                     let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
                     let mut old_highlight_id = None;
@@ -17442,8 +17504,16 @@ impl Editor {
                     let rename_editor = cx.new(|cx| {
                         let mut editor = Editor::single_line(window, cx);
                         editor.buffer.update(cx, |buffer, cx| {
-                            buffer.edit([(0..0, old_name.clone())], None, cx)
+                            buffer.edit(
+                                [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
+                                None,
+                                cx,
+                            )
                         });
+                        let cursor_offset_in_rename_range =
+                            MultiBufferOffset(cursor_offset_in_rename_range);
+                        let cursor_offset_in_rename_range_end =
+                            MultiBufferOffset(cursor_offset_in_rename_range_end);
                         let rename_selection_range = match cursor_offset_in_rename_range
                             .cmp(&cursor_offset_in_rename_range_end)
                         {
@@ -17458,7 +17528,7 @@ impl Editor {
                                 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
                             }
                         };
-                        if rename_selection_range.end > old_name.len() {
+                        if rename_selection_range.end.0 > old_name.len() {
                             editor.select_all(&SelectAll, window, cx);
                         } else {
                             editor.change_selections(Default::default(), window, cx, |s| {
@@ -17623,7 +17693,7 @@ impl Editor {
             let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
                 editor
                     .selections
-                    .newest::<usize>(&editor.display_snapshot(cx))
+                    .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
                     .head()
             });
 
@@ -17922,7 +17992,7 @@ impl Editor {
             let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
             let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
             let is_valid = buffer
-                .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
+                .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
                 .any(|entry| {
                     entry.diagnostic.is_primary
                         && !entry.range.is_empty()
@@ -17954,7 +18024,7 @@ impl Editor {
     fn activate_diagnostics(
         &mut self,
         buffer_id: BufferId,
-        diagnostic: DiagnosticEntryRef<'_, usize>,
+        diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -18146,7 +18216,9 @@ impl Editor {
             let new_inline_diagnostics = cx
                 .background_spawn(async move {
                     let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
-                    for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
+                    for diagnostic_entry in
+                        snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
+                    {
                         let message = diagnostic_entry
                             .diagnostic
                             .message
@@ -18525,7 +18597,7 @@ impl Editor {
         if self.buffer.read(cx).is_singleton() {
             let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
             let has_folds = display_map
-                .folds_in_range(0..display_map.buffer_snapshot().len())
+                .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
                 .next()
                 .is_some();
 
@@ -18728,7 +18800,10 @@ impl Editor {
         let snapshot = self.buffer.read(cx).snapshot(cx);
 
         let ranges = snapshot
-            .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
+            .text_object_ranges(
+                MultiBufferOffset(0)..snapshot.len(),
+                TreeSitterOptions::default(),
+            )
             .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
             .collect::<Vec<_>>();
 

crates/editor/src/editor_tests.rs 🔗

@@ -35,7 +35,7 @@ use language_settings::Formatter;
 use languages::markdown_lang;
 use languages::rust_lang;
 use lsp::CompletionParams;
-use multi_buffer::{IndentGuide, PathKey};
+use multi_buffer::{IndentGuide, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey};
 use parking_lot::Mutex;
 use pretty_assertions::{assert_eq, assert_ne};
 use project::{
@@ -197,7 +197,7 @@ fn test_edit_events(cx: &mut TestAppContext) {
     // No event is emitted when the mutation is a no-op.
     _ = editor2.update(cx, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([0..0])
+            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
         });
 
         editor.backspace(&Backspace, window, cx);
@@ -222,7 +222,7 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
     _ = editor.update(cx, |editor, window, cx| {
         editor.start_transaction_at(now, window, cx);
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([2..4])
+            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
         });
 
         editor.insert("cd", window, cx);
@@ -230,38 +230,46 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
         assert_eq!(editor.text(cx), "12cd56");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            vec![4..4]
+            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
         );
 
         editor.start_transaction_at(now, window, cx);
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([4..5])
+            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
         });
         editor.insert("e", window, cx);
         editor.end_transaction_at(now, cx);
         assert_eq!(editor.text(cx), "12cde6");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            vec![5..5]
+            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
         );
 
         now += group_interval + Duration::from_millis(1);
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([2..2])
+            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
         });
 
         // Simulate an edit in another editor
         buffer.update(cx, |buffer, cx| {
             buffer.start_transaction_at(now, cx);
-            buffer.edit([(0..1, "a")], None, cx);
-            buffer.edit([(1..1, "b")], None, cx);
+            buffer.edit(
+                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
+                None,
+                cx,
+            );
+            buffer.edit(
+                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
+                None,
+                cx,
+            );
             buffer.end_transaction_at(now, cx);
         });
 
         assert_eq!(editor.text(cx), "ab2cde6");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            vec![3..3]
+            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
         );
 
         // Last transaction happened past the group interval in a different editor.
@@ -270,7 +278,7 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
         assert_eq!(editor.text(cx), "12cde6");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            vec![2..2]
+            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
         );
 
         // First two transactions happened within the group interval in this editor.
@@ -280,7 +288,7 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
         assert_eq!(editor.text(cx), "123456");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            vec![0..0]
+            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
         );
 
         // Redo the first two transactions together.
@@ -288,7 +296,7 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
         assert_eq!(editor.text(cx), "12cde6");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            vec![5..5]
+            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
         );
 
         // Redo the last transaction on its own.
@@ -296,7 +304,7 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
         assert_eq!(editor.text(cx), "ab2cde6");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            vec![6..6]
+            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
         );
 
         // Test empty transactions.
@@ -329,7 +337,9 @@ fn test_ime_composition(cx: &mut TestAppContext) {
         assert_eq!(editor.text(cx), "äbcde");
         assert_eq!(
             editor.marked_text_ranges(cx),
-            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
+            Some(vec![
+                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
+            ])
         );
 
         // Finalize IME composition.
@@ -349,7 +359,9 @@ fn test_ime_composition(cx: &mut TestAppContext) {
         editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
         assert_eq!(
             editor.marked_text_ranges(cx),
-            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
+            Some(vec![
+                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
+            ])
         );
 
         // Undoing during an IME composition cancels it.
@@ -362,7 +374,9 @@ fn test_ime_composition(cx: &mut TestAppContext) {
         assert_eq!(editor.text(cx), "ābcdè");
         assert_eq!(
             editor.marked_text_ranges(cx),
-            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
+            Some(vec![
+                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
+            ])
         );
 
         // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
@@ -373,9 +387,9 @@ fn test_ime_composition(cx: &mut TestAppContext) {
         // Start a new IME composition with multiple cursors.
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
             s.select_ranges([
-                OffsetUtf16(1)..OffsetUtf16(1),
-                OffsetUtf16(3)..OffsetUtf16(3),
-                OffsetUtf16(5)..OffsetUtf16(5),
+                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
+                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
+                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
             ])
         });
         editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
@@ -383,9 +397,9 @@ fn test_ime_composition(cx: &mut TestAppContext) {
         assert_eq!(
             editor.marked_text_ranges(cx),
             Some(vec![
-                OffsetUtf16(0)..OffsetUtf16(3),
-                OffsetUtf16(4)..OffsetUtf16(7),
-                OffsetUtf16(8)..OffsetUtf16(11)
+                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
+                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
+                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
             ])
         );
 
@@ -395,9 +409,9 @@ fn test_ime_composition(cx: &mut TestAppContext) {
         assert_eq!(
             editor.marked_text_ranges(cx),
             Some(vec![
-                OffsetUtf16(1)..OffsetUtf16(2),
-                OffsetUtf16(5)..OffsetUtf16(6),
-                OffsetUtf16(9)..OffsetUtf16(10)
+                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
+                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
+                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
             ])
         );
 
@@ -757,7 +771,11 @@ fn test_clone(cx: &mut TestAppContext) {
 
     _ = editor.update(cx, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges(selection_ranges.clone())
+            s.select_ranges(
+                selection_ranges
+                    .iter()
+                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
+            )
         });
         editor.fold_creases(
             vec![
@@ -794,9 +812,11 @@ fn test_clone(cx: &mut TestAppContext) {
     );
     assert_eq!(
         cloned_snapshot
-            .folds_in_range(0..text.len())
+            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
+            .collect::<Vec<_>>(),
+        snapshot
+            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
             .collect::<Vec<_>>(),
-        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
     );
     assert_set_eq!(
         cloned_editor
@@ -1418,7 +1438,11 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
         );
 
         editor.change_selections(SelectionEffects::default(), window, cx, |s| {
-            s.select_ranges(positions)
+            s.select_ranges(
+                positions
+                    .iter()
+                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
+            )
         });
 
         editor.fold_at_level(&FoldAtLevel(2), window, cx);
@@ -3700,7 +3724,11 @@ fn test_insert_with_old_selections(cx: &mut TestAppContext) {
         let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
         let mut editor = build_editor(buffer, window, cx);
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([3..4, 11..12, 19..20])
+            s.select_ranges([
+                MultiBufferOffset(3)..MultiBufferOffset(4),
+                MultiBufferOffset(11)..MultiBufferOffset(12),
+                MultiBufferOffset(19)..MultiBufferOffset(20),
+            ])
         });
         editor
     });
@@ -3708,12 +3736,24 @@ fn test_insert_with_old_selections(cx: &mut TestAppContext) {
     _ = editor.update(cx, |editor, window, cx| {
         // Edit the buffer directly, deleting ranges surrounding the editor's selections
         editor.buffer.update(cx, |buffer, cx| {
-            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
+            buffer.edit(
+                [
+                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
+                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
+                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
+                ],
+                None,
+                cx,
+            );
             assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
         });
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            &[2..2, 7..7, 12..12],
+            &[
+                MultiBufferOffset(2)..MultiBufferOffset(2),
+                MultiBufferOffset(7)..MultiBufferOffset(7),
+                MultiBufferOffset(12)..MultiBufferOffset(12)
+            ],
         );
 
         editor.insert("Z", window, cx);
@@ -3722,7 +3762,11 @@ fn test_insert_with_old_selections(cx: &mut TestAppContext) {
         // The selections are moved after the inserted characters
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            &[3..3, 9..9, 15..15],
+            &[
+                MultiBufferOffset(3)..MultiBufferOffset(3),
+                MultiBufferOffset(9)..MultiBufferOffset(9),
+                MultiBufferOffset(15)..MultiBufferOffset(15)
+            ],
         );
     });
 }
@@ -4692,7 +4736,7 @@ async fn test_custom_newlines_cause_no_false_positive_diffs(
         assert_eq!(
             snapshot
                 .buffer_snapshot()
-                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
+                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
                 .collect::<Vec<_>>(),
             Vec::new(),
             "Should not have any diffs for files with custom newlines"
@@ -5964,27 +6008,27 @@ fn test_transpose(cx: &mut TestAppContext) {
         let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
         editor.set_style(EditorStyle::default(), window, cx);
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([1..1])
+            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
         });
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "bac");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [2..2]
+            [MultiBufferOffset(2)..MultiBufferOffset(2)]
         );
 
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "bca");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [3..3]
+            [MultiBufferOffset(3)..MultiBufferOffset(3)]
         );
 
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "bac");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [3..3]
+            [MultiBufferOffset(3)..MultiBufferOffset(3)]
         );
 
         editor
@@ -5994,37 +6038,37 @@ fn test_transpose(cx: &mut TestAppContext) {
         let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
         editor.set_style(EditorStyle::default(), window, cx);
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([3..3])
+            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
         });
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "acb\nde");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [3..3]
+            [MultiBufferOffset(3)..MultiBufferOffset(3)]
         );
 
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([4..4])
+            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
         });
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "acbd\ne");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [5..5]
+            [MultiBufferOffset(5)..MultiBufferOffset(5)]
         );
 
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "acbde\n");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [6..6]
+            [MultiBufferOffset(6)..MultiBufferOffset(6)]
         );
 
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "acbd\ne");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [6..6]
+            [MultiBufferOffset(6)..MultiBufferOffset(6)]
         );
 
         editor
@@ -6034,41 +6078,62 @@ fn test_transpose(cx: &mut TestAppContext) {
         let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
         editor.set_style(EditorStyle::default(), window, cx);
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([1..1, 2..2, 4..4])
+            s.select_ranges([
+                MultiBufferOffset(1)..MultiBufferOffset(1),
+                MultiBufferOffset(2)..MultiBufferOffset(2),
+                MultiBufferOffset(4)..MultiBufferOffset(4),
+            ])
         });
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "bacd\ne");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [2..2, 3..3, 5..5]
+            [
+                MultiBufferOffset(2)..MultiBufferOffset(2),
+                MultiBufferOffset(3)..MultiBufferOffset(3),
+                MultiBufferOffset(5)..MultiBufferOffset(5)
+            ]
         );
 
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "bcade\n");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [3..3, 4..4, 6..6]
+            [
+                MultiBufferOffset(3)..MultiBufferOffset(3),
+                MultiBufferOffset(4)..MultiBufferOffset(4),
+                MultiBufferOffset(6)..MultiBufferOffset(6)
+            ]
         );
 
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "bcda\ne");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [4..4, 6..6]
+            [
+                MultiBufferOffset(4)..MultiBufferOffset(4),
+                MultiBufferOffset(6)..MultiBufferOffset(6)
+            ]
         );
 
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "bcade\n");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [4..4, 6..6]
+            [
+                MultiBufferOffset(4)..MultiBufferOffset(4),
+                MultiBufferOffset(6)..MultiBufferOffset(6)
+            ]
         );
 
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "bcaed\n");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [5..5, 6..6]
+            [
+                MultiBufferOffset(5)..MultiBufferOffset(5),
+                MultiBufferOffset(6)..MultiBufferOffset(6)
+            ]
         );
 
         editor
@@ -6078,27 +6143,27 @@ fn test_transpose(cx: &mut TestAppContext) {
         let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
         editor.set_style(EditorStyle::default(), window, cx);
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([4..4])
+            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
         });
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "🏀🍐✋");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [8..8]
+            [MultiBufferOffset(8)..MultiBufferOffset(8)]
         );
 
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "🏀✋🍐");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [11..11]
+            [MultiBufferOffset(11)..MultiBufferOffset(11)]
         );
 
         editor.transpose(&Default::default(), window, cx);
         assert_eq!(editor.text(cx), "🏀🍐✋");
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
-            [11..11]
+            [MultiBufferOffset(11)..MultiBufferOffset(11)]
         );
 
         editor
@@ -9731,7 +9796,11 @@ async fn test_autoindent(cx: &mut TestAppContext) {
 
     editor.update_in(cx, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([5..5, 8..8, 9..9])
+            s.select_ranges([
+                MultiBufferOffset(5)..MultiBufferOffset(5),
+                MultiBufferOffset(8)..MultiBufferOffset(8),
+                MultiBufferOffset(9)..MultiBufferOffset(9),
+            ])
         });
         editor.newline(&Newline, window, cx);
         assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
@@ -9796,7 +9865,11 @@ async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 
     editor.update_in(cx, |editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([5..5, 8..8, 9..9])
+            s.select_ranges([
+                MultiBufferOffset(5)..MultiBufferOffset(5),
+                MultiBufferOffset(8)..MultiBufferOffset(8),
+                MultiBufferOffset(9)..MultiBufferOffset(9),
+            ])
         });
         editor.newline(&Newline, window, cx);
         assert_eq!(
@@ -10453,7 +10526,7 @@ async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
         let snapshot = editor.snapshot(window, cx);
         let cursors = editor
             .selections
-            .ranges::<usize>(&editor.display_snapshot(cx));
+            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
         let languages = cursors
             .iter()
             .map(|c| snapshot.language_at(c.start).unwrap().name())
@@ -11143,17 +11216,26 @@ async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
         let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 
         editor
-            .insert_snippet(&insertion_ranges, snippet, window, cx)
+            .insert_snippet(
+                &insertion_ranges
+                    .iter()
+                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
+                    .collect::<Vec<_>>(),
+                snippet,
+                window,
+                cx,
+            )
             .unwrap();
 
         fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
             let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
             assert_eq!(editor.text(cx), expected_text);
             assert_eq!(
-                editor
-                    .selections
-                    .ranges::<usize>(&editor.display_snapshot(cx)),
+                editor.selections.ranges(&editor.display_snapshot(cx)),
                 selection_ranges
+                    .iter()
+                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
+                    .collect::<Vec<_>>()
             );
         }
 
@@ -11177,10 +11259,11 @@ async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppConte
         let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
         assert_eq!(editor.text(cx), expected_text);
         assert_eq!(
-            editor
-                .selections
-                .ranges::<usize>(&editor.display_snapshot(cx)),
+            editor.selections.ranges(&editor.display_snapshot(cx)),
             selection_ranges
+                .iter()
+                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
+                .collect::<Vec<_>>()
         );
     }
 
@@ -11198,7 +11281,15 @@ async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppConte
         let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
 
         editor
-            .insert_snippet(&insertion_ranges, snippet, window, cx)
+            .insert_snippet(
+                &insertion_ranges
+                    .iter()
+                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
+                    .collect::<Vec<_>>(),
+                snippet,
+                window,
+                cx,
+            )
             .unwrap();
 
         assert_state(
@@ -11646,7 +11737,7 @@ async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
     });
     editor.update_in(cx, |editor, window, cx| {
         editor.change_selections(SelectionEffects::default(), window, cx, |s| {
-            s.select_ranges([0..0])
+            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
         });
     });
     assert!(!cx.read(|cx| editor.is_dirty(cx)));
@@ -11812,7 +11903,7 @@ async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
             SelectionEffects::scroll(Autoscroll::Next),
             window,
             cx,
-            |s| s.select_ranges(Some(1..2)),
+            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
         );
         editor.insert("|one|two|three|", window, cx);
     });
@@ -11822,7 +11913,7 @@ async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
             SelectionEffects::scroll(Autoscroll::Next),
             window,
             cx,
-            |s| s.select_ranges(Some(60..70)),
+            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
         );
         editor.insert("|four|five|six|", window, cx);
     });
@@ -11990,7 +12081,7 @@ async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
             SelectionEffects::scroll(Autoscroll::Next),
             window,
             cx,
-            |s| s.select_ranges(Some(10..10)),
+            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
         );
         editor.insert("// edited", window, cx);
     });
@@ -13428,7 +13519,7 @@ async fn test_signature_help(cx: &mut TestAppContext) {
 
     cx.update_editor(|editor, window, cx| {
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([0..0])
+            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
         });
     });
 
@@ -16413,7 +16504,11 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
         );
         assert_eq!(editor.text(cx), expected_text);
         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges(selection_ranges)
+            s.select_ranges(
+                selection_ranges
+                    .iter()
+                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
+            )
         });
 
         editor.handle_input("X", window, cx);
@@ -16431,6 +16526,9 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
             expected_selections
+                .iter()
+                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
+                .collect::<Vec<_>>()
         );
 
         editor.newline(&Newline, window, cx);
@@ -16451,6 +16549,9 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
         assert_eq!(
             editor.selections.ranges(&editor.display_snapshot(cx)),
             expected_selections
+                .iter()
+                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
+                .collect::<Vec<_>>()
         );
     });
 }
@@ -16826,7 +16927,7 @@ async fn test_following(cx: &mut TestAppContext) {
     // Update the selections only
     _ = leader.update(cx, |leader, window, cx| {
         leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([1..1])
+            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
         });
     });
     follower
@@ -16844,7 +16945,7 @@ async fn test_following(cx: &mut TestAppContext) {
     _ = follower.update(cx, |follower, _, cx| {
         assert_eq!(
             follower.selections.ranges(&follower.display_snapshot(cx)),
-            vec![1..1]
+            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
         );
     });
     assert!(*is_still_following.borrow());
@@ -16879,7 +16980,7 @@ async fn test_following(cx: &mut TestAppContext) {
     // via autoscroll, not via the leader's exact scroll position.
     _ = leader.update(cx, |leader, window, cx| {
         leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([0..0])
+            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
         });
         leader.request_autoscroll(Autoscroll::newest(), cx);
         leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
@@ -16900,7 +17001,7 @@ async fn test_following(cx: &mut TestAppContext) {
         assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
         assert_eq!(
             follower.selections.ranges(&follower.display_snapshot(cx)),
-            vec![0..0]
+            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
         );
     });
     assert!(*is_still_following.borrow());
@@ -16908,7 +17009,7 @@ async fn test_following(cx: &mut TestAppContext) {
     // Creating a pending selection that precedes another selection
     _ = leader.update(cx, |leader, window, cx| {
         leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-            s.select_ranges([1..1])
+            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
         });
         leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
     });
@@ -16927,7 +17028,10 @@ async fn test_following(cx: &mut TestAppContext) {
     _ = follower.update(cx, |follower, _, cx| {
         assert_eq!(
             follower.selections.ranges(&follower.display_snapshot(cx)),
-            vec![0..0, 1..1]
+            vec![
+                MultiBufferOffset(0)..MultiBufferOffset(0),
+                MultiBufferOffset(1)..MultiBufferOffset(1)
+            ]
         );
     });
     assert!(*is_still_following.borrow());
@@ -16951,13 +17055,17 @@ async fn test_following(cx: &mut TestAppContext) {
     _ = follower.update(cx, |follower, _, cx| {
         assert_eq!(
             follower.selections.ranges(&follower.display_snapshot(cx)),
-            vec![0..2]
+            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
         );
     });
 
     // Scrolling locally breaks the follow
     _ = follower.update(cx, |follower, window, cx| {
-        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
+        let top_anchor = follower
+            .buffer()
+            .read(cx)
+            .read(cx)
+            .anchor_after(MultiBufferOffset(0));
         follower.set_scroll_anchor(
             ScrollAnchor {
                 anchor: top_anchor,
@@ -19451,7 +19559,7 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
             SelectionEffects::scroll(Autoscroll::Next),
             window,
             cx,
-            |s| s.select_ranges(Some(1..2)),
+            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
         );
         editor.open_excerpts(&OpenExcerpts, window, cx);
     });
@@ -19507,7 +19615,7 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
             SelectionEffects::scroll(Autoscroll::Next),
             window,
             cx,
-            |s| s.select_ranges(Some(39..40)),
+            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
         );
         editor.open_excerpts(&OpenExcerpts, window, cx);
     });
@@ -19567,7 +19675,7 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
             SelectionEffects::scroll(Autoscroll::Next),
             window,
             cx,
-            |s| s.select_ranges(Some(70..70)),
+            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
         );
         editor.open_excerpts(&OpenExcerpts, window, cx);
     });
@@ -22357,7 +22465,7 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
             (buffer.read(cx).remote_id(), 3),
             RunnableTasks {
                 templates: vec![],
-                offset: snapshot.anchor_before(43),
+                offset: snapshot.anchor_before(MultiBufferOffset(43)),
                 column: 0,
                 extra_variables: HashMap::default(),
                 context_range: BufferOffset(43)..BufferOffset(85),
@@ -22367,7 +22475,7 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
             (buffer.read(cx).remote_id(), 8),
             RunnableTasks {
                 templates: vec![],
-                offset: snapshot.anchor_before(86),
+                offset: snapshot.anchor_before(MultiBufferOffset(86)),
                 column: 0,
                 extra_variables: HashMap::default(),
                 context_range: BufferOffset(86)..BufferOffset(191),
@@ -25749,7 +25857,10 @@ fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Cont
     assert_eq!(editor.text(cx), text);
     assert_eq!(
         editor.selections.ranges(&editor.display_snapshot(cx)),
-        ranges,
+        ranges
+            .iter()
+            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
+            .collect::<Vec<_>>(),
         "Assert selections are {}",
         marked_text
     );
@@ -25995,10 +26106,12 @@ pub fn handle_completion_request(
         vec![complete_from_marker.clone(), replace_range_marker.clone()],
     );
 
-    let complete_from_position =
-        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
+    let complete_from_position = cx.to_lsp(MultiBufferOffset(
+        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
+    ));
+    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
     let replace_range =
-        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
+        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
 
     let mut request =
         cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
@@ -26059,13 +26172,18 @@ pub fn handle_completion_request_with_insert_and_replace(
         ],
     );
 
-    let complete_from_position =
-        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
+    let complete_from_position = cx.to_lsp(MultiBufferOffset(
+        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
+    ));
+    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
     let replace_range =
-        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
+        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
 
     let insert_range = match marked_ranges.remove(&insert_range_marker) {
-        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
+        Some(ranges) if !ranges.is_empty() => {
+            let range1 = ranges[0].clone();
+            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
+        }
         _ => lsp::Range {
             start: replace_range.start,
             end: complete_from_position,
@@ -26115,7 +26233,10 @@ fn handle_resolve_completion_request(
             .iter()
             .map(|(marked_string, new_text)| {
                 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
-                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
+                let replace_range = cx.to_lsp_range(
+                    MultiBufferOffset(marked_ranges[0].start)
+                        ..MultiBufferOffset(marked_ranges[0].end),
+                );
                 lsp::TextEdit::new(replace_range, new_text.to_string())
             })
             .collect::<Vec<_>>()
@@ -26199,7 +26320,7 @@ fn assert_hunk_revert(
         let snapshot = editor.snapshot(window, cx);
         let reverted_hunk_statuses = snapshot
             .buffer_snapshot()
-            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
+            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
             .map(|hunk| hunk.status().kind)
             .collect::<Vec<_>>();
 
@@ -26823,7 +26944,9 @@ async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
     editor.update(cx, |editor, cx| {
         assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
     });
-    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
+    editor.update(cx, |editor, cx| {
+        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
+    });
     cx.run_until_parked();
     editor.update(cx, |editor, cx| {
         assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
@@ -27895,7 +28018,7 @@ async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
     // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
     cx.update_editor(|editor, window, cx| {
         editor.change_selections(None.into(), window, cx, |s| {
-            s.select_ranges([2..3]);
+            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
         });
     });
     cx.assert_excerpts_with_selections(indoc! {"
@@ -27952,7 +28075,7 @@ async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
     // Select "2" and select all matches
     cx.update_editor(|editor, window, cx| {
         editor.change_selections(None.into(), window, cx, |s| {
-            s.select_ranges([2..3]);
+            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
         });
         editor
             .select_all_matches(&SelectAllMatches, window, cx)
@@ -28003,7 +28126,7 @@ async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
     // Select "2" and select all matches
     cx.update_editor(|editor, window, cx| {
         editor.change_selections(None.into(), window, cx, |s| {
-            s.select_ranges([2..3]);
+            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
         });
         editor
             .select_all_matches(&SelectAllMatches, window, cx)

crates/editor/src/git/blame.rs 🔗

@@ -67,7 +67,7 @@ impl<'a> sum_tree::Dimension<'a, GitBlameEntrySummary> for u32 {
 struct GitBlameBuffer {
     entries: SumTree<GitBlameEntry>,
     buffer_snapshot: BufferSnapshot,
-    buffer_edits: text::Subscription,
+    buffer_edits: text::Subscription<usize>,
     commit_details: HashMap<Oid, ParsedCommitMessage>,
 }
 

crates/editor/src/highlight_matching_bracket.rs 🔗

@@ -1,6 +1,7 @@
 use crate::{Editor, RangeToAnchorExt};
 use gpui::{Context, HighlightStyle, Window};
 use language::CursorShape;
+use multi_buffer::MultiBufferOffset;
 use theme::ActiveTheme;
 
 enum MatchingBracketHighlight {}
@@ -15,7 +16,7 @@ impl Editor {
 
         let snapshot = self.snapshot(window, cx);
         let buffer_snapshot = snapshot.buffer_snapshot();
-        let newest_selection = self.selections.newest::<usize>(&snapshot);
+        let newest_selection = self.selections.newest::<MultiBufferOffset>(&snapshot);
         // Don't highlight brackets if the selection isn't empty
         if !newest_selection.is_empty() {
             return;

crates/editor/src/hover_links.rs 🔗

@@ -738,6 +738,7 @@ mod tests {
     use gpui::Modifiers;
     use indoc::indoc;
     use lsp::request::{GotoDefinition, GotoTypeDefinition};
+    use multi_buffer::MultiBufferOffset;
     use settings::InlayHintSettingsContent;
     use util::{assert_set_eq, path};
     use workspace::item::Item;
@@ -1067,8 +1068,8 @@ mod tests {
             .clone();
         cx.update_editor(|editor, window, cx| {
             let snapshot = editor.buffer().read(cx).snapshot(cx);
-            let anchor_range = snapshot.anchor_before(selection_range.start)
-                ..snapshot.anchor_after(selection_range.end);
+            let anchor_range = snapshot.anchor_before(MultiBufferOffset(selection_range.start))
+                ..snapshot.anchor_after(MultiBufferOffset(selection_range.end));
             editor.change_selections(Default::default(), window, cx, |s| {
                 s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character)
             });
@@ -1122,7 +1123,7 @@ mod tests {
                 }
             "})[0]
             .start;
-        let hint_position = cx.to_lsp(hint_start_offset);
+        let hint_position = cx.to_lsp(MultiBufferOffset(hint_start_offset));
         let target_range = cx.lsp_range(indoc! {"
                 struct «TestStruct»;
 
@@ -1179,8 +1180,8 @@ mod tests {
             .unwrap();
         let midpoint = cx.update_editor(|editor, window, cx| {
             let snapshot = editor.snapshot(window, cx);
-            let previous_valid = inlay_range.start.to_display_point(&snapshot);
-            let next_valid = inlay_range.end.to_display_point(&snapshot);
+            let previous_valid = MultiBufferOffset(inlay_range.start).to_display_point(&snapshot);
+            let next_valid = MultiBufferOffset(inlay_range.end).to_display_point(&snapshot);
             assert_eq!(previous_valid.row(), next_valid.row());
             assert!(previous_valid.column() < next_valid.column());
             DisplayPoint::new(
@@ -1203,7 +1204,7 @@ mod tests {
             let buffer_snapshot = editor.buffer().update(cx, |buffer, cx| buffer.snapshot(cx));
             let expected_highlight = InlayHighlight {
                 inlay: InlayId::Hint(0),
-                inlay_position: buffer_snapshot.anchor_after(inlay_range.start),
+                inlay_position: buffer_snapshot.anchor_after(MultiBufferOffset(inlay_range.start)),
                 range: 0..hint_label.len(),
             };
             assert_set_eq!(actual_highlights, vec![&expected_highlight]);

crates/editor/src/hover_popover.rs 🔗

@@ -17,7 +17,7 @@ use itertools::Itertools;
 use language::{DiagnosticEntry, Language, LanguageRegistry};
 use lsp::DiagnosticSeverity;
 use markdown::{Markdown, MarkdownElement, MarkdownStyle};
-use multi_buffer::{ToOffset, ToPoint};
+use multi_buffer::{MultiBufferOffset, ToOffset, ToPoint};
 use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
 use settings::Settings;
 use std::{borrow::Cow, cell::RefCell};
@@ -106,7 +106,7 @@ pub fn find_hovered_hint_part(
     hovered_offset: InlayOffset,
 ) -> Option<(InlayHintLabelPart, Range<InlayOffset>)> {
     if hovered_offset >= hint_start {
-        let mut hovered_character = (hovered_offset - hint_start).0;
+        let mut hovered_character = hovered_offset - hint_start;
         let mut part_start = hint_start;
         for part in label_parts {
             let part_len = part.value.chars().count();
@@ -316,12 +316,12 @@ fn show_hover(
             } else {
                 snapshot
                     .buffer_snapshot()
-                    .diagnostics_with_buffer_ids_in_range::<usize>(offset..offset)
+                    .diagnostics_with_buffer_ids_in_range::<MultiBufferOffset>(offset..offset)
                     .filter(|(_, diagnostic)| {
                         Some(diagnostic.diagnostic.group_id) != active_group_id
                     })
                     // Find the entry with the most specific range
-                    .min_by_key(|(_, entry)| entry.range.len())
+                    .min_by_key(|(_, entry)| entry.range.end - entry.range.start)
             };
 
             let diagnostic_popover = if let Some((buffer_id, local_diagnostic)) = local_diagnostic {
@@ -1633,7 +1633,7 @@ mod tests {
             }
         "})[0]
             .start;
-        let hint_position = cx.to_lsp(hint_start_offset);
+        let hint_position = cx.to_lsp(MultiBufferOffset(hint_start_offset));
         let new_type_target_range = cx.lsp_range(indoc! {"
             struct TestStruct;
 
@@ -1708,8 +1708,8 @@ mod tests {
             .unwrap();
         let new_type_hint_part_hover_position = cx.update_editor(|editor, window, cx| {
             let snapshot = editor.snapshot(window, cx);
-            let previous_valid = inlay_range.start.to_display_point(&snapshot);
-            let next_valid = inlay_range.end.to_display_point(&snapshot);
+            let previous_valid = MultiBufferOffset(inlay_range.start).to_display_point(&snapshot);
+            let next_valid = MultiBufferOffset(inlay_range.end).to_display_point(&snapshot);
             assert_eq!(previous_valid.row(), next_valid.row());
             assert!(previous_valid.column() < next_valid.column());
             let exact_unclipped = DisplayPoint::new(
@@ -1819,7 +1819,8 @@ mod tests {
                 popover.symbol_range,
                 RangeInEditor::Inlay(InlayHighlight {
                     inlay: InlayId::Hint(0),
-                    inlay_position: buffer_snapshot.anchor_after(inlay_range.start),
+                    inlay_position: buffer_snapshot
+                        .anchor_after(MultiBufferOffset(inlay_range.start)),
                     range: ": ".len()..": ".len() + new_type_label.len(),
                 }),
                 "Popover range should match the new type label part"
@@ -1832,8 +1833,8 @@ mod tests {
 
         let struct_hint_part_hover_position = cx.update_editor(|editor, window, cx| {
             let snapshot = editor.snapshot(window, cx);
-            let previous_valid = inlay_range.start.to_display_point(&snapshot);
-            let next_valid = inlay_range.end.to_display_point(&snapshot);
+            let previous_valid = MultiBufferOffset(inlay_range.start).to_display_point(&snapshot);
+            let next_valid = MultiBufferOffset(inlay_range.end).to_display_point(&snapshot);
             assert_eq!(previous_valid.row(), next_valid.row());
             assert!(previous_valid.column() < next_valid.column());
             let exact_unclipped = DisplayPoint::new(
@@ -1873,7 +1874,8 @@ mod tests {
                 popover.symbol_range,
                 RangeInEditor::Inlay(InlayHighlight {
                     inlay: InlayId::Hint(0),
-                    inlay_position: buffer_snapshot.anchor_after(inlay_range.start),
+                    inlay_position: buffer_snapshot
+                        .anchor_after(MultiBufferOffset(inlay_range.start)),
                     range: ": ".len() + new_type_label.len() + "<".len()
                         ..": ".len() + new_type_label.len() + "<".len() + struct_label.len(),
                 }),

crates/editor/src/inlays/inlay_hints.rs 🔗

@@ -645,9 +645,9 @@ impl Editor {
                                         )
                                     {
                                         let highlight_start =
-                                            (part_range.start - hint_start).0 + extra_shift_left;
+                                            (part_range.start - hint_start) + extra_shift_left;
                                         let highlight_end =
-                                            (part_range.end - hint_start).0 + extra_shift_right;
+                                            (part_range.end - hint_start) + extra_shift_right;
                                         let highlight = InlayHighlight {
                                             inlay: hovered_hint.id,
                                             inlay_position: hovered_hint.position,
@@ -948,7 +948,7 @@ pub mod tests {
     use language::{Language, LanguageConfig, LanguageMatcher};
     use languages::rust_lang;
     use lsp::FakeLanguageServer;
-    use multi_buffer::MultiBuffer;
+    use multi_buffer::{MultiBuffer, MultiBufferOffset};
     use parking_lot::Mutex;
     use pretty_assertions::assert_eq;
     use project::{FakeFs, Project};
@@ -1029,7 +1029,7 @@ pub mod tests {
         editor
             .update(cx, |editor, window, cx| {
                 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-                    s.select_ranges([13..13])
+                    s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
                 });
                 editor.handle_input("some change", window, cx);
             })
@@ -1429,7 +1429,7 @@ pub mod tests {
         rs_editor
             .update(cx, |editor, window, cx| {
                 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-                    s.select_ranges([13..13])
+                    s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
                 });
                 editor.handle_input("some rs change", window, cx);
             })
@@ -1461,7 +1461,7 @@ pub mod tests {
         md_editor
             .update(cx, |editor, window, cx| {
                 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-                    s.select_ranges([13..13])
+                    s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
                 });
                 editor.handle_input("some md change", window, cx);
             })
@@ -1909,7 +1909,7 @@ pub mod tests {
             editor
                 .update(cx, |editor, window, cx| {
                     editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-                        s.select_ranges([13..13])
+                        s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
                     });
                     editor.handle_input(change_after_opening, window, cx);
                 })
@@ -1955,7 +1955,7 @@ pub mod tests {
                 task_editor
                     .update(&mut cx, |editor, window, cx| {
                         editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-                            s.select_ranges([13..13])
+                            s.select_ranges([MultiBufferOffset(13)..MultiBufferOffset(13)])
                         });
                         editor.handle_input(async_later_change, window, cx);
                     })
@@ -2706,7 +2706,7 @@ let c = 3;"#
             let mut editor =
                 Editor::for_multibuffer(multi_buffer, Some(project.clone()), window, cx);
             editor.change_selections(SelectionEffects::default(), window, cx, |s| {
-                s.select_ranges([0..0])
+                s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
             });
             editor
         });

crates/editor/src/items.rs 🔗

@@ -21,6 +21,7 @@ use language::{
     SelectionGoal, proto::serialize_anchor as serialize_text_anchor,
 };
 use lsp::DiagnosticSeverity;
+use multi_buffer::MultiBufferOffset;
 use project::{
     Project, ProjectItem as _, ProjectPath, lsp_store::FormatTrigger,
     project_settings::ProjectSettings, search::SearchQuery,
@@ -1735,7 +1736,7 @@ impl SearchableItem for Editor {
             let mut ranges = Vec::new();
 
             let search_within_ranges = if search_within_ranges.is_empty() {
-                vec![buffer.anchor_before(0)..buffer.anchor_after(buffer.len())]
+                vec![buffer.anchor_before(MultiBufferOffset(0))..buffer.anchor_after(buffer.len())]
             } else {
                 search_within_ranges
             };
@@ -1746,7 +1747,10 @@ impl SearchableItem for Editor {
                 {
                     ranges.extend(
                         query
-                            .search(search_buffer, Some(search_range.clone()))
+                            .search(
+                                search_buffer,
+                                Some(search_range.start.0..search_range.end.0),
+                            )
                             .await
                             .into_iter()
                             .map(|match_range| {

crates/editor/src/jsx_tag_auto_close.rs 🔗

@@ -1,7 +1,7 @@
 use anyhow::{Context as _, Result, anyhow};
 use collections::HashMap;
 use gpui::{Context, Entity, Window};
-use multi_buffer::{MultiBuffer, ToOffset};
+use multi_buffer::{BufferOffset, MultiBuffer, ToOffset};
 use std::ops::Range;
 use util::ResultExt as _;
 
@@ -546,9 +546,10 @@ pub(crate) fn handle_from(
                 if edit_range_offset.start != edit_range_offset.end {
                     continue;
                 }
-                if let Some(selection) =
-                    buffer_selection_map.get_mut(&(edit_range_offset.start, edit_range_offset.end))
-                {
+                if let Some(selection) = buffer_selection_map.get_mut(&(
+                    BufferOffset(edit_range_offset.start),
+                    BufferOffset(edit_range_offset.end),
+                )) {
                     if selection.0.head().bias() != text::Bias::Right
                         || selection.0.tail().bias() != text::Bias::Right
                     {
@@ -621,7 +622,7 @@ mod jsx_tag_autoclose_tests {
     use super::*;
     use gpui::{AppContext as _, TestAppContext};
     use languages::language;
-    use multi_buffer::ExcerptRange;
+    use multi_buffer::{ExcerptRange, MultiBufferOffset};
     use text::Selection;
 
     async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext {
@@ -842,9 +843,9 @@ mod jsx_tag_autoclose_tests {
         cx.update_editor(|editor, window, cx| {
             editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
                 selections.select(vec![
-                    Selection::from_offset(4),
-                    Selection::from_offset(9),
-                    Selection::from_offset(15),
+                    Selection::from_offset(MultiBufferOffset(4)),
+                    Selection::from_offset(MultiBufferOffset(9)),
+                    Selection::from_offset(MultiBufferOffset(15)),
                 ])
             })
         });

crates/editor/src/linked_editing_ranges.rs 🔗

@@ -1,6 +1,7 @@
 use collections::HashMap;
 use gpui::{AppContext, Context, Window};
 use itertools::Itertools;
+use multi_buffer::MultiBufferOffset;
 use std::{ops::Range, time::Duration};
 use text::{AnchorRangeExt, BufferId, ToPoint};
 use util::ResultExt;
@@ -60,7 +61,9 @@ pub(super) fn refresh_linked_ranges(
         editor
             .update(cx, |editor, cx| {
                 let display_snapshot = editor.display_snapshot(cx);
-                let selections = editor.selections.all::<usize>(&display_snapshot);
+                let selections = editor
+                    .selections
+                    .all::<MultiBufferOffset>(&display_snapshot);
                 let snapshot = display_snapshot.buffer_snapshot();
                 let buffer = editor.buffer.read(cx);
                 for selection in selections {

crates/editor/src/movement.rs 🔗

@@ -8,7 +8,7 @@ use crate::{
 };
 use gpui::{Pixels, WindowTextSystem};
 use language::{CharClassifier, Point};
-use multi_buffer::{MultiBufferRow, MultiBufferSnapshot};
+use multi_buffer::{MultiBufferOffset, MultiBufferRow, MultiBufferSnapshot};
 use serde::Deserialize;
 use workspace::searchable::Direction;
 
@@ -358,28 +358,28 @@ pub fn adjust_greedy_deletion(
 
     let mut whitespace_sequences = Vec::new();
     let mut current_offset = trimmed_delete_range.start;
-    let mut whitespace_sequence_length = 0;
-    let mut whitespace_sequence_start = 0;
+    let mut whitespace_sequence_length = MultiBufferOffset(0);
+    let mut whitespace_sequence_start = MultiBufferOffset(0);
     for ch in map
         .buffer_snapshot()
         .text_for_range(trimmed_delete_range.clone())
         .flat_map(str::chars)
     {
         if ch.is_whitespace() {
-            if whitespace_sequence_length == 0 {
+            if whitespace_sequence_length == MultiBufferOffset(0) {
                 whitespace_sequence_start = current_offset;
             }
             whitespace_sequence_length += 1;
         } else {
-            if whitespace_sequence_length >= 2 {
+            if whitespace_sequence_length >= MultiBufferOffset(2) {
                 whitespace_sequences.push((whitespace_sequence_start, current_offset));
             }
-            whitespace_sequence_start = 0;
-            whitespace_sequence_length = 0;
+            whitespace_sequence_start = MultiBufferOffset(0);
+            whitespace_sequence_length = MultiBufferOffset(0);
         }
         current_offset += ch.len_utf8();
     }
-    if whitespace_sequence_length >= 2 {
+    if whitespace_sequence_length >= MultiBufferOffset(2) {
         whitespace_sequences.push((whitespace_sequence_start, current_offset));
     }
 
@@ -731,7 +731,7 @@ pub fn find_preceding_boundary_trail(
     }
 
     let trail = trail_offset
-        .map(|trail_offset: usize| map.clip_point(trail_offset.to_display_point(map), Bias::Left));
+        .map(|trail_offset| map.clip_point(trail_offset.to_display_point(map), Bias::Left));
 
     (
         trail,
@@ -779,7 +779,7 @@ pub fn find_boundary_trail(
     }
 
     let trail = trail_offset
-        .map(|trail_offset: usize| map.clip_point(trail_offset.to_display_point(map), Bias::Right));
+        .map(|trail_offset| map.clip_point(trail_offset.to_display_point(map), Bias::Right));
 
     (
         trail,
@@ -810,8 +810,8 @@ pub fn find_boundary_exclusive(
 /// the [`DisplaySnapshot`]. The offsets are relative to the start of a buffer.
 pub fn chars_after(
     map: &DisplaySnapshot,
-    mut offset: usize,
-) -> impl Iterator<Item = (char, Range<usize>)> + '_ {
+    mut offset: MultiBufferOffset,
+) -> impl Iterator<Item = (char, Range<MultiBufferOffset>)> + '_ {
     map.buffer_snapshot().chars_at(offset).map(move |ch| {
         let before = offset;
         offset += ch.len_utf8();
@@ -824,8 +824,8 @@ pub fn chars_after(
 /// the [`DisplaySnapshot`]. The offsets are relative to the start of a buffer.
 pub fn chars_before(
     map: &DisplaySnapshot,
-    mut offset: usize,
-) -> impl Iterator<Item = (char, Range<usize>)> + '_ {
+    mut offset: MultiBufferOffset,
+) -> impl Iterator<Item = (char, Range<MultiBufferOffset>)> + '_ {
     map.buffer_snapshot()
         .reversed_chars_at(offset)
         .map(move |ch| {
@@ -1018,8 +1018,9 @@ mod tests {
 
         // add all kinds of inlays between two word boundaries: we should be able to cross them all, when looking for another boundary
         let mut id = 0;
-        let inlays = (0..buffer_snapshot.len())
+        let inlays = (0..buffer_snapshot.len().0)
             .flat_map(|offset| {
+                let offset = MultiBufferOffset(offset);
                 [
                     Inlay::edit_prediction(
                         post_inc(&mut id),
@@ -1058,7 +1059,7 @@ mod tests {
             ),
             snapshot
                 .buffer_snapshot()
-                .offset_to_point(5)
+                .offset_to_point(MultiBufferOffset(5))
                 .to_display_point(&snapshot),
             "Should not stop at inlays when looking for boundaries"
         );

crates/editor/src/selections_collection.rs 🔗

@@ -1,18 +1,18 @@
 use std::{
     cmp, fmt, iter, mem,
-    ops::{Deref, DerefMut, Range, Sub},
+    ops::{AddAssign, Deref, DerefMut, Range, Sub},
     sync::Arc,
 };
 
 use collections::HashMap;
 use gpui::Pixels;
 use itertools::Itertools as _;
-use language::{Bias, Point, Selection, SelectionGoal, TextDimension};
+use language::{Bias, Point, Selection, SelectionGoal};
+use multi_buffer::{MultiBufferDimension, MultiBufferOffset};
 use util::post_inc;
 
 use crate::{
     Anchor, DisplayPoint, DisplayRow, ExcerptId, MultiBufferSnapshot, SelectMode, ToOffset,
-    ToPoint,
     display_map::{DisplaySnapshot, ToDisplayPoint},
     movement::TextLayoutDetails,
 };
@@ -97,7 +97,7 @@ impl SelectionsCollection {
         if self.pending.is_none() {
             self.disjoint_anchors_arc()
         } else {
-            let all_offset_selections = self.all::<usize>(snapshot);
+            let all_offset_selections = self.all::<MultiBufferOffset>(snapshot);
             all_offset_selections
                 .into_iter()
                 .map(|selection| selection_to_anchor_selection(selection, snapshot))
@@ -113,10 +113,10 @@ impl SelectionsCollection {
         self.pending.as_mut().map(|pending| &mut pending.selection)
     }
 
-    pub fn pending<D: TextDimension + Ord + Sub<D, Output = D>>(
-        &self,
-        snapshot: &DisplaySnapshot,
-    ) -> Option<Selection<D>> {
+    pub fn pending<D>(&self, snapshot: &DisplaySnapshot) -> Option<Selection<D>>
+    where
+        D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
+    {
         resolve_selections_wrapping_blocks(self.pending_anchor(), &snapshot).next()
     }
 
@@ -124,9 +124,9 @@ impl SelectionsCollection {
         self.pending.as_ref().map(|pending| pending.mode.clone())
     }
 
-    pub fn all<'a, D>(&self, snapshot: &DisplaySnapshot) -> Vec<Selection<D>>
+    pub fn all<D>(&self, snapshot: &DisplaySnapshot) -> Vec<Selection<D>>
     where
-        D: 'a + TextDimension + Ord + Sub<D, Output = D>,
+        D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
     {
         let disjoint_anchors = &self.disjoint;
         let mut disjoint =
@@ -204,13 +204,13 @@ impl SelectionsCollection {
         }
     }
 
-    pub fn disjoint_in_range<'a, D>(
+    pub fn disjoint_in_range<D>(
         &self,
         range: Range<Anchor>,
         snapshot: &DisplaySnapshot,
     ) -> Vec<Selection<D>>
     where
-        D: 'a + TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug,
+        D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord + std::fmt::Debug,
     {
         let start_ix = match self
             .disjoint
@@ -267,10 +267,10 @@ impl SelectionsCollection {
             .unwrap()
     }
 
-    pub fn newest<D: TextDimension + Ord + Sub<D, Output = D>>(
-        &self,
-        snapshot: &DisplaySnapshot,
-    ) -> Selection<D> {
+    pub fn newest<D>(&self, snapshot: &DisplaySnapshot) -> Selection<D>
+    where
+        D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
+    {
         resolve_selections_wrapping_blocks([self.newest_anchor()], &snapshot)
             .next()
             .unwrap()
@@ -290,10 +290,10 @@ impl SelectionsCollection {
             .unwrap()
     }
 
-    pub fn oldest<D: TextDimension + Ord + Sub<D, Output = D>>(
-        &self,
-        snapshot: &DisplaySnapshot,
-    ) -> Selection<D> {
+    pub fn oldest<D>(&self, snapshot: &DisplaySnapshot) -> Selection<D>
+    where
+        D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
+    {
         resolve_selections_wrapping_blocks([self.oldest_anchor()], &snapshot)
             .next()
             .unwrap()
@@ -306,27 +306,27 @@ impl SelectionsCollection {
             .unwrap_or_else(|| self.disjoint.first().cloned().unwrap())
     }
 
-    pub fn first<D: TextDimension + Ord + Sub<D, Output = D>>(
-        &self,
-        snapshot: &DisplaySnapshot,
-    ) -> Selection<D> {
+    pub fn first<D>(&self, snapshot: &DisplaySnapshot) -> Selection<D>
+    where
+        D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
+    {
         self.all(snapshot).first().unwrap().clone()
     }
 
-    pub fn last<D: TextDimension + Ord + Sub<D, Output = D>>(
-        &self,
-        snapshot: &DisplaySnapshot,
-    ) -> Selection<D> {
+    pub fn last<D>(&self, snapshot: &DisplaySnapshot) -> Selection<D>
+    where
+        D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
+    {
         self.all(snapshot).last().unwrap().clone()
     }
 
     /// Returns a list of (potentially backwards!) ranges representing the selections.
     /// Useful for test assertions, but prefer `.all()` instead.
     #[cfg(any(test, feature = "test-support"))]
-    pub fn ranges<D: TextDimension + Ord + Sub<D, Output = D>>(
-        &self,
-        snapshot: &DisplaySnapshot,
-    ) -> Vec<Range<D>> {
+    pub fn ranges<D>(&self, snapshot: &DisplaySnapshot) -> Vec<Range<D>>
+    where
+        D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
+    {
         self.all::<D>(snapshot)
             .iter()
             .map(|s| {
@@ -509,7 +509,7 @@ impl<'snap, 'a> MutableSelectionsCollection<'snap, 'a> {
         };
 
         if filtered_selections.is_empty() {
-            let default_anchor = self.snapshot.anchor_before(0);
+            let default_anchor = self.snapshot.anchor_before(MultiBufferOffset(0));
             self.collection.disjoint = Arc::from([Selection {
                 id: post_inc(&mut self.collection.next_selection_id),
                 start: default_anchor,
@@ -590,7 +590,7 @@ impl<'snap, 'a> MutableSelectionsCollection<'snap, 'a> {
 
     pub fn insert_range<T>(&mut self, range: Range<T>)
     where
-        T: 'a + ToOffset + ToPoint + TextDimension + Ord + Sub<T, Output = T> + std::marker::Copy,
+        T: ToOffset,
     {
         let display_map = self.display_snapshot();
         let mut selections = self.collection.all(&display_map);
@@ -656,7 +656,8 @@ impl<'snap, 'a> MutableSelectionsCollection<'snap, 'a> {
     pub fn select_anchors(&mut self, selections: Vec<Selection<Anchor>>) {
         let map = self.display_snapshot();
         let resolved_selections =
-            resolve_selections_wrapping_blocks::<usize, _>(&selections, &map).collect::<Vec<_>>();
+            resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(&selections, &map)
+                .collect::<Vec<_>>();
         self.select(resolved_selections);
     }
 
@@ -673,7 +674,7 @@ impl<'snap, 'a> MutableSelectionsCollection<'snap, 'a> {
 
     fn select_offset_ranges<I>(&mut self, ranges: I)
     where
-        I: IntoIterator<Item = Range<usize>>,
+        I: IntoIterator<Item = Range<MultiBufferOffset>>,
     {
         let selections = ranges
             .into_iter()
@@ -808,13 +809,13 @@ impl<'snap, 'a> MutableSelectionsCollection<'snap, 'a> {
 
     pub fn move_offsets_with(
         &mut self,
-        mut move_selection: impl FnMut(&MultiBufferSnapshot, &mut Selection<usize>),
+        mut move_selection: impl FnMut(&MultiBufferSnapshot, &mut Selection<MultiBufferOffset>),
     ) {
         let mut changed = false;
         let display_map = self.display_snapshot();
         let selections = self
             .collection
-            .all::<usize>(&display_map)
+            .all::<MultiBufferOffset>(&display_map)
             .into_iter()
             .map(|selection| {
                 let mut moved_selection = selection.clone();
@@ -938,7 +939,7 @@ impl<'snap, 'a> MutableSelectionsCollection<'snap, 'a> {
             let map = self.display_snapshot();
             let resolved_selections =
                 resolve_selections_wrapping_blocks(adjusted_disjoint.iter(), &map).collect();
-            self.select::<usize>(resolved_selections);
+            self.select::<MultiBufferOffset>(resolved_selections);
         }
 
         if let Some(pending) = pending.as_mut() {
@@ -981,7 +982,7 @@ impl DerefMut for MutableSelectionsCollection<'_, '_> {
 }
 
 fn selection_to_anchor_selection(
-    selection: Selection<usize>,
+    selection: Selection<MultiBufferOffset>,
     buffer: &MultiBufferSnapshot,
 ) -> Selection<Anchor> {
     let end_bias = if selection.start == selection.end {
@@ -1054,7 +1055,7 @@ fn resolve_selections_display<'a>(
     coalesce_selections(selections)
 }
 
-/// Resolves the passed in anchors to [`TextDimension`]s `D`
+/// Resolves the passed in anchors to [`MultiBufferDimension`]s `D`
 /// wrapping around blocks inbetween.
 ///
 /// # Panics
@@ -1065,7 +1066,7 @@ pub(crate) fn resolve_selections_wrapping_blocks<'a, D, I>(
     map: &'a DisplaySnapshot,
 ) -> impl 'a + Iterator<Item = Selection<D>>
 where
-    D: TextDimension + Ord + Sub<D, Output = D>,
+    D: MultiBufferDimension + Sub + AddAssign<<D as Sub>::Output> + Ord,
     I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
 {
     // Transforms `Anchor -> DisplayPoint -> Point -> DisplayPoint -> D`

crates/editor/src/signature_help.rs 🔗

@@ -1,13 +1,13 @@
 use crate::actions::ShowSignatureHelp;
 use crate::hover_popover::open_markdown_url;
-use crate::{Editor, EditorSettings, ToggleAutoSignatureHelp, hover_markdown_style};
+use crate::{BufferOffset, Editor, EditorSettings, ToggleAutoSignatureHelp, hover_markdown_style};
 use gpui::{
     App, Context, Entity, HighlightStyle, MouseButton, ScrollHandle, Size, StyledText, Task,
     TextStyle, Window, combine_highlights,
 };
 use language::BufferSnapshot;
 use markdown::{Markdown, MarkdownElement};
-use multi_buffer::{Anchor, ToOffset};
+use multi_buffer::{Anchor, MultiBufferOffset, ToOffset};
 use settings::Settings;
 use std::ops::Range;
 use text::Rope;
@@ -82,7 +82,9 @@ impl Editor {
         if !(self.signature_help_state.is_shown() || self.auto_signature_help_enabled(cx)) {
             return false;
         }
-        let newest_selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
+        let newest_selection = self
+            .selections
+            .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
         let head = newest_selection.head();
 
         if !newest_selection.is_empty() && head != newest_selection.tail() {
@@ -92,14 +94,14 @@ impl Editor {
         }
 
         let buffer_snapshot = self.buffer().read(cx).snapshot(cx);
-        let bracket_range = |position: usize| match (position, position + 1) {
-            (0, b) if b <= buffer_snapshot.len() => 0..b,
-            (0, b) => 0..b - 1,
+        let bracket_range = |position: MultiBufferOffset| match (position, position + 1usize) {
+            (MultiBufferOffset(0), b) if b <= buffer_snapshot.len() => MultiBufferOffset(0)..b,
+            (MultiBufferOffset(0), b) => MultiBufferOffset(0)..b - 1,
             (a, b) if b <= buffer_snapshot.len() => a - 1..b,
             (a, b) => a - 1..b - 1,
         };
         let not_quote_like_brackets =
-            |buffer: &BufferSnapshot, start: Range<usize>, end: Range<usize>| {
+            |buffer: &BufferSnapshot, start: Range<BufferOffset>, end: Range<BufferOffset>| {
                 let text_start = buffer.text_for_range(start).collect::<String>();
                 let text_end = buffer.text_for_range(end).collect::<String>();
                 QUOTE_PAIRS

crates/editor/src/test.rs 🔗

@@ -16,7 +16,7 @@ use gpui::{
     AppContext as _, Context, Entity, EntityId, Font, FontFeatures, FontStyle, FontWeight, Pixels,
     VisualTestContext, Window, font, size,
 };
-use multi_buffer::ToPoint;
+use multi_buffer::{MultiBufferOffset, ToPoint};
 use pretty_assertions::assert_eq;
 use project::{Project, project_settings::DiagnosticSeverity};
 use ui::{App, BorrowAppContext, px};
@@ -78,7 +78,7 @@ pub fn marked_display_snapshot(
     let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
     let markers = markers
         .into_iter()
-        .map(|offset| offset.to_display_point(&snapshot))
+        .map(|offset| MultiBufferOffset(offset).to_display_point(&snapshot))
         .collect();
 
     (snapshot, markers)
@@ -94,7 +94,11 @@ pub fn select_ranges(
     let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
     assert_eq!(editor.text(cx), unmarked_text);
     editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
-        s.select_ranges(text_ranges)
+        s.select_ranges(
+            text_ranges
+                .into_iter()
+                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
+        )
     });
 }
 
@@ -108,7 +112,12 @@ pub fn assert_text_with_selections(
     assert_eq!(editor.text(cx), unmarked_text, "text doesn't match");
     let actual = generate_marked_text(
         &editor.text(cx),
-        &editor.selections.ranges(&editor.display_snapshot(cx)),
+        &editor
+            .selections
+            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx))
+            .into_iter()
+            .map(|range| range.start.0..range.end.0)
+            .collect::<Vec<_>>(),
         marked_text.contains("«"),
     );
     assert_eq!(actual, marked_text, "Selections don't match");

crates/editor/src/test/editor_lsp_test_context.rs 🔗

@@ -7,6 +7,7 @@ use std::{
 
 use anyhow::Result;
 use language::{markdown_lang, rust_lang};
+use multi_buffer::MultiBufferOffset;
 use serde_json::json;
 
 use crate::{Editor, ToPoint};
@@ -333,50 +334,38 @@ impl EditorLspTestContext {
     #[track_caller]
     pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
         let ranges = self.ranges(marked_text);
-        self.to_lsp_range(ranges[0].clone())
+        self.to_lsp_range(MultiBufferOffset(ranges[0].start)..MultiBufferOffset(ranges[0].end))
     }
 
     #[expect(clippy::wrong_self_convention, reason = "This is test code")]
-    pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
+    pub fn to_lsp_range(&mut self, range: Range<MultiBufferOffset>) -> lsp::Range {
+        use language::ToPointUtf16;
         let snapshot = self.update_editor(|editor, window, cx| editor.snapshot(window, cx));
         let start_point = range.start.to_point(&snapshot.buffer_snapshot());
         let end_point = range.end.to_point(&snapshot.buffer_snapshot());
 
         self.editor(|editor, _, cx| {
             let buffer = editor.buffer().read(cx);
-            let start = point_to_lsp(
-                buffer
-                    .point_to_buffer_offset(start_point, cx)
-                    .unwrap()
-                    .1
-                    .to_point_utf16(&buffer.read(cx)),
-            );
-            let end = point_to_lsp(
-                buffer
-                    .point_to_buffer_offset(end_point, cx)
-                    .unwrap()
-                    .1
-                    .to_point_utf16(&buffer.read(cx)),
-            );
-
+            let (start_buffer, start_offset) =
+                buffer.point_to_buffer_offset(start_point, cx).unwrap();
+            let start = point_to_lsp(start_offset.to_point_utf16(&start_buffer.read(cx)));
+            let (end_buffer, end_offset) = buffer.point_to_buffer_offset(end_point, cx).unwrap();
+            let end = point_to_lsp(end_offset.to_point_utf16(&end_buffer.read(cx)));
             lsp::Range { start, end }
         })
     }
 
     #[expect(clippy::wrong_self_convention, reason = "This is test code")]
-    pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
+    pub fn to_lsp(&mut self, offset: MultiBufferOffset) -> lsp::Position {
+        use language::ToPointUtf16;
+
         let snapshot = self.update_editor(|editor, window, cx| editor.snapshot(window, cx));
         let point = offset.to_point(&snapshot.buffer_snapshot());
 
         self.editor(|editor, _, cx| {
             let buffer = editor.buffer().read(cx);
-            point_to_lsp(
-                buffer
-                    .point_to_buffer_offset(point, cx)
-                    .unwrap()
-                    .1
-                    .to_point_utf16(&buffer.read(cx)),
-            )
+            let (buffer, offset) = buffer.point_to_buffer_offset(point, cx).unwrap();
+            point_to_lsp(offset.to_point_utf16(&buffer.read(cx)))
         })
     }
 

crates/editor/src/test/editor_test_context.rs 🔗

@@ -13,7 +13,7 @@ use gpui::{
 };
 use itertools::Itertools;
 use language::{Buffer, BufferSnapshot, LanguageRegistry};
-use multi_buffer::{Anchor, ExcerptRange, MultiBufferRow};
+use multi_buffer::{Anchor, ExcerptRange, MultiBufferOffset, MultiBufferRow};
 use parking_lot::RwLock;
 use project::{FakeFs, Project};
 use std::{
@@ -267,7 +267,7 @@ impl EditorTestContext {
         let snapshot = self.editor.update_in(&mut self.cx, |editor, window, cx| {
             editor.snapshot(window, cx)
         });
-        ranges[0].start.to_display_point(&snapshot)
+        MultiBufferOffset(ranges[0].start).to_display_point(&snapshot)
     }
 
     pub fn pixel_position(&mut self, marked_text: &str) -> Point<Pixels> {
@@ -373,7 +373,11 @@ impl EditorTestContext {
         self.editor.update_in(&mut self.cx, |editor, window, cx| {
             editor.set_text(unmarked_text, window, cx);
             editor.change_selections(Default::default(), window, cx, |s| {
-                s.select_ranges(selection_ranges)
+                s.select_ranges(
+                    selection_ranges
+                        .into_iter()
+                        .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
+                )
             })
         });
         state_context
@@ -390,7 +394,11 @@ impl EditorTestContext {
         self.editor.update_in(&mut self.cx, |editor, window, cx| {
             assert_eq!(editor.text(cx), unmarked_text);
             editor.change_selections(Default::default(), window, cx, |s| {
-                s.select_ranges(selection_ranges)
+                s.select_ranges(
+                    selection_ranges
+                        .into_iter()
+                        .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
+                )
             })
         });
         state_context
@@ -576,6 +584,7 @@ impl EditorTestContext {
                 .unwrap_or_default()
                 .iter()
                 .map(|range| range.to_offset(&snapshot.buffer_snapshot()))
+                .map(|range| range.start.0..range.end.0)
                 .collect()
         });
         assert_set_eq!(actual_ranges, expected_ranges);
@@ -591,6 +600,7 @@ impl EditorTestContext {
             .unwrap_or_default()
             .into_iter()
             .map(|range| range.to_offset(&snapshot.buffer_snapshot()))
+            .map(|range| range.start.0..range.end.0)
             .collect();
         assert_set_eq!(actual_ranges, expected_ranges);
     }
@@ -608,14 +618,16 @@ impl EditorTestContext {
     fn editor_selections(&mut self) -> Vec<Range<usize>> {
         self.editor
             .update(&mut self.cx, |editor, cx| {
-                editor.selections.all::<usize>(&editor.display_snapshot(cx))
+                editor
+                    .selections
+                    .all::<MultiBufferOffset>(&editor.display_snapshot(cx))
             })
             .into_iter()
             .map(|s| {
                 if s.reversed {
-                    s.end..s.start
+                    s.end.0..s.start.0
                 } else {
-                    s.start..s.end
+                    s.start.0..s.end.0
                 }
             })
             .collect::<Vec<_>>()
@@ -711,7 +723,10 @@ pub fn assert_state_with_diff(
             snapshot.buffer_snapshot().clone(),
             editor
                 .selections
-                .ranges::<usize>(&snapshot.display_snapshot),
+                .ranges::<MultiBufferOffset>(&snapshot.display_snapshot)
+                .into_iter()
+                .map(|range| range.start.0..range.end.0)
+                .collect::<Vec<_>>(),
         )
     });
 

crates/git_ui/src/commit_view.rs 🔗

@@ -1,6 +1,9 @@
 use anyhow::{Context as _, Result};
 use buffer_diff::{BufferDiff, BufferDiffSnapshot};
-use editor::{Editor, EditorEvent, MultiBuffer, SelectionEffects, multibuffer_context_lines};
+use editor::{
+    Editor, EditorEvent, MultiBuffer, MultiBufferOffset, SelectionEffects,
+    multibuffer_context_lines,
+};
 use git::repository::{CommitDetails, CommitDiff, RepoPath};
 use gpui::{
     Action, AnyElement, AnyView, App, AppContext as _, AsyncApp, AsyncWindowContext, Context,
@@ -187,7 +190,7 @@ impl CommitView {
             editor.update(cx, |editor, cx| {
                 editor.disable_header_for_buffer(metadata_buffer_id.unwrap(), cx);
                 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
-                    selections.select_ranges(vec![0..0]);
+                    selections.select_ranges(vec![MultiBufferOffset(0)..MultiBufferOffset(0)]);
                 });
             });
         }

crates/git_ui/src/git_panel.rs 🔗

@@ -13,7 +13,8 @@ use anyhow::Context as _;
 use askpass::AskPassDelegate;
 use db::kvp::KEY_VALUE_STORE;
 use editor::{
-    Direction, Editor, EditorElement, EditorMode, MultiBuffer, actions::ExpandAllDiffHunks,
+    Direction, Editor, EditorElement, EditorMode, MultiBuffer, MultiBufferOffset,
+    actions::ExpandAllDiffHunks,
 };
 use futures::StreamExt as _;
 use git::blame::ParsedCommitMessage;
@@ -1551,7 +1552,10 @@ impl GitPanel {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Option<String> {
-        let git_commit_language = self.commit_editor.read(cx).language_at(0, cx);
+        let git_commit_language = self
+            .commit_editor
+            .read(cx)
+            .language_at(MultiBufferOffset(0), cx);
         let message = self.commit_editor.read(cx).text(cx);
         if message.is_empty() {
             return self

crates/git_ui/src/project_diff.rs 🔗

@@ -520,7 +520,8 @@ impl ProjectDiff {
             if was_empty {
                 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
                     // TODO select the very beginning (possibly inside a deletion)
-                    selections.select_ranges([0..0])
+                    selections
+                        .select_ranges([multi_buffer::Anchor::min()..multi_buffer::Anchor::min()])
                 });
             }
             if is_excerpt_newly_added

crates/git_ui/src/text_diff_view.rs 🔗

@@ -446,7 +446,7 @@ impl Render for TextDiffView {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use editor::test::editor_test_context::assert_state_with_diff;
+    use editor::{MultiBufferOffset, test::editor_test_context::assert_state_with_diff};
     use gpui::{TestAppContext, VisualContext};
     use project::{FakeFs, Project};
     use serde_json::json;
@@ -691,7 +691,11 @@ mod tests {
             let (unmarked_text, selection_ranges) = marked_text_ranges(editor_text, false);
             editor.set_text(unmarked_text, window, cx);
             editor.change_selections(Default::default(), window, cx, |s| {
-                s.select_ranges(selection_ranges)
+                s.select_ranges(
+                    selection_ranges
+                        .into_iter()
+                        .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
+                )
             });
 
             editor

crates/go_to_line/src/cursor_position.rs 🔗

@@ -1,4 +1,4 @@
-use editor::{Editor, EditorEvent, MultiBufferSnapshot};
+use editor::{Editor, EditorEvent, MBTextSummary, MultiBufferSnapshot};
 use gpui::{App, Entity, FocusHandle, Focusable, Styled, Subscription, Task, WeakEntity};
 use settings::{RegisterSetting, Settings};
 use std::{fmt::Write, num::NonZeroU32, time::Duration};
@@ -55,7 +55,7 @@ impl UserCaretPosition {
             let line_start = Point::new(selection_end.row, 0);
 
             let chars_to_last_position = snapshot
-                .text_summary_for_range::<text::TextSummary, _>(line_start..selection_end)
+                .text_summary_for_range::<MBTextSummary, _>(line_start..selection_end)
                 .chars as u32;
             (selection_end.row, chars_to_last_position)
         };
@@ -116,7 +116,7 @@ impl CursorPosition {
                                     for selection in editor.selections.all_adjusted(&snapshot) {
                                         let selection_summary = snapshot
                                             .buffer_snapshot()
-                                            .text_summary_for_range::<text::TextSummary, _>(
+                                            .text_summary_for_range::<MBTextSummary, _>(
                                             selection.start..selection.end,
                                         );
                                         cursor_position.selected_count.characters +=

crates/journal/src/journal.rs 🔗

@@ -159,7 +159,7 @@ pub fn new_journal_entry(workspace: &Workspace, window: &mut Window, cx: &mut Ap
                         cx,
                         |s| s.select_ranges([len..len]),
                     );
-                    if len > 0 {
+                    if len.0 > 0 {
                         editor.insert("\n\n", window, cx);
                     }
                     editor.insert(&entry_heading, window, cx);

crates/language/src/buffer.rs 🔗

@@ -2083,7 +2083,7 @@ impl Buffer {
     }
 
     /// Gets a [`Subscription`] that tracks all of the changes to the buffer's text.
-    pub fn subscribe(&mut self) -> Subscription {
+    pub fn subscribe(&mut self) -> Subscription<usize> {
         self.text.subscribe()
     }
 

crates/language_tools/src/lsp_log_view.rs 🔗

@@ -1,6 +1,6 @@
 use collections::VecDeque;
 use copilot::Copilot;
-use editor::{Editor, EditorEvent, actions::MoveToEnd, scroll::Autoscroll};
+use editor::{Editor, EditorEvent, MultiBufferOffset, actions::MoveToEnd, scroll::Autoscroll};
 use gpui::{
     AnyView, App, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
     ParentElement, Render, Styled, Subscription, Task, WeakEntity, Window, actions, div,
@@ -231,7 +231,7 @@ impl LspLogView {
                             let last_offset = editor.buffer().read(cx).len(cx);
                             let newest_cursor_is_at_end = editor
                                 .selections
-                                .newest::<usize>(&editor.display_snapshot(cx))
+                                .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
                                 .start
                                 >= last_offset;
                             editor.edit(

crates/language_tools/src/syntax_tree_view.rs 🔗

@@ -1,5 +1,5 @@
 use command_palette_hooks::CommandPaletteFilter;
-use editor::{Anchor, Editor, ExcerptId, SelectionEffects, scroll::Autoscroll};
+use editor::{Anchor, Editor, ExcerptId, MultiBufferOffset, SelectionEffects, scroll::Autoscroll};
 use gpui::{
     App, AppContext as _, Context, Div, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
     Hsla, InteractiveElement, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent,
@@ -254,7 +254,7 @@ impl SyntaxTreeView {
         let (buffer, range, excerpt_id) = editor_state.editor.update(cx, |editor, cx| {
             let selection_range = editor
                 .selections
-                .last::<usize>(&editor.display_snapshot(cx))
+                .last::<MultiBufferOffset>(&editor.display_snapshot(cx))
                 .range();
             let multi_buffer = editor.buffer().read(cx);
             let (buffer, range, excerpt_id) = snapshot
@@ -308,8 +308,8 @@ impl SyntaxTreeView {
         // Within the active layer, find the syntax node under the cursor,
         // and scroll to it.
         let mut cursor = layer.node().walk();
-        while cursor.goto_first_child_for_byte(range.start).is_some() {
-            if !range.is_empty() && cursor.node().end_byte() == range.start {
+        while cursor.goto_first_child_for_byte(range.start.0).is_some() {
+            if !range.is_empty() && cursor.node().end_byte() == range.start.0 {
                 cursor.goto_next_sibling();
             }
         }
@@ -317,7 +317,7 @@ impl SyntaxTreeView {
         // Ascend to the smallest ancestor that contains the range.
         loop {
             let node_range = cursor.node().byte_range();
-            if node_range.start <= range.start && node_range.end >= range.end {
+            if node_range.start <= range.start.0 && node_range.end >= range.end.0 {
                 break;
             }
             if !cursor.goto_parent() {

crates/markdown_preview/src/markdown_preview_view.rs 🔗

@@ -4,7 +4,7 @@ use std::{ops::Range, path::PathBuf};
 
 use anyhow::Result;
 use editor::scroll::Autoscroll;
-use editor::{Editor, EditorEvent, SelectionEffects};
+use editor::{Editor, EditorEvent, MultiBufferOffset, SelectionEffects};
 use gpui::{
     App, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
     IntoElement, IsZero, ListState, ParentElement, Render, RetainAllImageCache, Styled,
@@ -281,7 +281,7 @@ impl MarkdownPreviewView {
                         let selection_range = editor.update(cx, |editor, cx| {
                             editor
                                 .selections
-                                .last::<usize>(&editor.display_snapshot(cx))
+                                .last::<MultiBufferOffset>(&editor.display_snapshot(cx))
                                 .range()
                         });
                         this.selected_block = this.get_block_index_under_cursor(selection_range);
@@ -358,7 +358,7 @@ impl MarkdownPreviewView {
         &self,
         window: &mut Window,
         cx: &mut Context<Self>,
-        selection: Range<usize>,
+        selection: Range<MultiBufferOffset>,
     ) {
         if let Some(state) = &self.active_editor {
             state.editor.update(cx, |editor, cx| {
@@ -375,7 +375,7 @@ impl MarkdownPreviewView {
 
     /// The absolute path of the file that is currently being previewed.
     fn get_folder_for_active_editor(editor: &Editor, cx: &App) -> Option<PathBuf> {
-        if let Some(file) = editor.file_at(0, cx) {
+        if let Some(file) = editor.file_at(MultiBufferOffset(0), cx) {
             if let Some(file) = file.as_local() {
                 file.abs_path(cx).parent().map(|p| p.to_path_buf())
             } else {
@@ -386,9 +386,9 @@ impl MarkdownPreviewView {
         }
     }
 
-    fn get_block_index_under_cursor(&self, selection_range: Range<usize>) -> usize {
+    fn get_block_index_under_cursor(&self, selection_range: Range<MultiBufferOffset>) -> usize {
         let mut block_index = None;
-        let cursor = selection_range.start;
+        let cursor = selection_range.start.0;
 
         let mut last_end = 0;
         if let Some(content) = &self.contents {
@@ -524,7 +524,15 @@ impl Render for MarkdownPreviewView {
                                                         if e.checked() { "[x]" } else { "[ ]" };
 
                                                     editor.edit(
-                                                        vec![(e.source_range(), task_marker)],
+                                                        vec![(
+                                                            MultiBufferOffset(
+                                                                e.source_range().start,
+                                                            )
+                                                                ..MultiBufferOffset(
+                                                                    e.source_range().end,
+                                                                ),
+                                                            task_marker,
+                                                        )],
                                                         cx,
                                                     );
                                                 });
@@ -564,7 +572,8 @@ impl Render for MarkdownPreviewView {
                                             this.move_cursor_to_block(
                                                 window,
                                                 cx,
-                                                source_range.start..source_range.start,
+                                                MultiBufferOffset(source_range.start)
+                                                    ..MultiBufferOffset(source_range.start),
                                             );
                                         }
                                     },

crates/multi_buffer/src/anchor.rs 🔗

@@ -1,8 +1,10 @@
+use crate::{MultiBufferDimension, MultiBufferOffset, MultiBufferOffsetUtf16};
+
 use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToPoint};
-use language::{OffsetUtf16, Point, TextDimension};
+use language::Point;
 use std::{
     cmp::Ordering,
-    ops::{Range, Sub},
+    ops::{AddAssign, Range, Sub},
 };
 use sum_tree::Bias;
 use text::BufferId;
@@ -162,7 +164,11 @@ impl Anchor {
 
     pub fn summary<D>(&self, snapshot: &MultiBufferSnapshot) -> D
     where
-        D: TextDimension + Ord + Sub<D, Output = D>,
+        D: MultiBufferDimension
+            + Ord
+            + Sub<Output = D::TextDimension>
+            + AddAssign<D::TextDimension>,
+        D::TextDimension: Sub<Output = D::TextDimension> + Ord,
     {
         snapshot.summary_for_anchor(self)
     }
@@ -182,10 +188,10 @@ impl Anchor {
 }
 
 impl ToOffset for Anchor {
-    fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize {
+    fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset {
         self.summary(snapshot)
     }
-    fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16 {
+    fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16 {
         self.summary(snapshot)
     }
 }
@@ -203,7 +209,7 @@ pub trait AnchorRangeExt {
     fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering;
     fn includes(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
     fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
-    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize>;
+    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<MultiBufferOffset>;
     fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point>;
 }
 
@@ -223,7 +229,7 @@ impl AnchorRangeExt for Range<Anchor> {
         self.end.cmp(&other.start, buffer).is_ge() && self.start.cmp(&other.end, buffer).is_le()
     }
 
-    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize> {
+    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<MultiBufferOffset> {
         self.start.to_offset(content)..self.end.to_offset(content)
     }
 
@@ -231,6 +237,3 @@ impl AnchorRangeExt for Range<Anchor> {
         self.start.to_point(content)..self.end.to_point(content)
     }
 }
-
-#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Ord, PartialOrd)]
-pub struct Offset(pub usize);

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -7,7 +7,7 @@ mod transaction;
 
 use self::transaction::History;
 
-pub use anchor::{Anchor, AnchorRangeExt, Offset};
+pub use anchor::{Anchor, AnchorRangeExt};
 pub use position::{TypedOffset, TypedPoint, TypedRow};
 
 use anyhow::{Result, anyhow};
@@ -43,13 +43,13 @@ use std::{
     io,
     iter::{self, FromIterator},
     mem,
-    ops::{Range, RangeBounds, Sub},
+    ops::{self, AddAssign, Range, RangeBounds, Sub},
     rc::Rc,
     str,
     sync::Arc,
     time::Duration,
 };
-use sum_tree::{Bias, Cursor, Dimension, Dimensions, SumTree, Summary, TreeMap};
+use sum_tree::{Bias, Cursor, Dimension, Dimensions, SumTree, TreeMap};
 use text::{
     BufferId, Edit, LineIndent, TextSummary,
     locator::Locator,
@@ -78,7 +78,7 @@ pub struct MultiBuffer {
     paths_by_excerpt: HashMap<ExcerptId, PathKey>,
     /// Mapping from buffer IDs to their diff states
     diffs: HashMap<BufferId, DiffState>,
-    subscriptions: Topic,
+    subscriptions: Topic<MultiBufferOffset>,
     /// If true, the multi-buffer only contains a single [`Buffer`] and a single [`Excerpt`]
     singleton: bool,
     /// The history of the multi-buffer.
@@ -138,7 +138,7 @@ pub struct MultiBufferDiffHunk {
     /// The excerpt that contains the diff hunk.
     pub excerpt_id: ExcerptId,
     /// The range within the buffer's diff base that this hunk corresponds to.
-    pub diff_base_byte_range: Range<usize>,
+    pub diff_base_byte_range: Range<BufferOffset>,
     /// Whether or not this hunk also appears in the 'secondary diff'.
     pub secondary_status: DiffHunkSecondaryStatus,
 }
@@ -159,7 +159,7 @@ impl MultiBufferDiffHunk {
     }
 
     pub fn is_created_file(&self) -> bool {
-        self.diff_base_byte_range == (0..0)
+        self.diff_base_byte_range == (BufferOffset(0)..BufferOffset(0))
             && self.buffer_range == (text::Anchor::MIN..text::Anchor::MAX)
     }
 
@@ -183,7 +183,7 @@ impl MultiBufferRow {
     pub const MAX: Self = Self(u32::MAX);
 }
 
-impl std::ops::Add<usize> for MultiBufferRow {
+impl ops::Add<usize> for MultiBufferRow {
     type Output = Self;
 
     fn add(self, rhs: usize) -> Self::Output {
@@ -191,9 +191,305 @@ impl std::ops::Add<usize> for MultiBufferRow {
     }
 }
 
+pub trait MultiBufferDimension: 'static + Copy + Default + std::fmt::Debug {
+    type TextDimension: TextDimension;
+    fn from_summary(summary: &MBTextSummary) -> Self;
+
+    fn add_text_dim(&mut self, summary: &Self::TextDimension);
+
+    fn add_mb_text_summary(&mut self, summary: &MBTextSummary);
+}
+
+// todo(lw): MultiBufferPoint
+impl MultiBufferDimension for Point {
+    type TextDimension = Point;
+    fn from_summary(summary: &MBTextSummary) -> Self {
+        summary.lines
+    }
+
+    fn add_text_dim(&mut self, other: &Self::TextDimension) {
+        *self += *other;
+    }
+
+    fn add_mb_text_summary(&mut self, summary: &MBTextSummary) {
+        *self += summary.lines;
+    }
+}
+
+// todo(lw): MultiBufferPointUtf16
+impl MultiBufferDimension for PointUtf16 {
+    type TextDimension = PointUtf16;
+    fn from_summary(summary: &MBTextSummary) -> Self {
+        summary.lines_utf16()
+    }
+
+    fn add_text_dim(&mut self, other: &Self::TextDimension) {
+        *self += *other;
+    }
+
+    fn add_mb_text_summary(&mut self, summary: &MBTextSummary) {
+        *self += summary.lines_utf16();
+    }
+}
+
+#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq, Hash, serde::Deserialize)]
+pub struct MultiBufferOffset(pub usize);
+
+impl fmt::Display for MultiBufferOffset {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl rand::distr::uniform::SampleUniform for MultiBufferOffset {
+    type Sampler = MultiBufferOffsetUniformSampler;
+}
+
+pub struct MultiBufferOffsetUniformSampler {
+    sampler: rand::distr::uniform::UniformUsize,
+}
+
+impl rand::distr::uniform::UniformSampler for MultiBufferOffsetUniformSampler {
+    type X = MultiBufferOffset;
+
+    fn new<B1, B2>(low_b: B1, high_b: B2) -> Result<Self, rand::distr::uniform::Error>
+    where
+        B1: rand::distr::uniform::SampleBorrow<Self::X> + Sized,
+        B2: rand::distr::uniform::SampleBorrow<Self::X> + Sized,
+    {
+        let low = *low_b.borrow();
+        let high = *high_b.borrow();
+        let sampler = rand::distr::uniform::UniformUsize::new(low.0, high.0);
+        sampler.map(|sampler| MultiBufferOffsetUniformSampler { sampler })
+    }
+
+    #[inline] // if the range is constant, this helps LLVM to do the
+    // calculations at compile-time.
+    fn new_inclusive<B1, B2>(low_b: B1, high_b: B2) -> Result<Self, rand::distr::uniform::Error>
+    where
+        B1: rand::distr::uniform::SampleBorrow<Self::X> + Sized,
+        B2: rand::distr::uniform::SampleBorrow<Self::X> + Sized,
+    {
+        let low = *low_b.borrow();
+        let high = *high_b.borrow();
+        let sampler = rand::distr::uniform::UniformUsize::new_inclusive(low.0, high.0);
+        sampler.map(|sampler| MultiBufferOffsetUniformSampler { sampler })
+    }
+
+    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Self::X {
+        MultiBufferOffset(self.sampler.sample(rng))
+    }
+}
+impl MultiBufferDimension for MultiBufferOffset {
+    type TextDimension = usize;
+    fn from_summary(summary: &MBTextSummary) -> Self {
+        summary.len
+    }
+
+    fn add_text_dim(&mut self, other: &Self::TextDimension) {
+        self.0 += *other;
+    }
+
+    fn add_mb_text_summary(&mut self, summary: &MBTextSummary) {
+        *self += summary.len;
+    }
+}
+impl MultiBufferDimension for MultiBufferOffsetUtf16 {
+    type TextDimension = OffsetUtf16;
+    fn from_summary(summary: &MBTextSummary) -> Self {
+        MultiBufferOffsetUtf16(summary.len_utf16)
+    }
+
+    fn add_text_dim(&mut self, other: &Self::TextDimension) {
+        self.0 += *other;
+    }
+
+    fn add_mb_text_summary(&mut self, summary: &MBTextSummary) {
+        self.0 += summary.len_utf16;
+    }
+}
+
+#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq, Hash, serde::Deserialize)]
+pub struct BufferOffset(pub usize);
+
+impl TextDimension for BufferOffset {
+    fn from_text_summary(summary: &TextSummary) -> Self {
+        BufferOffset(usize::from_text_summary(summary))
+    }
+    fn from_chunk(chunk: rope::ChunkSlice) -> Self {
+        BufferOffset(usize::from_chunk(chunk))
+    }
+    fn add_assign(&mut self, other: &Self) {
+        TextDimension::add_assign(&mut self.0, &other.0);
+    }
+}
+impl<'a> sum_tree::Dimension<'a, rope::ChunkSummary> for BufferOffset {
+    fn zero(cx: ()) -> Self {
+        BufferOffset(<usize as sum_tree::Dimension<'a, rope::ChunkSummary>>::zero(cx))
+    }
+
+    fn add_summary(&mut self, summary: &'a rope::ChunkSummary, cx: ()) {
+        usize::add_summary(&mut self.0, summary, cx);
+    }
+}
+
+impl Sub for BufferOffset {
+    type Output = usize;
+
+    fn sub(self, other: BufferOffset) -> Self::Output {
+        self.0 - other.0
+    }
+}
+
+impl AddAssign<DimensionPair<usize, Point>> for BufferOffset {
+    fn add_assign(&mut self, other: DimensionPair<usize, Point>) {
+        self.0 += other.key;
+    }
+}
+
+impl language::ToPoint for BufferOffset {
+    fn to_point(&self, snapshot: &text::BufferSnapshot) -> Point {
+        self.0.to_point(snapshot)
+    }
+}
+
+impl language::ToPointUtf16 for BufferOffset {
+    fn to_point_utf16(&self, snapshot: &text::BufferSnapshot) -> PointUtf16 {
+        self.0.to_point_utf16(snapshot)
+    }
+}
+
+impl language::ToOffset for BufferOffset {
+    fn to_offset(&self, snapshot: &text::BufferSnapshot) -> usize {
+        self.0.to_offset(snapshot)
+    }
+}
+
+impl language::ToOffsetUtf16 for BufferOffset {
+    fn to_offset_utf16(&self, snapshot: &text::BufferSnapshot) -> OffsetUtf16 {
+        self.0.to_offset_utf16(snapshot)
+    }
+}
+
+#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
+pub struct MultiBufferOffsetUtf16(pub OffsetUtf16);
+
+impl ops::Add<usize> for MultiBufferOffsetUtf16 {
+    type Output = MultiBufferOffsetUtf16;
+
+    fn add(self, rhs: usize) -> Self::Output {
+        MultiBufferOffsetUtf16(OffsetUtf16(self.0.0 + rhs))
+    }
+}
+
+impl AddAssign<OffsetUtf16> for MultiBufferOffsetUtf16 {
+    fn add_assign(&mut self, rhs: OffsetUtf16) {
+        self.0 += rhs;
+    }
+}
+
+impl AddAssign<usize> for MultiBufferOffsetUtf16 {
+    fn add_assign(&mut self, rhs: usize) {
+        self.0.0 += rhs;
+    }
+}
+
+impl Sub for MultiBufferOffsetUtf16 {
+    type Output = OffsetUtf16;
+
+    fn sub(self, other: MultiBufferOffsetUtf16) -> Self::Output {
+        self.0 - other.0
+    }
+}
+
+#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
+pub struct BufferOffsetUtf16(pub OffsetUtf16);
+
+impl MultiBufferOffset {
+    const ZERO: Self = Self(0);
+    pub fn saturating_sub(self, other: MultiBufferOffset) -> usize {
+        self.0.saturating_sub(other.0)
+    }
+    pub fn saturating_sub_usize(self, other: usize) -> MultiBufferOffset {
+        MultiBufferOffset(self.0.saturating_sub(other))
+    }
+}
+
+impl ops::Sub for MultiBufferOffset {
+    type Output = usize;
+
+    fn sub(self, other: MultiBufferOffset) -> Self::Output {
+        self.0 - other.0
+    }
+}
+
+impl ops::Sub<usize> for MultiBufferOffset {
+    type Output = Self;
+
+    fn sub(self, other: usize) -> Self::Output {
+        MultiBufferOffset(self.0 - other)
+    }
+}
+
+impl ops::SubAssign<usize> for MultiBufferOffset {
+    fn sub_assign(&mut self, other: usize) {
+        self.0 -= other;
+    }
+}
+
+impl ops::Add<usize> for BufferOffset {
+    type Output = Self;
+
+    fn add(self, rhs: usize) -> Self::Output {
+        BufferOffset(self.0 + rhs)
+    }
+}
+
+impl ops::AddAssign<usize> for BufferOffset {
+    fn add_assign(&mut self, other: usize) {
+        self.0 += other;
+    }
+}
+
+impl ops::Add<usize> for MultiBufferOffset {
+    type Output = Self;
+
+    fn add(self, rhs: usize) -> Self::Output {
+        MultiBufferOffset(self.0 + rhs)
+    }
+}
+
+impl ops::AddAssign<usize> for MultiBufferOffset {
+    fn add_assign(&mut self, other: usize) {
+        self.0 += other;
+    }
+}
+
+impl ops::Add<isize> for MultiBufferOffset {
+    type Output = Self;
+
+    fn add(self, rhs: isize) -> Self::Output {
+        MultiBufferOffset((self.0 as isize + rhs) as usize)
+    }
+}
+
+impl ops::Add for MultiBufferOffset {
+    type Output = Self;
+
+    fn add(self, rhs: MultiBufferOffset) -> Self::Output {
+        MultiBufferOffset(self.0 + rhs.0)
+    }
+}
+
+impl ops::AddAssign<MultiBufferOffset> for MultiBufferOffset {
+    fn add_assign(&mut self, other: MultiBufferOffset) {
+        self.0 += other.0;
+    }
+}
+
 pub trait ToOffset: 'static + fmt::Debug {
-    fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize;
-    fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16;
+    fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset;
+    fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16;
 }
 
 pub trait ToPoint: 'static + fmt::Debug {
@@ -255,7 +551,7 @@ pub struct MultiBufferSnapshot {
 #[derive(Debug, Clone)]
 enum DiffTransform {
     BufferContent {
-        summary: TextSummary,
+        summary: MBTextSummary,
         inserted_hunk_info: Option<DiffTransformHunkInfo>,
     },
     DeletedHunk {
@@ -370,10 +666,12 @@ struct Excerpt {
 #[derive(Clone)]
 pub struct MultiBufferExcerpt<'a> {
     excerpt: &'a Excerpt,
-    diff_transforms: sum_tree::Cursor<'a, 'static, DiffTransform, DiffTransforms<usize>>,
-    offset: usize,
-    excerpt_offset: ExcerptDimension<usize>,
-    buffer_offset: usize,
+    diff_transforms:
+        sum_tree::Cursor<'a, 'static, DiffTransform, DiffTransforms<MultiBufferOffset>>,
+    offset: MultiBufferOffset,
+    // todo unsure about this type
+    excerpt_offset: MultiBufferOffset,
+    buffer_offset: BufferOffset,
 }
 
 #[derive(Clone, Debug)]
@@ -408,13 +706,153 @@ pub struct ExcerptSummary {
     /// The location of the last [`Excerpt`] being summarized
     excerpt_locator: Locator,
     widest_line_number: u32,
-    text: TextSummary,
+    text: MBTextSummary,
 }
 
 #[derive(Debug, Clone)]
 pub struct DiffTransformSummary {
-    input: TextSummary,
-    output: TextSummary,
+    input: MBTextSummary,
+    output: MBTextSummary,
+}
+
+/// Summary of a string of text.
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+pub struct MBTextSummary {
+    /// Length in bytes.
+    pub len: MultiBufferOffset,
+    /// Length in UTF-8.
+    pub chars: usize,
+    /// Length in UTF-16 code units
+    pub len_utf16: OffsetUtf16,
+    /// A point representing the number of lines and the length of the last line.
+    ///
+    /// In other words, it marks the point after the last byte in the text, (if
+    /// EOF was a character, this would be its position).
+    pub lines: Point,
+    /// How many `char`s are in the first line
+    pub first_line_chars: u32,
+    /// How many `char`s are in the last line
+    pub last_line_chars: u32,
+    /// How many UTF-16 code units are in the last line
+    pub last_line_len_utf16: u32,
+    /// The row idx of the longest row
+    pub longest_row: u32,
+    /// How many `char`s are in the longest row
+    pub longest_row_chars: u32,
+}
+
+impl From<TextSummary> for MBTextSummary {
+    fn from(summary: TextSummary) -> Self {
+        MBTextSummary {
+            len: MultiBufferOffset(summary.len),
+            chars: summary.chars,
+            len_utf16: summary.len_utf16,
+            lines: summary.lines,
+            first_line_chars: summary.first_line_chars,
+            last_line_chars: summary.last_line_chars,
+            last_line_len_utf16: summary.last_line_len_utf16,
+            longest_row: summary.longest_row,
+            longest_row_chars: summary.longest_row_chars,
+        }
+    }
+}
+impl From<&str> for MBTextSummary {
+    fn from(text: &str) -> Self {
+        MBTextSummary::from(TextSummary::from(text))
+    }
+}
+
+impl MultiBufferDimension for MBTextSummary {
+    type TextDimension = TextSummary;
+
+    fn from_summary(summary: &MBTextSummary) -> Self {
+        *summary
+    }
+
+    fn add_text_dim(&mut self, summary: &Self::TextDimension) {
+        *self += *summary;
+    }
+
+    fn add_mb_text_summary(&mut self, summary: &MBTextSummary) {
+        *self += *summary;
+    }
+}
+
+impl AddAssign for MBTextSummary {
+    fn add_assign(&mut self, other: MBTextSummary) {
+        let joined_chars = self.last_line_chars + other.first_line_chars;
+        if joined_chars > self.longest_row_chars {
+            self.longest_row = self.lines.row;
+            self.longest_row_chars = joined_chars;
+        }
+        if other.longest_row_chars > self.longest_row_chars {
+            self.longest_row = self.lines.row + other.longest_row;
+            self.longest_row_chars = other.longest_row_chars;
+        }
+
+        if self.lines.row == 0 {
+            self.first_line_chars += other.first_line_chars;
+        }
+
+        if other.lines.row == 0 {
+            self.last_line_chars += other.first_line_chars;
+            self.last_line_len_utf16 += other.last_line_len_utf16;
+        } else {
+            self.last_line_chars = other.last_line_chars;
+            self.last_line_len_utf16 = other.last_line_len_utf16;
+        }
+
+        self.chars += other.chars;
+        self.len += other.len;
+        self.len_utf16 += other.len_utf16;
+        self.lines += other.lines;
+    }
+}
+
+impl AddAssign<TextSummary> for MBTextSummary {
+    fn add_assign(&mut self, other: TextSummary) {
+        *self += MBTextSummary::from(other);
+    }
+}
+
+impl MBTextSummary {
+    pub fn lines_utf16(&self) -> PointUtf16 {
+        PointUtf16 {
+            row: self.lines.row,
+            column: self.last_line_len_utf16,
+        }
+    }
+}
+
+impl<K, V> MultiBufferDimension for DimensionPair<K, V>
+where
+    K: MultiBufferDimension,
+    V: MultiBufferDimension,
+{
+    type TextDimension = DimensionPair<K::TextDimension, V::TextDimension>;
+
+    fn from_summary(summary: &MBTextSummary) -> Self {
+        Self {
+            key: K::from_summary(summary),
+            value: Some(V::from_summary(summary)),
+        }
+    }
+
+    fn add_text_dim(&mut self, summary: &Self::TextDimension) {
+        self.key.add_text_dim(&summary.key);
+        if let Some(value) = &mut self.value {
+            if let Some(other_value) = summary.value.as_ref() {
+                value.add_text_dim(other_value);
+            }
+        }
+    }
+
+    fn add_mb_text_summary(&mut self, summary: &MBTextSummary) {
+        self.key.add_mb_text_summary(summary);
+        if let Some(value) = &mut self.value {
+            value.add_mb_text_summary(summary);
+        }
+    }
 }
 
 #[derive(Clone)]
@@ -422,53 +860,54 @@ pub struct MultiBufferRows<'a> {
     point: Point,
     is_empty: bool,
     is_singleton: bool,
-    cursor: MultiBufferCursor<'a, Point>,
+    cursor: MultiBufferCursor<'a, Point, Point>,
 }
 
 pub struct MultiBufferChunks<'a> {
     excerpts: Cursor<'a, 'static, Excerpt, ExcerptOffset>,
-    diff_transforms: Cursor<'a, 'static, DiffTransform, Dimensions<usize, ExcerptOffset>>,
+    diff_transforms:
+        Cursor<'a, 'static, DiffTransform, Dimensions<MultiBufferOffset, ExcerptOffset>>,
     diffs: &'a TreeMap<BufferId, BufferDiffSnapshot>,
     diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>,
     buffer_chunk: Option<Chunk<'a>>,
-    range: Range<usize>,
+    range: Range<MultiBufferOffset>,
     excerpt_offset_range: Range<ExcerptOffset>,
     excerpt_chunks: Option<ExcerptChunks<'a>>,
     language_aware: bool,
 }
 
 pub struct ReversedMultiBufferChunks<'a> {
-    cursor: MultiBufferCursor<'a, usize>,
+    cursor: MultiBufferCursor<'a, MultiBufferOffset, BufferOffset>,
     current_chunks: Option<rope::Chunks<'a>>,
-    start: usize,
-    offset: usize,
+    start: MultiBufferOffset,
+    offset: MultiBufferOffset,
 }
 
 pub struct MultiBufferBytes<'a> {
-    range: Range<usize>,
-    cursor: MultiBufferCursor<'a, usize>,
+    range: Range<MultiBufferOffset>,
+    cursor: MultiBufferCursor<'a, MultiBufferOffset, BufferOffset>,
     excerpt_bytes: Option<text::Bytes<'a>>,
     has_trailing_newline: bool,
     chunk: &'a [u8],
 }
 
 pub struct ReversedMultiBufferBytes<'a> {
-    range: Range<usize>,
+    range: Range<MultiBufferOffset>,
     chunks: ReversedMultiBufferChunks<'a>,
     chunk: &'a [u8],
 }
 
 #[derive(Clone)]
-struct DiffTransforms<D> {
-    output_dimension: OutputDimension<D>,
-    excerpt_dimension: ExcerptDimension<D>,
+struct DiffTransforms<MBD> {
+    output_dimension: OutputDimension<MBD>,
+    excerpt_dimension: ExcerptDimension<MBD>,
 }
 
-impl<'a, D: TextDimension> Dimension<'a, DiffTransformSummary> for DiffTransforms<D> {
+impl<'a, MBD: MultiBufferDimension> Dimension<'a, DiffTransformSummary> for DiffTransforms<MBD> {
     fn zero(cx: <DiffTransformSummary as sum_tree::Summary>::Context<'_>) -> Self {
         Self {
             output_dimension: OutputDimension::zero(cx),
-            excerpt_dimension: <ExcerptDimension<D> as Dimension<'a, DiffTransformSummary>>::zero(
+            excerpt_dimension: <ExcerptDimension<MBD> as Dimension<'a, DiffTransformSummary>>::zero(
                 cx,
             ),
         }
@@ -485,21 +924,21 @@ impl<'a, D: TextDimension> Dimension<'a, DiffTransformSummary> for DiffTransform
 }
 
 #[derive(Clone)]
-struct MultiBufferCursor<'a, D: TextDimension> {
-    excerpts: Cursor<'a, 'static, Excerpt, ExcerptDimension<D>>,
-    diff_transforms: Cursor<'a, 'static, DiffTransform, DiffTransforms<D>>,
+struct MultiBufferCursor<'a, MBD, BD> {
+    excerpts: Cursor<'a, 'static, Excerpt, ExcerptDimension<MBD>>,
+    diff_transforms: Cursor<'a, 'static, DiffTransform, DiffTransforms<MBD>>,
     diffs: &'a TreeMap<BufferId, BufferDiffSnapshot>,
-    cached_region: Option<MultiBufferRegion<'a, D>>,
+    cached_region: Option<MultiBufferRegion<'a, MBD, BD>>,
 }
 
 #[derive(Clone)]
-struct MultiBufferRegion<'a, D: TextDimension> {
+struct MultiBufferRegion<'a, MBD, BD> {
     buffer: &'a BufferSnapshot,
     is_main_buffer: bool,
     diff_hunk_status: Option<DiffHunkStatus>,
     excerpt: &'a Excerpt,
-    buffer_range: Range<D>,
-    range: Range<D>,
+    buffer_range: Range<BD>,
+    range: Range<MBD>,
     has_trailing_newline: bool,
 }
 
@@ -511,7 +950,7 @@ struct ExcerptChunks<'a> {
 
 #[derive(Debug)]
 struct BufferEdit {
-    range: Range<usize>,
+    range: Range<BufferOffset>,
     new_text: Arc<str>,
     is_insertion: bool,
     original_indent_column: Option<u32>,
@@ -693,7 +1132,7 @@ impl MultiBuffer {
         self.singleton
     }
 
-    pub fn subscribe(&mut self) -> Subscription {
+    pub fn subscribe(&mut self) -> Subscription<MultiBufferOffset> {
         self.subscriptions.subscribe()
     }
 
@@ -711,7 +1150,7 @@ impl MultiBuffer {
 
     // The `is_empty` signature doesn't match what clippy expects
     #[allow(clippy::len_without_is_empty)]
-    pub fn len(&self, cx: &App) -> usize {
+    pub fn len(&self, cx: &App) -> MultiBufferOffset {
         self.read(cx).len()
     }
 
@@ -750,7 +1189,7 @@ impl MultiBuffer {
         // Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
         fn edit_internal(
             this: &mut MultiBuffer,
-            edits: Vec<(Range<usize>, Arc<str>)>,
+            edits: Vec<(Range<MultiBufferOffset>, Arc<str>)>,
             mut autoindent_mode: Option<AutoindentMode>,
             cx: &mut Context<MultiBuffer>,
         ) {
@@ -849,13 +1288,13 @@ impl MultiBuffer {
     }
 
     fn convert_edits_to_buffer_edits(
-        edits: Vec<(Range<usize>, Arc<str>)>,
+        edits: Vec<(Range<MultiBufferOffset>, Arc<str>)>,
         snapshot: &MultiBufferSnapshot,
         original_indent_columns: &[Option<u32>],
     ) -> (HashMap<BufferId, Vec<BufferEdit>>, Vec<ExcerptId>) {
         let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default();
         let mut edited_excerpt_ids = Vec::new();
-        let mut cursor = snapshot.cursor::<usize>();
+        let mut cursor = snapshot.cursor::<MultiBufferOffset, BufferOffset>();
         for (ix, (range, new_text)) in edits.into_iter().enumerate() {
             let original_indent_column = original_indent_columns.get(ix).copied().flatten();
 
@@ -994,7 +1433,7 @@ impl MultiBuffer {
 
         fn autoindent_ranges_internal(
             this: &mut MultiBuffer,
-            edits: Vec<(Range<usize>, Arc<str>)>,
+            edits: Vec<(Range<MultiBufferOffset>, Arc<str>)>,
             cx: &mut Context<MultiBuffer>,
         ) {
             let (buffer_edits, edited_excerpt_ids) =
@@ -1005,7 +1444,7 @@ impl MultiBuffer {
                 buffer_ids.push(buffer_id);
                 edits.sort_unstable_by_key(|edit| edit.range.start);
 
-                let mut ranges: Vec<Range<usize>> = Vec::new();
+                let mut ranges: Vec<Range<BufferOffset>> = Vec::new();
                 for edit in edits {
                     if let Some(last_range) = ranges.last_mut()
                         && edit.range.start <= last_range.end
@@ -1243,7 +1682,7 @@ impl MultiBuffer {
         let mut new_excerpts = cursor.slice(&prev_locator, Bias::Right);
         prev_locator = cursor.start().unwrap_or(Locator::min_ref()).clone();
 
-        let edit_start = ExcerptOffset::new(new_excerpts.summary().text.len);
+        let edit_start = ExcerptOffset::new(new_excerpts.summary().text.len.0);
         new_excerpts.update_last(
             |excerpt| {
                 excerpt.has_trailing_newline = true;
@@ -1287,7 +1726,7 @@ impl MultiBuffer {
             new_excerpt_ids.push(ExcerptIdMapping { id, locator }, ());
         }
 
-        let edit_end = ExcerptOffset::new(new_excerpts.summary().text.len);
+        let edit_end = ExcerptOffset::new(new_excerpts.summary().text.len.0);
 
         let suffix = cursor.suffix();
         let changed_trailing_excerpt = suffix.is_empty();
@@ -1345,7 +1784,7 @@ impl MultiBuffer {
             show_headers: _,
         } = self.snapshot.get_mut();
         let start = ExcerptOffset::new(0);
-        let prev_len = ExcerptOffset::new(excerpts.summary().text.len);
+        let prev_len = ExcerptOffset::new(excerpts.summary().text.len.0);
         *excerpts = Default::default();
         *trailing_excerpt_update_count += 1;
         *is_dirty = false;
@@ -1416,7 +1855,7 @@ impl MultiBuffer {
             if let Some(excerpt) = excerpts.item()
                 && excerpt.locator == *locator
             {
-                let excerpt_start = excerpts.start().1.clone();
+                let excerpt_start = excerpts.start().1;
                 let excerpt_end = ExcerptDimension(excerpt_start.0 + excerpt.text_summary.lines);
 
                 diff_transforms.seek_forward(&excerpt_start, Bias::Left);
@@ -1459,7 +1898,7 @@ impl MultiBuffer {
         let snapshot = self.read(cx);
         let offset = position.to_offset(&snapshot);
 
-        let mut cursor = snapshot.cursor::<usize>();
+        let mut cursor = snapshot.cursor::<MultiBufferOffset, BufferOffset>();
         cursor.seek(&offset);
         cursor
             .excerpt()
@@ -1487,7 +1926,7 @@ impl MultiBuffer {
         &self,
         point: T,
         cx: &App,
-    ) -> Option<(Entity<Buffer>, usize)> {
+    ) -> Option<(Entity<Buffer>, BufferOffset)> {
         let snapshot = self.read(cx);
         let (buffer, offset) = snapshot.point_to_buffer_offset(point)?;
         Some((
@@ -1631,7 +2070,7 @@ impl MultiBuffer {
 
                 // Push an edit for the removal of this run of excerpts.
                 let old_end = cursor.start().1;
-                let new_start = ExcerptOffset::new(new_excerpts.summary().text.len);
+                let new_start = ExcerptOffset::new(new_excerpts.summary().text.len.0);
                 edits.push(Edit {
                     old: old_start..old_end,
                     new: new_start..new_start,
@@ -1855,7 +2294,7 @@ impl MultiBuffer {
                 let buffer = buffer.read(cx);
                 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
             })
-            .unwrap_or_else(move || self.language_settings_at(0, cx))
+            .unwrap_or_else(move || self.language_settings_at(MultiBufferOffset::default(), cx))
     }
 
     pub fn language_settings_at<'a, T: ToOffset>(
@@ -2007,7 +2446,7 @@ impl MultiBuffer {
 
     pub fn single_hunk_is_expanded(&self, range: Range<Anchor>, cx: &App) -> bool {
         let snapshot = self.read(cx);
-        let mut cursor = snapshot.diff_transforms.cursor::<usize>(());
+        let mut cursor = snapshot.diff_transforms.cursor::<MultiBufferOffset>(());
         let offset_range = range.to_offset(&snapshot);
         cursor.seek(&offset_range.start, Bias::Left);
         while let Some(item) = cursor.item() {
@@ -2024,13 +2463,13 @@ impl MultiBuffer {
 
     pub fn has_expanded_diff_hunks_in_ranges(&self, ranges: &[Range<Anchor>], cx: &App) -> bool {
         let snapshot = self.read(cx);
-        let mut cursor = snapshot.diff_transforms.cursor::<usize>(());
+        let mut cursor = snapshot.diff_transforms.cursor::<MultiBufferOffset>(());
         for range in ranges {
             let range = range.to_point(&snapshot);
             let start = snapshot.point_to_offset(Point::new(range.start.row, 0));
             let end = snapshot.point_to_offset(Point::new(range.end.row + 1, 0));
-            let start = start.saturating_sub(1);
-            let end = snapshot.len().min(end + 1);
+            let start = start.saturating_sub_usize(1);
+            let end = snapshot.len().min(end + 1usize);
             cursor.seek(&start, Bias::Right);
             while let Some(item) = cursor.item() {
                 if *cursor.start() >= end {
@@ -2149,7 +2588,7 @@ impl MultiBuffer {
             .buffer
             .text_summary_for_range(excerpt.range.context.clone());
 
-        let new_start_offset = ExcerptOffset::new(new_excerpts.summary().text.len);
+        let new_start_offset = ExcerptOffset::new(new_excerpts.summary().text.len.0);
         let old_start_offset = cursor.start().1;
         let new_text_len = ExcerptOffset::new(excerpt.text_summary.len);
         let edit = Edit {
@@ -2255,7 +2694,7 @@ impl MultiBuffer {
                 .buffer
                 .text_summary_for_range(excerpt.range.context.clone());
 
-            let new_start_offset = ExcerptOffset::new(new_excerpts.summary().text.len);
+            let new_start_offset = ExcerptOffset::new(new_excerpts.summary().text.len.0);
             let old_start_offset = cursor.start().1;
             let new_text_len = ExcerptOffset::new(excerpt.text_summary.len);
             let edit = Edit {
@@ -2328,7 +2767,7 @@ impl MultiBuffer {
         buffers: &HashMap<BufferId, BufferState>,
         diffs: &HashMap<BufferId, DiffState>,
         cx: &App,
-    ) -> Vec<Edit<usize>> {
+    ) -> Vec<Edit<MultiBufferOffset>> {
         let MultiBufferSnapshot {
             excerpts,
             diffs: buffer_diff,
@@ -2417,7 +2856,7 @@ impl MultiBuffer {
                         .map(|edit| {
                             let excerpt_old_start = cursor.start().1;
                             let excerpt_new_start =
-                                ExcerptOffset::new(new_excerpts.summary().text.len);
+                                ExcerptOffset::new(new_excerpts.summary().text.len.0);
                             let old_start = excerpt_old_start + ExcerptOffset::new(edit.old.start);
                             let old_end = excerpt_old_start + ExcerptOffset::new(edit.old.end);
                             let new_start = excerpt_new_start + ExcerptOffset::new(edit.new.start);
@@ -2456,7 +2895,7 @@ impl MultiBuffer {
         snapshot: &mut MultiBufferSnapshot,
         excerpt_edits: Vec<text::Edit<ExcerptOffset>>,
         change_kind: DiffChangeKind,
-    ) -> Vec<Edit<usize>> {
+    ) -> Vec<Edit<MultiBufferOffset>> {
         if excerpt_edits.is_empty() {
             return vec![];
         }
@@ -2464,7 +2903,7 @@ impl MultiBuffer {
         let mut excerpts = snapshot.excerpts.cursor::<ExcerptOffset>(());
         let mut old_diff_transforms = snapshot
             .diff_transforms
-            .cursor::<Dimensions<ExcerptOffset, usize>>(());
+            .cursor::<Dimensions<ExcerptOffset, MultiBufferOffset>>(());
         let mut new_diff_transforms = SumTree::default();
         let mut old_expanded_hunks = HashSet::default();
         let mut output_edits = Vec::new();
@@ -2496,7 +2935,8 @@ impl MultiBuffer {
             // Compute the start of the edit in output coordinates.
             let edit_start_overshoot = (edit.old.start - old_diff_transforms.start().0).value;
             let edit_old_start = old_diff_transforms.start().1 + edit_start_overshoot;
-            let edit_new_start = (edit_old_start as isize + output_delta) as usize;
+            let edit_new_start =
+                MultiBufferOffset((edit_old_start.0 as isize + output_delta) as usize);
 
             let changed_diff_hunks = Self::recompute_diff_transforms_for_edit(
                 &edit,
@@ -2588,7 +3028,10 @@ impl MultiBuffer {
     fn recompute_diff_transforms_for_edit(
         edit: &Edit<TypedOffset<Excerpt>>,
         excerpts: &mut Cursor<Excerpt, TypedOffset<Excerpt>>,
-        old_diff_transforms: &mut Cursor<DiffTransform, Dimensions<TypedOffset<Excerpt>, usize>>,
+        old_diff_transforms: &mut Cursor<
+            DiffTransform,
+            Dimensions<TypedOffset<Excerpt>, MultiBufferOffset>,
+        >,
         new_diff_transforms: &mut SumTree<DiffTransform>,
         end_of_current_insert: &mut Option<(TypedOffset<Excerpt>, DiffTransformHunkInfo)>,
         old_expanded_hunks: &mut HashSet<DiffTransformHunkInfo>,
@@ -2807,7 +3250,7 @@ impl MultiBuffer {
                 continue;
             }
             let summary_to_add = old_snapshot
-                .text_summary_for_excerpt_offset_range::<TextSummary>(start_offset..end_offset);
+                .text_summary_for_excerpt_offset_range::<MBTextSummary>(start_offset..end_offset);
 
             if !Self::extend_last_buffer_content_transform(
                 new_transforms,
@@ -2828,7 +3271,7 @@ impl MultiBuffer {
     fn extend_last_buffer_content_transform(
         new_transforms: &mut SumTree<DiffTransform>,
         new_inserted_hunk_info: Option<DiffTransformHunkInfo>,
-        summary_to_add: TextSummary,
+        summary_to_add: MBTextSummary,
     ) -> bool {
         let mut did_extend = false;
         new_transforms.update_last(
@@ -2914,14 +3357,14 @@ impl MultiBuffer {
         use util::RandomCharIter;
 
         let snapshot = self.read(cx);
-        let mut edits: Vec<(Range<usize>, Arc<str>)> = Vec::new();
+        let mut edits: Vec<(Range<MultiBufferOffset>, Arc<str>)> = Vec::new();
         let mut last_end = None;
         for _ in 0..edit_count {
             if last_end.is_some_and(|last_end| last_end >= snapshot.len()) {
                 break;
             }
 
-            let new_start = last_end.map_or(0, |last_end| last_end + 1);
+            let new_start = last_end.map_or(MultiBufferOffset::ZERO, |last_end| last_end + 1usize);
             let end =
                 snapshot.clip_offset(rng.random_range(new_start..=snapshot.len()), Bias::Right);
             let start = snapshot.clip_offset(rng.random_range(new_start..=end), Bias::Right);
@@ -3078,18 +3521,21 @@ impl EventEmitter<Event> for MultiBuffer {}
 
 impl MultiBufferSnapshot {
     pub fn text(&self) -> String {
-        self.chunks(0..self.len(), false)
+        self.chunks(MultiBufferOffset::ZERO..self.len(), false)
             .map(|chunk| chunk.text)
             .collect()
     }
 
     pub fn reversed_chars_at<T: ToOffset>(&self, position: T) -> impl Iterator<Item = char> + '_ {
-        self.reversed_chunks_in_range(0..position.to_offset(self))
+        self.reversed_chunks_in_range(MultiBufferOffset::ZERO..position.to_offset(self))
             .flat_map(|c| c.chars().rev())
     }
 
-    fn reversed_chunks_in_range(&self, range: Range<usize>) -> ReversedMultiBufferChunks<'_> {
-        let mut cursor = self.cursor::<usize>();
+    fn reversed_chunks_in_range(
+        &self,
+        range: Range<MultiBufferOffset>,
+    ) -> ReversedMultiBufferChunks<'_> {
+        let mut cursor = self.cursor::<MultiBufferOffset, BufferOffset>();
         cursor.seek(&range.end);
         let current_chunks = cursor.region().as_ref().map(|region| {
             let start_overshoot = range.start.saturating_sub(region.range.start);
@@ -3173,7 +3619,8 @@ impl MultiBufferSnapshot {
                 buffer_id: excerpt.buffer_id,
                 excerpt_id: excerpt.id,
                 buffer_range: hunk.buffer_range.clone(),
-                diff_base_byte_range: hunk.diff_base_byte_range.clone(),
+                diff_base_byte_range: BufferOffset(hunk.diff_base_byte_range.start)
+                    ..BufferOffset(hunk.diff_base_byte_range.end),
                 secondary_status: hunk.secondary_status,
             })
         })
@@ -3184,7 +3631,7 @@ impl MultiBufferSnapshot {
         range: Range<T>,
     ) -> impl Iterator<Item = ExcerptId> + '_ {
         let range = range.start.to_offset(self)..range.end.to_offset(self);
-        let mut cursor = self.cursor::<usize>();
+        let mut cursor = self.cursor::<MultiBufferOffset, BufferOffset>();
         cursor.seek(&range.start);
         std::iter::from_fn(move || {
             let region = cursor.region()?;
@@ -3201,7 +3648,7 @@ impl MultiBufferSnapshot {
         range: Range<T>,
     ) -> impl Iterator<Item = BufferId> + '_ {
         let range = range.start.to_offset(self)..range.end.to_offset(self);
-        let mut cursor = self.cursor::<usize>();
+        let mut cursor = self.cursor::<MultiBufferOffset, BufferOffset>();
         cursor.seek(&range.start);
         std::iter::from_fn(move || {
             let region = cursor.region()?;
@@ -3218,21 +3665,21 @@ impl MultiBufferSnapshot {
     pub fn ranges_to_buffer_ranges<T: ToOffset>(
         &self,
         ranges: impl Iterator<Item = Range<T>>,
-    ) -> impl Iterator<Item = (&BufferSnapshot, Range<usize>, ExcerptId)> {
+    ) -> impl Iterator<Item = (&BufferSnapshot, Range<BufferOffset>, ExcerptId)> {
         ranges.flat_map(|range| self.range_to_buffer_ranges(range).into_iter())
     }
 
     pub fn range_to_buffer_ranges<T: ToOffset>(
         &self,
         range: Range<T>,
-    ) -> Vec<(&BufferSnapshot, Range<usize>, ExcerptId)> {
+    ) -> Vec<(&BufferSnapshot, Range<BufferOffset>, ExcerptId)> {
         let start = range.start.to_offset(self);
         let end = range.end.to_offset(self);
 
-        let mut cursor = self.cursor::<usize>();
+        let mut cursor = self.cursor::<MultiBufferOffset, BufferOffset>();
         cursor.seek(&start);
 
-        let mut result: Vec<(&BufferSnapshot, Range<usize>, ExcerptId)> = Vec::new();
+        let mut result: Vec<(&BufferSnapshot, Range<BufferOffset>, ExcerptId)> = Vec::new();
         while let Some(region) = cursor.region() {
             if region.range.start > end {
                 break;

crates/multi_buffer/src/multi_buffer_tests.rs 🔗

@@ -130,8 +130,8 @@ fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
         assert_eq!(
             subscription.consume().into_inner(),
             [Edit {
-                old: 0..0,
-                new: 0..10
+                old: MultiBufferOffset(0)..MultiBufferOffset(0),
+                new: MultiBufferOffset(0)..MultiBufferOffset(10)
             }]
         );
 
@@ -148,8 +148,8 @@ fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
         assert_eq!(
             subscription.consume().into_inner(),
             [Edit {
-                old: 10..10,
-                new: 10..22
+                old: MultiBufferOffset(10)..MultiBufferOffset(10),
+                new: MultiBufferOffset(10)..MultiBufferOffset(22)
             }]
         );
 
@@ -282,8 +282,8 @@ fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
     assert_eq!(
         subscription.consume().into_inner(),
         [Edit {
-            old: 6..8,
-            new: 6..7
+            old: MultiBufferOffset(6)..MultiBufferOffset(8),
+            new: MultiBufferOffset(6)..MultiBufferOffset(7)
         }]
     );
 
@@ -925,7 +925,7 @@ fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
         .next()
         .unwrap();
 
-    assert_eq!(hunk.diff_base_byte_range.start, 0);
+    assert_eq!(hunk.diff_base_byte_range.start, BufferOffset(0));
 
     let buf2 = cx.new(|cx| Buffer::local("X", cx));
     multibuffer.update(cx, |multibuffer, cx| {
@@ -971,10 +971,30 @@ fn test_singleton_multibuffer_anchors(cx: &mut App) {
     assert_eq!(old_snapshot.text(), "abcd");
     assert_eq!(new_snapshot.text(), "XabcdY");
 
-    assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
-    assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
-    assert_eq!(old_snapshot.anchor_before(4).to_offset(&new_snapshot), 5);
-    assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6);
+    assert_eq!(
+        old_snapshot
+            .anchor_before(MultiBufferOffset(0))
+            .to_offset(&new_snapshot),
+        MultiBufferOffset(0)
+    );
+    assert_eq!(
+        old_snapshot
+            .anchor_after(MultiBufferOffset(0))
+            .to_offset(&new_snapshot),
+        MultiBufferOffset(1)
+    );
+    assert_eq!(
+        old_snapshot
+            .anchor_before(MultiBufferOffset(4))
+            .to_offset(&new_snapshot),
+        MultiBufferOffset(5)
+    );
+    assert_eq!(
+        old_snapshot
+            .anchor_after(MultiBufferOffset(4))
+            .to_offset(&new_snapshot),
+        MultiBufferOffset(6)
+    );
 }
 
 #[gpui::test]
@@ -989,12 +1009,28 @@ fn test_multibuffer_anchors(cx: &mut App) {
     });
     let old_snapshot = multibuffer.read(cx).snapshot(cx);
 
-    assert_eq!(old_snapshot.anchor_before(0).to_offset(&old_snapshot), 0);
-    assert_eq!(old_snapshot.anchor_after(0).to_offset(&old_snapshot), 0);
-    assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
-    assert_eq!(Anchor::min().to_offset(&old_snapshot), 0);
-    assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
-    assert_eq!(Anchor::max().to_offset(&old_snapshot), 10);
+    assert_eq!(
+        old_snapshot
+            .anchor_before(MultiBufferOffset(0))
+            .to_offset(&old_snapshot),
+        MultiBufferOffset(0)
+    );
+    assert_eq!(
+        old_snapshot
+            .anchor_after(MultiBufferOffset(0))
+            .to_offset(&old_snapshot),
+        MultiBufferOffset(0)
+    );
+    assert_eq!(Anchor::min().to_offset(&old_snapshot), MultiBufferOffset(0));
+    assert_eq!(Anchor::min().to_offset(&old_snapshot), MultiBufferOffset(0));
+    assert_eq!(
+        Anchor::max().to_offset(&old_snapshot),
+        MultiBufferOffset(10)
+    );
+    assert_eq!(
+        Anchor::max().to_offset(&old_snapshot),
+        MultiBufferOffset(10)
+    );
 
     buffer_1.update(cx, |buffer, cx| {
         buffer.edit([(0..0, "W")], None, cx);
@@ -1009,16 +1045,66 @@ fn test_multibuffer_anchors(cx: &mut App) {
     assert_eq!(old_snapshot.text(), "abcd\nefghi");
     assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ");
 
-    assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0);
-    assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1);
-    assert_eq!(old_snapshot.anchor_before(1).to_offset(&new_snapshot), 2);
-    assert_eq!(old_snapshot.anchor_after(1).to_offset(&new_snapshot), 2);
-    assert_eq!(old_snapshot.anchor_before(2).to_offset(&new_snapshot), 3);
-    assert_eq!(old_snapshot.anchor_after(2).to_offset(&new_snapshot), 3);
-    assert_eq!(old_snapshot.anchor_before(5).to_offset(&new_snapshot), 7);
-    assert_eq!(old_snapshot.anchor_after(5).to_offset(&new_snapshot), 8);
-    assert_eq!(old_snapshot.anchor_before(10).to_offset(&new_snapshot), 13);
-    assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14);
+    assert_eq!(
+        old_snapshot
+            .anchor_before(MultiBufferOffset(0))
+            .to_offset(&new_snapshot),
+        MultiBufferOffset(0)
+    );
+    assert_eq!(
+        old_snapshot
+            .anchor_after(MultiBufferOffset(0))
+            .to_offset(&new_snapshot),
+        MultiBufferOffset(1)
+    );
+    assert_eq!(
+        old_snapshot
+            .anchor_before(MultiBufferOffset(1))
+            .to_offset(&new_snapshot),
+        MultiBufferOffset(2)
+    );
+    assert_eq!(
+        old_snapshot
+            .anchor_after(MultiBufferOffset(1))
+            .to_offset(&new_snapshot),
+        MultiBufferOffset(2)
+    );
+    assert_eq!(
+        old_snapshot
+            .anchor_before(MultiBufferOffset(2))
+            .to_offset(&new_snapshot),
+        MultiBufferOffset(3)
+    );
+    assert_eq!(
+        old_snapshot
+            .anchor_after(MultiBufferOffset(2))
+            .to_offset(&new_snapshot),
+        MultiBufferOffset(3)
+    );
+    assert_eq!(
+        old_snapshot
+            .anchor_before(MultiBufferOffset(5))
+            .to_offset(&new_snapshot),
+        MultiBufferOffset(7)
+    );
+    assert_eq!(
+        old_snapshot
+            .anchor_after(MultiBufferOffset(5))
+            .to_offset(&new_snapshot),
+        MultiBufferOffset(8)
+    );
+    assert_eq!(
+        old_snapshot
+            .anchor_before(MultiBufferOffset(10))
+            .to_offset(&new_snapshot),
+        MultiBufferOffset(13)
+    );
+    assert_eq!(
+        old_snapshot
+            .anchor_after(MultiBufferOffset(10))
+            .to_offset(&new_snapshot),
+        MultiBufferOffset(14)
+    );
 }
 
 #[gpui::test]
@@ -1066,26 +1152,30 @@ fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut App) {
     // The current excerpts are from a different buffer, so we don't attempt to
     // resolve the old text anchor in the new buffer.
     assert_eq!(
-        snapshot_2.summary_for_anchor::<usize>(&snapshot_1.anchor_before(2)),
-        0
+        snapshot_2.summary_for_anchor::<MultiBufferOffset>(
+            &snapshot_1.anchor_before(MultiBufferOffset(2))
+        ),
+        MultiBufferOffset(0)
     );
     assert_eq!(
-        snapshot_2.summaries_for_anchors::<usize, _>(&[
-            snapshot_1.anchor_before(2),
-            snapshot_1.anchor_after(3)
+        snapshot_2.summaries_for_anchors::<MultiBufferOffset, _>(&[
+            snapshot_1.anchor_before(MultiBufferOffset(2)),
+            snapshot_1.anchor_after(MultiBufferOffset(3))
         ]),
-        vec![0, 0]
+        vec![MultiBufferOffset(0), MultiBufferOffset(0)]
     );
 
     // Refresh anchors from the old snapshot. The return value indicates that both
     // anchors lost their original excerpt.
-    let refresh =
-        snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]);
+    let refresh = snapshot_2.refresh_anchors(&[
+        snapshot_1.anchor_before(MultiBufferOffset(2)),
+        snapshot_1.anchor_after(MultiBufferOffset(3)),
+    ]);
     assert_eq!(
         refresh,
         &[
-            (0, snapshot_2.anchor_before(0), false),
-            (1, snapshot_2.anchor_after(0), false),
+            (0, snapshot_2.anchor_before(MultiBufferOffset(0)), false),
+            (1, snapshot_2.anchor_after(MultiBufferOffset(0)), false),
         ]
     );
 
@@ -1112,14 +1202,19 @@ fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut App) {
     // The third anchor can't be resolved, since its excerpt has been removed,
     // so it resolves to the same position as its predecessor.
     let anchors = [
-        snapshot_2.anchor_before(0),
-        snapshot_2.anchor_after(2),
-        snapshot_2.anchor_after(6),
-        snapshot_2.anchor_after(14),
+        snapshot_2.anchor_before(MultiBufferOffset(0)),
+        snapshot_2.anchor_after(MultiBufferOffset(2)),
+        snapshot_2.anchor_after(MultiBufferOffset(6)),
+        snapshot_2.anchor_after(MultiBufferOffset(14)),
     ];
     assert_eq!(
-        snapshot_3.summaries_for_anchors::<usize, _>(&anchors),
-        &[0, 2, 9, 13]
+        snapshot_3.summaries_for_anchors::<MultiBufferOffset, _>(&anchors),
+        &[
+            MultiBufferOffset(0),
+            MultiBufferOffset(2),
+            MultiBufferOffset(9),
+            MultiBufferOffset(13)
+        ]
     );
 
     let new_anchors = snapshot_3.refresh_anchors(&anchors);
@@ -1128,8 +1223,13 @@ fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut App) {
         &[(0, true), (1, true), (2, true), (3, true)]
     );
     assert_eq!(
-        snapshot_3.summaries_for_anchors::<usize, _>(new_anchors.iter().map(|a| &a.1)),
-        &[0, 2, 7, 13]
+        snapshot_3.summaries_for_anchors::<MultiBufferOffset, _>(new_anchors.iter().map(|a| &a.1)),
+        &[
+            MultiBufferOffset(0),
+            MultiBufferOffset(2),
+            MultiBufferOffset(7),
+            MultiBufferOffset(13)
+        ]
     );
 }
 
@@ -1371,7 +1471,7 @@ fn test_basic_diff_hunks(cx: &mut TestAppContext) {
 
     assert_eq!(
         snapshot
-            .diff_hunks_in_range(0..snapshot.len())
+            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.len())
             .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
             .collect::<Vec<_>>(),
         &[0..4, 5..7]
@@ -2072,7 +2172,7 @@ fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
 
     assert_eq!(
         snapshot
-            .diff_hunks_in_range(0..snapshot.len())
+            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.len())
             .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
             .collect::<Vec<_>>(),
         &[0..1, 2..4, 5..7, 9..10, 12..13, 14..17]
@@ -2636,14 +2736,16 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
             30..=39 if !reference.excerpts.is_empty() => {
                 let multibuffer =
                     multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
-                let offset =
-                    multibuffer.clip_offset(rng.random_range(0..=multibuffer.len()), Bias::Left);
+                let offset = multibuffer.clip_offset(
+                    MultiBufferOffset(rng.random_range(0..=multibuffer.len().0)),
+                    Bias::Left,
+                );
                 let bias = if rng.random() {
                     Bias::Left
                 } else {
                     Bias::Right
                 };
-                log::info!("Creating anchor at {} with bias {:?}", offset, bias);
+                log::info!("Creating anchor at {} with bias {:?}", offset.0, bias);
                 anchors.push(multibuffer.anchor_at(offset, bias));
                 anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
             }
@@ -2796,7 +2898,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
         let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
         let actual_text = snapshot.text();
         let actual_boundary_rows = snapshot
-            .excerpt_boundaries_in_range(0..)
+            .excerpt_boundaries_in_range(MultiBufferOffset(0)..)
             .map(|b| b.row)
             .collect::<HashSet<_>>();
         let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
@@ -2874,9 +2976,14 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
                 })
                 .collect::<HashMap<_, _>>()
         });
-        for i in 0..snapshot.len() {
-            let excerpt = snapshot.excerpt_containing(i..i).unwrap();
-            assert_eq!(excerpt.buffer_range(), reference_ranges[&excerpt.id()]);
+        for i in 0..snapshot.len().0 {
+            let excerpt = snapshot
+                .excerpt_containing(MultiBufferOffset(i)..MultiBufferOffset(i))
+                .unwrap();
+            assert_eq!(
+                excerpt.buffer_range().start.0..excerpt.buffer_range().end.0,
+                reference_ranges[&excerpt.id()]
+            );
         }
 
         assert_consistent_line_numbers(&snapshot);
@@ -2897,7 +3004,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
             let start_ix = text_rope.clip_offset(rng.random_range(0..=end_ix), Bias::Left);
 
             let text_for_range = snapshot
-                .text_for_range(start_ix..end_ix)
+                .text_for_range(MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix))
                 .collect::<String>();
             assert_eq!(
                 text_for_range,
@@ -2906,9 +3013,12 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
                 start_ix..end_ix
             );
 
-            let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
+            let expected_summary =
+                MBTextSummary::from(TextSummary::from(&expected_text[start_ix..end_ix]));
             assert_eq!(
-                snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
+                snapshot.text_summary_for_range::<MBTextSummary, _>(
+                    MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix)
+                ),
                 expected_summary,
                 "incorrect summary for range {:?}",
                 start_ix..end_ix
@@ -2916,12 +3026,12 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
         }
 
         // Anchor resolution
-        let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
+        let summaries = snapshot.summaries_for_anchors::<MultiBufferOffset, _>(&anchors);
         assert_eq!(anchors.len(), summaries.len());
         for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
             assert!(resolved_offset <= snapshot.len());
             assert_eq!(
-                snapshot.summary_for_anchor::<usize>(anchor),
+                snapshot.summary_for_anchor::<MultiBufferOffset>(anchor),
                 resolved_offset,
                 "anchor: {:?}",
                 anchor
@@ -2931,7 +3041,9 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
         for _ in 0..10 {
             let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
             assert_eq!(
-                snapshot.reversed_chars_at(end_ix).collect::<String>(),
+                snapshot
+                    .reversed_chars_at(MultiBufferOffset(end_ix))
+                    .collect::<String>(),
                 expected_text[..end_ix].chars().rev().collect::<String>(),
             );
         }
@@ -2941,7 +3053,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
             let start_ix = rng.random_range(0..=end_ix);
             assert_eq!(
                 snapshot
-                    .bytes_in_range(start_ix..end_ix)
+                    .bytes_in_range(MultiBufferOffset(start_ix)..MultiBufferOffset(end_ix))
                     .flatten()
                     .copied()
                     .collect::<Vec<_>>(),
@@ -2964,8 +3076,13 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
 
         let mut text = old_snapshot.text();
         for edit in edits {
-            let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
-            text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
+            let new_text: String = snapshot
+                .text_for_range(edit.new.start..edit.new.end)
+                .collect();
+            text.replace_range(
+                edit.new.start.0..edit.new.start.0 + (edit.old.end.0 - edit.old.start.0),
+                &new_text,
+            );
         }
         assert_eq!(text.to_string(), snapshot.text());
     }
@@ -3038,7 +3155,11 @@ fn test_history(cx: &mut App) {
         // Edit buffer 1 through the multibuffer
         now += 2 * group_interval;
         multibuffer.start_transaction_at(now, cx);
-        multibuffer.edit([(2..2, "C")], None, cx);
+        multibuffer.edit(
+            [(MultiBufferOffset(2)..MultiBufferOffset(2), "C")],
+            None,
+            cx,
+        );
         multibuffer.end_transaction_at(now, cx);
         assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678");
 
@@ -3091,7 +3212,11 @@ fn test_history(cx: &mut App) {
         // Redo stack gets cleared after an edit.
         now += 2 * group_interval;
         multibuffer.start_transaction_at(now, cx);
-        multibuffer.edit([(0..0, "X")], None, cx);
+        multibuffer.edit(
+            [(MultiBufferOffset(0)..MultiBufferOffset(0), "X")],
+            None,
+            cx,
+        );
         multibuffer.end_transaction_at(now, cx);
         assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
         multibuffer.redo(cx);
@@ -3320,7 +3445,7 @@ fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
     );
 
     assert_eq!(snapshot.max_point(), Point::new(2, 0));
-    assert_eq!(snapshot.len(), 8);
+    assert_eq!(snapshot.len().0, 8);
 
     assert_eq!(
         snapshot
@@ -3330,7 +3455,7 @@ fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
     );
 
     let (_, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
-    assert_eq!(translated_offset, "one\n".len());
+    assert_eq!(translated_offset.0, "one\n".len());
     let (_, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
     assert_eq!(translated_point, Point::new(1, 0));
 
@@ -3371,7 +3496,7 @@ fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
     let buffer_1_id = buffer_1.read_with(cx, |buffer_1, _| buffer_1.remote_id());
     let (buffer, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap();
     assert_eq!(buffer.remote_id(), buffer_1_id);
-    assert_eq!(translated_offset, "one\n".len());
+    assert_eq!(translated_offset.0, "one\n".len());
     let (buffer, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap();
     assert_eq!(buffer.remote_id(), buffer_1_id);
     assert_eq!(translated_point, Point::new(1, 0));
@@ -3439,7 +3564,7 @@ fn assert_excerpts_match(
 fn assert_new_snapshot(
     multibuffer: &Entity<MultiBuffer>,
     snapshot: &mut MultiBufferSnapshot,
-    subscription: &mut Subscription,
+    subscription: &mut Subscription<MultiBufferOffset>,
     cx: &mut TestAppContext,
     expected_diff: &str,
 ) {
@@ -3462,15 +3587,15 @@ fn assert_new_snapshot(
 fn check_edits(
     old_snapshot: &MultiBufferSnapshot,
     new_snapshot: &MultiBufferSnapshot,
-    edits: &[Edit<usize>],
+    edits: &[Edit<MultiBufferOffset>],
 ) {
     let mut text = old_snapshot.text();
     let new_text = new_snapshot.text();
     for edit in edits.iter().rev() {
-        if !text.is_char_boundary(edit.old.start)
-            || !text.is_char_boundary(edit.old.end)
-            || !new_text.is_char_boundary(edit.new.start)
-            || !new_text.is_char_boundary(edit.new.end)
+        if !text.is_char_boundary(edit.old.start.0)
+            || !text.is_char_boundary(edit.old.end.0)
+            || !new_text.is_char_boundary(edit.new.start.0)
+            || !new_text.is_char_boundary(edit.new.end.0)
         {
             panic!(
                 "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}",
@@ -3479,8 +3604,8 @@ fn check_edits(
         }
 
         text.replace_range(
-            edit.old.start..edit.old.end,
-            &new_text[edit.new.start..edit.new.end],
+            edit.old.start.0..edit.old.end.0,
+            &new_text[edit.new.start.0..edit.new.end.0],
         );
     }
 
@@ -3491,8 +3616,8 @@ fn check_edits(
 fn assert_chunks_in_ranges(snapshot: &MultiBufferSnapshot) {
     let full_text = snapshot.text();
     for ix in 0..full_text.len() {
-        let mut chunks = snapshot.chunks(0..snapshot.len(), false);
-        chunks.seek(ix..snapshot.len());
+        let mut chunks = snapshot.chunks(MultiBufferOffset(0)..snapshot.len(), false);
+        chunks.seek(MultiBufferOffset(ix)..snapshot.len());
         let tail = chunks.map(|chunk| chunk.text).collect::<String>();
         assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..);
     }
@@ -3522,44 +3647,49 @@ fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
     let mut offsets = Vec::new();
     let mut points = Vec::new();
     for offset in 0..=text.len() + 1 {
+        let offset = MultiBufferOffset(offset);
         let clipped_left = snapshot.clip_offset(offset, Bias::Left);
         let clipped_right = snapshot.clip_offset(offset, Bias::Right);
         assert_eq!(
-            clipped_left,
-            text.clip_offset(offset, Bias::Left),
+            clipped_left.0,
+            text.clip_offset(offset.0, Bias::Left),
             "clip_offset({offset:?}, Left)"
         );
         assert_eq!(
-            clipped_right,
-            text.clip_offset(offset, Bias::Right),
+            clipped_right.0,
+            text.clip_offset(offset.0, Bias::Right),
             "clip_offset({offset:?}, Right)"
         );
         assert_eq!(
             snapshot.offset_to_point(clipped_left),
-            text.offset_to_point(clipped_left),
-            "offset_to_point({clipped_left})"
+            text.offset_to_point(clipped_left.0),
+            "offset_to_point({})",
+            clipped_left.0
         );
         assert_eq!(
             snapshot.offset_to_point(clipped_right),
-            text.offset_to_point(clipped_right),
-            "offset_to_point({clipped_right})"
+            text.offset_to_point(clipped_right.0),
+            "offset_to_point({})",
+            clipped_right.0
         );
         let anchor_after = snapshot.anchor_after(clipped_left);
         assert_eq!(
             anchor_after.to_offset(snapshot),
             clipped_left,
-            "anchor_after({clipped_left}).to_offset {anchor_after:?}"
+            "anchor_after({}).to_offset {anchor_after:?}",
+            clipped_left.0
         );
         let anchor_before = snapshot.anchor_before(clipped_left);
         assert_eq!(
             anchor_before.to_offset(snapshot),
             clipped_left,
-            "anchor_before({clipped_left}).to_offset"
+            "anchor_before({}).to_offset",
+            clipped_left.0
         );
         left_anchors.push(anchor_before);
         right_anchors.push(anchor_after);
         offsets.push(clipped_left);
-        points.push(text.offset_to_point(clipped_left));
+        points.push(text.offset_to_point(clipped_left.0));
     }
 
     for row in 0..text.max_point().row {
@@ -3578,12 +3708,12 @@ fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
                 "clip_point({point:?}, Right)"
             );
             assert_eq!(
-                snapshot.point_to_offset(clipped_left),
+                snapshot.point_to_offset(clipped_left).0,
                 text.point_to_offset(clipped_left),
                 "point_to_offset({clipped_left:?})"
             );
             assert_eq!(
-                snapshot.point_to_offset(clipped_right),
+                snapshot.point_to_offset(clipped_right).0,
                 text.point_to_offset(clipped_right),
                 "point_to_offset({clipped_right:?})"
             );
@@ -3591,7 +3721,7 @@ fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
     }
 
     assert_eq!(
-        snapshot.summaries_for_anchors::<usize, _>(&left_anchors),
+        snapshot.summaries_for_anchors::<MultiBufferOffset, _>(&left_anchors),
         offsets,
         "left_anchors <-> offsets"
     );
@@ -3601,7 +3731,7 @@ fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
         "left_anchors <-> points"
     );
     assert_eq!(
-        snapshot.summaries_for_anchors::<usize, _>(&right_anchors),
+        snapshot.summaries_for_anchors::<MultiBufferOffset, _>(&right_anchors),
         offsets,
         "right_anchors <-> offsets"
     );
@@ -3613,7 +3743,7 @@ fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
 
     for (anchors, bias) in [(&left_anchors, Bias::Left), (&right_anchors, Bias::Right)] {
         for (ix, (offset, anchor)) in offsets.iter().zip(anchors).enumerate() {
-            if ix > 0 && *offset == 252 && offset > &offsets[ix - 1] {
+            if ix > 0 && *offset == MultiBufferOffset(252) && offset > &offsets[ix - 1] {
                 let prev_anchor = left_anchors[ix - 1];
                 assert!(
                     anchor.cmp(&prev_anchor, snapshot).is_gt(),
@@ -3632,7 +3762,7 @@ fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
     }
 
     if let Some((buffer, offset)) = snapshot.point_to_buffer_offset(snapshot.max_point()) {
-        assert!(offset <= buffer.len());
+        assert!(offset.0 <= buffer.len());
     }
     if let Some((buffer, point, _)) = snapshot.point_to_buffer_point(snapshot.max_point()) {
         assert!(point <= buffer.max_point());
@@ -3747,7 +3877,7 @@ fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) {
 
     let snapshot = multibuffer.read(cx).snapshot(cx);
 
-    let chunks = snapshot.chunks(0..snapshot.len(), false);
+    let chunks = snapshot.chunks(MultiBufferOffset(0)..snapshot.len(), false);
 
     for chunk in chunks {
         let chunk_text = chunk.text;
@@ -3879,24 +4009,24 @@ fn test_random_chunk_bitmaps_with_diffs(cx: &mut App, mut rng: StdRng) {
 
             let mut ranges = Vec::new();
             for _ in 0..rng.random_range(1..5) {
-                if snapshot.len() == 0 {
+                if snapshot.len().0 == 0 {
                     break;
                 }
 
                 let diff_size = rng.random_range(5..1000);
-                let mut start = rng.random_range(0..snapshot.len());
+                let mut start = rng.random_range(0..snapshot.len().0);
 
                 while !text.is_char_boundary(start) {
                     start = start.saturating_sub(1);
                 }
 
-                let mut end = rng.random_range(start..snapshot.len().min(start + diff_size));
+                let mut end = rng.random_range(start..snapshot.len().0.min(start + diff_size));
 
                 while !text.is_char_boundary(end) {
                     end = end.saturating_add(1);
                 }
-                let start_anchor = snapshot.anchor_after(start);
-                let end_anchor = snapshot.anchor_before(end);
+                let start_anchor = snapshot.anchor_after(MultiBufferOffset(start));
+                let end_anchor = snapshot.anchor_before(MultiBufferOffset(end));
                 ranges.push(start_anchor..end_anchor);
             }
             multibuffer.expand_diff_hunks(ranges, cx);
@@ -3905,7 +4035,7 @@ fn test_random_chunk_bitmaps_with_diffs(cx: &mut App, mut rng: StdRng) {
 
     let snapshot = multibuffer.read(cx).snapshot(cx);
 
-    let chunks = snapshot.chunks(0..snapshot.len(), false);
+    let chunks = snapshot.chunks(MultiBufferOffset(0)..snapshot.len(), false);
 
     for chunk in chunks {
         let chunk_text = chunk.text;

crates/multi_buffer/src/transaction.rs 🔗

@@ -1,14 +1,14 @@
 use gpui::{App, Context, Entity};
-use language::{self, Buffer, TextDimension, TransactionId};

+use language::{self, Buffer, TransactionId};

 use std::{
     collections::HashMap,
-    ops::{Range, Sub},

+    ops::{AddAssign, Range, Sub},

     time::{Duration, Instant},
 };
 use sum_tree::Bias;
 use text::BufferId;
 
-use crate::BufferState;

+use crate::{BufferState, MultiBufferDimension};

 
 use super::{Event, ExcerptSummary, MultiBuffer};
 
@@ -320,7 +320,11 @@ impl MultiBuffer {
         cx: &App,
     ) -> Vec<Range<D>>
     where
-        D: TextDimension + Ord + Sub<D, Output = D>,

+        D: MultiBufferDimension

+            + Ord

+            + Sub<D, Output = D::TextDimension>

+            + AddAssign<D::TextDimension>,

+        D::TextDimension: PartialOrd + Sub<D::TextDimension, Output = D::TextDimension>,

     {
         let Some(transaction) = self.history.transaction(transaction_id) else {
             return Vec::new();
@@ -336,24 +340,34 @@ impl MultiBuffer {
             };
 
             let buffer = buffer_state.buffer.read(cx);
-            for range in buffer.edited_ranges_for_transaction_id::<D>(*buffer_transaction) {

+            for range in

+                buffer.edited_ranges_for_transaction_id::<D::TextDimension>(*buffer_transaction)

+            {

                 for excerpt_id in &buffer_state.excerpts {
                     cursor.seek(excerpt_id, Bias::Left);
                     if let Some(excerpt) = cursor.item()
                         && excerpt.locator == *excerpt_id
                     {
-                        let excerpt_buffer_start = excerpt.range.context.start.summary::<D>(buffer);

-                        let excerpt_buffer_end = excerpt.range.context.end.summary::<D>(buffer);

+                        let excerpt_buffer_start = excerpt

+                            .range

+                            .context

+                            .start

+                            .summary::<D::TextDimension>(buffer);

+                        let excerpt_buffer_end = excerpt

+                            .range

+                            .context

+                            .end

+                            .summary::<D::TextDimension>(buffer);

                         let excerpt_range = excerpt_buffer_start..excerpt_buffer_end;
                         if excerpt_range.contains(&range.start)
                             && excerpt_range.contains(&range.end)
                         {
-                            let excerpt_start = D::from_text_summary(&cursor.start().text);

+                            let excerpt_start = D::from_summary(&cursor.start().text);

 
                             let mut start = excerpt_start;
-                            start.add_assign(&(range.start - excerpt_buffer_start));

+                            start += range.start - excerpt_buffer_start;

                             let mut end = excerpt_start;
-                            end.add_assign(&(range.end - excerpt_buffer_start));

+                            end += range.end - excerpt_buffer_start;

 
                             ranges.push(start..end);
                             break;

crates/outline/src/outline.rs 🔗

@@ -6,7 +6,7 @@ use std::{
 
 use editor::scroll::ScrollOffset;
 use editor::{Anchor, AnchorRangeExt, Editor, scroll::Autoscroll};
-use editor::{RowHighlightOptions, SelectionEffects};
+use editor::{MultiBufferOffset, RowHighlightOptions, SelectionEffects};
 use fuzzy::StringMatch;
 use gpui::{
     App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, HighlightStyle,
@@ -247,7 +247,7 @@ impl PickerDelegate for OutlineViewDelegate {
                 let buffer = editor.buffer().read(cx).snapshot(cx);
                 let cursor_offset = editor
                     .selections
-                    .newest::<usize>(&editor.display_snapshot(cx))
+                    .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
                     .head();
                 (buffer, cursor_offset)
             });
@@ -259,8 +259,8 @@ impl PickerDelegate for OutlineViewDelegate {
                 .map(|(ix, item)| {
                     let range = item.range.to_offset(&buffer);
                     let distance_to_closest_endpoint = cmp::min(
-                        (range.start as isize - cursor_offset as isize).abs(),
-                        (range.end as isize - cursor_offset as isize).abs(),
+                        (range.start.0 as isize - cursor_offset.0 as isize).abs(),
+                        (range.end.0 as isize - cursor_offset.0 as isize).abs(),
                     );
                     let depth = if range.contains(&cursor_offset) {
                         Some(item.depth)

crates/project_panel/src/project_panel.rs 🔗

@@ -7,7 +7,7 @@ use collections::{BTreeSet, HashMap, hash_map};
 use command_palette_hooks::CommandPaletteFilter;
 use db::kvp::KEY_VALUE_STORE;
 use editor::{
-    Editor, EditorEvent,
+    Editor, EditorEvent, MultiBufferOffset,
     items::{
         entry_diagnostic_aware_icon_decoration_and_color,
         entry_diagnostic_aware_icon_name_and_color, entry_git_aware_label_color,
@@ -1925,7 +1925,9 @@ impl ProjectPanel {
                 self.filename_editor.update(cx, |editor, cx| {
                     editor.set_text(file_name, window, cx);
                     editor.change_selections(Default::default(), window, cx, |s| {
-                        s.select_ranges([selection])
+                        s.select_ranges([
+                            MultiBufferOffset(selection.start)..MultiBufferOffset(selection.end)
+                        ])
                     });
                 });
                 self.update_visible_entries(None, true, true, window, cx);

crates/project_panel/src/project_panel_tests.rs 🔗

@@ -1,5 +1,6 @@
 use super::*;
 use collections::HashSet;
+use editor::MultiBufferOffset;
 use gpui::{Empty, Entity, TestAppContext, VisualTestContext, WindowHandle};
 use pretty_assertions::assert_eq;
 use project::FakeFs;
@@ -658,7 +659,9 @@ async fn test_editing_files(cx: &mut gpui::TestAppContext) {
 
     let confirm = panel.update_in(cx, |panel, window, cx| {
         panel.filename_editor.update(cx, |editor, cx| {
-            let file_name_selections = editor.selections.all::<usize>(&editor.display_snapshot(cx));
+            let file_name_selections = editor
+                .selections
+                .all::<MultiBufferOffset>(&editor.display_snapshot(cx));
             assert_eq!(
                 file_name_selections.len(),
                 1,
@@ -666,12 +669,13 @@ async fn test_editing_files(cx: &mut gpui::TestAppContext) {
             );
             let file_name_selection = &file_name_selections[0];
             assert_eq!(
-                file_name_selection.start, 0,
+                file_name_selection.start,
+                MultiBufferOffset(0),
                 "Should select the file name from the start"
             );
             assert_eq!(
                 file_name_selection.end,
-                "another-filename".len(),
+                MultiBufferOffset("another-filename".len()),
                 "Should not select file extension"
             );
 
@@ -732,11 +736,11 @@ async fn test_editing_files(cx: &mut gpui::TestAppContext) {
 
     panel.update_in(cx, |panel, window, cx| {
             panel.filename_editor.update(cx, |editor, cx| {
-                let file_name_selections = editor.selections.all::<usize>(&editor.display_snapshot(cx));
+                let file_name_selections = editor.selections.all::<MultiBufferOffset>(&editor.display_snapshot(cx));
                 assert_eq!(file_name_selections.len(), 1, "File editing should have a single selection, but got: {file_name_selections:?}");
                 let file_name_selection = &file_name_selections[0];
-                assert_eq!(file_name_selection.start, 0, "Should select the file name from the start");
-                assert_eq!(file_name_selection.end, "a-different-filename.tar".len(), "Should not select file extension, but still may select anything up to the last dot..");
+                assert_eq!(file_name_selection.start, MultiBufferOffset(0), "Should select the file name from the start");
+                assert_eq!(file_name_selection.end, MultiBufferOffset("a-different-filename.tar".len()), "Should not select file extension, but still may select anything up to the last dot..");
 
             });
             panel.cancel(&menu::Cancel, window, cx)
@@ -1218,7 +1222,9 @@ async fn test_copy_paste(cx: &mut gpui::TestAppContext) {
 
     panel.update_in(cx, |panel, window, cx| {
         panel.filename_editor.update(cx, |editor, cx| {
-            let file_name_selections = editor.selections.all::<usize>(&editor.display_snapshot(cx));
+            let file_name_selections = editor
+                .selections
+                .all::<MultiBufferOffset>(&editor.display_snapshot(cx));
             assert_eq!(
                 file_name_selections.len(),
                 1,
@@ -1227,12 +1233,12 @@ async fn test_copy_paste(cx: &mut gpui::TestAppContext) {
             let file_name_selection = &file_name_selections[0];
             assert_eq!(
                 file_name_selection.start,
-                "one".len(),
+                MultiBufferOffset("one".len()),
                 "Should select the file name disambiguation after the original file name"
             );
             assert_eq!(
                 file_name_selection.end,
-                "one copy".len(),
+                MultiBufferOffset("one copy".len()),
                 "Should select the file name disambiguation until the extension"
             );
         });

crates/repl/src/repl_editor.rs 🔗

@@ -4,7 +4,7 @@ use std::ops::Range;
 use std::sync::Arc;
 
 use anyhow::{Context as _, Result};
-use editor::Editor;
+use editor::{Editor, MultiBufferOffset};
 use gpui::{App, Entity, WeakEntity, Window, prelude::*};
 use language::{BufferSnapshot, Language, LanguageName, Point};
 use project::{ProjectItem as _, WorktreeId};
@@ -478,7 +478,9 @@ fn get_language(editor: WeakEntity<Editor>, cx: &mut App) -> Option<Arc<Language
     editor
         .update(cx, |editor, cx| {
             let display_snapshot = editor.display_snapshot(cx);
-            let selection = editor.selections.newest::<usize>(&display_snapshot);
+            let selection = editor
+                .selections
+                .newest::<MultiBufferOffset>(&display_snapshot);
             display_snapshot
                 .buffer_snapshot()
                 .language_at(selection.head())

crates/rope/src/offset_utf16.rs 🔗

@@ -32,7 +32,6 @@ impl Sub for OffsetUtf16 {
     type Output = OffsetUtf16;
 
     fn sub(self, other: Self) -> Self::Output {
-        debug_assert!(other <= self);
         Self(self.0 - other.0)
     }
 }

crates/rope/src/rope.rs 🔗

@@ -1534,39 +1534,63 @@ where
     }
 }
 
-impl<K, V> ops::Sub for DimensionPair<K, V>
+impl<R, R2, K, V> ops::Sub for DimensionPair<K, V>
 where
-    K: ops::Sub<K, Output = K>,
-    V: ops::Sub<V, Output = V>,
+    K: ops::Sub<K, Output = R>,
+    V: ops::Sub<V, Output = R2>,
 {
-    type Output = Self;
+    type Output = DimensionPair<R, R2>;
 
     fn sub(self, rhs: Self) -> Self::Output {
-        Self {
+        DimensionPair {
             key: self.key - rhs.key,
             value: self.value.zip(rhs.value).map(|(a, b)| a - b),
         }
     }
 }
 
+impl<R, R2, K, V> ops::AddAssign<DimensionPair<R, R2>> for DimensionPair<K, V>
+where
+    K: ops::AddAssign<R>,
+    V: ops::AddAssign<R2>,
+{
+    fn add_assign(&mut self, rhs: DimensionPair<R, R2>) {
+        self.key += rhs.key;
+        if let Some(value) = &mut self.value {
+            if let Some(other_value) = rhs.value {
+                *value += other_value;
+            } else {
+                self.value.take();
+            }
+        }
+    }
+}
+
+impl<D> std::ops::AddAssign<DimensionPair<Point, D>> for Point {
+    fn add_assign(&mut self, rhs: DimensionPair<Point, D>) {
+        *self += rhs.key;
+    }
+}
+
 impl<K, V> cmp::Eq for DimensionPair<K, V> where K: cmp::Eq {}
 
-impl<'a, K, V> sum_tree::Dimension<'a, ChunkSummary> for DimensionPair<K, V>
+impl<'a, K, V, S> sum_tree::Dimension<'a, S> for DimensionPair<K, V>
 where
-    K: sum_tree::Dimension<'a, ChunkSummary>,
-    V: sum_tree::Dimension<'a, ChunkSummary>,
+    S: sum_tree::Summary,
+    K: sum_tree::Dimension<'a, S>,
+    V: sum_tree::Dimension<'a, S>,
 {
-    fn zero(_cx: ()) -> Self {
+    fn zero(cx: S::Context<'_>) -> Self {
         Self {
-            key: K::zero(_cx),
-            value: Some(V::zero(_cx)),
+            key: K::zero(cx),
+            value: Some(V::zero(cx)),
         }
     }
 
-    fn add_summary(&mut self, summary: &'a ChunkSummary, _cx: ()) {
-        self.key.add_summary(summary, _cx);
+    fn add_summary(&mut self, summary: &'a S, cx: S::Context<'_>) {
+        self.key.add_summary(summary, cx);
         if let Some(value) = &mut self.value {
-            value.add_summary(summary, _cx);
+            value.add_summary(summary, cx);
         }
     }
 }

crates/search/src/buffer_search.rs 🔗

@@ -10,7 +10,7 @@ use any_vec::AnyVec;
 use anyhow::Context as _;
 use collections::HashMap;
 use editor::{
-    DisplayPoint, Editor, EditorSettings,
+    DisplayPoint, Editor, EditorSettings, MultiBufferOffset,
     actions::{Backtab, Tab},
 };
 use futures::channel::oneshot;
@@ -868,7 +868,11 @@ impl BufferSearchBar {
                     .buffer()
                     .update(cx, |replacement_buffer, cx| {
                         let len = replacement_buffer.len(cx);
-                        replacement_buffer.edit([(0..len, replacement.unwrap())], None, cx);
+                        replacement_buffer.edit(
+                            [(MultiBufferOffset(0)..len, replacement.unwrap())],
+                            None,
+                            cx,
+                        );
                     });
             });
     }
@@ -892,7 +896,7 @@ impl BufferSearchBar {
             self.query_editor.update(cx, |query_editor, cx| {
                 query_editor.buffer().update(cx, |query_buffer, cx| {
                     let len = query_buffer.len(cx);
-                    query_buffer.edit([(0..len, query)], None, cx);
+                    query_buffer.edit([(MultiBufferOffset(0)..len, query)], None, cx);
                 });
             });
             self.set_search_options(options, cx);

crates/tasks_ui/src/tasks_ui.rs 🔗

@@ -392,7 +392,7 @@ fn worktree_context(worktree_abs_path: &Path) -> TaskContext {
 mod tests {
     use std::{collections::HashMap, sync::Arc};
 
-    use editor::{Editor, SelectionEffects};
+    use editor::{Editor, MultiBufferOffset, SelectionEffects};
     use gpui::TestAppContext;
     use language::{Language, LanguageConfig};
     use project::{BasicContextProvider, FakeFs, Project, task_store::TaskStore};
@@ -539,7 +539,7 @@ mod tests {
         // And now, let's select an identifier.
         editor2.update_in(cx, |editor, window, cx| {
             editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
-                selections.select_ranges([14..18])
+                selections.select_ranges([MultiBufferOffset(14)..MultiBufferOffset(18)])
             })
         });
 

crates/text/src/selection.rs 🔗

@@ -130,9 +130,15 @@ impl<T: Copy> Selection<T> {
     }
 }
 
-impl Selection<usize> {
+impl<T: std::ops::Sub + Copy> Selection<T> {
+    pub fn len(&self) -> <T as std::ops::Sub>::Output {
+        self.end - self.start
+    }
+}
+
+impl<T: Copy + Eq> Selection<T> {
     #[cfg(feature = "test-support")]
-    pub fn from_offset(offset: usize) -> Self {
+    pub fn from_offset(offset: T) -> Self {
         Selection {
             id: 0,
             start: offset,
@@ -142,7 +148,7 @@ impl Selection<usize> {
         }
     }
 
-    pub fn equals(&self, offset_range: &Range<usize>) -> bool {
+    pub fn equals(&self, offset_range: &Range<T>) -> bool {
         self.start == offset_range.start && self.end == offset_range.end
     }
 }

crates/text/src/subscription.rs 🔗

@@ -6,36 +6,55 @@ use std::{
 };
 
 #[derive(Default)]
-pub struct Topic(Mutex<Vec<Weak<Mutex<Patch<usize>>>>>);
+pub struct Topic<T>(Mutex<Vec<Weak<Mutex<Patch<T>>>>>);
 
-pub struct Subscription(Arc<Mutex<Patch<usize>>>);
+pub struct Subscription<T>(Arc<Mutex<Patch<T>>>);
 
-impl Topic {
-    pub fn subscribe(&mut self) -> Subscription {
+impl<T: Default, TDelta> Topic<T>
+where
+    T: 'static
+        + Copy
+        + Ord
+        + std::ops::Sub<T, Output = TDelta>
+        + std::ops::Add<TDelta, Output = T>
+        + std::ops::AddAssign<TDelta>
+        + Default,
+    TDelta: Ord + Copy,
+{
+    pub fn subscribe(&mut self) -> Subscription<T> {
         let subscription = Subscription(Default::default());
         self.0.get_mut().push(Arc::downgrade(&subscription.0));
         subscription
     }
 
-    pub fn publish(&self, edits: impl Clone + IntoIterator<Item = Edit<usize>>) {
+    pub fn publish(&self, edits: impl Clone + IntoIterator<Item = Edit<T>>) {
         publish(&mut self.0.lock(), edits);
     }
 
-    pub fn publish_mut(&mut self, edits: impl Clone + IntoIterator<Item = Edit<usize>>) {
+    pub fn publish_mut(&mut self, edits: impl Clone + IntoIterator<Item = Edit<T>>) {
         publish(self.0.get_mut(), edits);
     }
 }
 
-impl Subscription {
-    pub fn consume(&self) -> Patch<usize> {
+impl<T: Default> Subscription<T> {
+    pub fn consume(&self) -> Patch<T> {
         mem::take(&mut *self.0.lock())
     }
 }
 
-fn publish(
-    subscriptions: &mut Vec<Weak<Mutex<Patch<usize>>>>,
-    edits: impl Clone + IntoIterator<Item = Edit<usize>>,
-) {
+fn publish<T, TDelta>(
+    subscriptions: &mut Vec<Weak<Mutex<Patch<T>>>>,
+    edits: impl Clone + IntoIterator<Item = Edit<T>>,
+) where
+    T: 'static
+        + Copy
+        + Ord
+        + std::ops::Sub<T, Output = TDelta>
+        + std::ops::Add<TDelta, Output = T>
+        + std::ops::AddAssign<TDelta>
+        + Default,
+    TDelta: Ord + Copy,
+{
     subscriptions.retain(|subscription| {
         if let Some(subscription) = subscription.upgrade() {
             let mut patch = subscription.lock();

crates/text/src/text.rs 🔗

@@ -54,7 +54,7 @@ pub struct Buffer {
     deferred_ops: OperationQueue<Operation>,
     deferred_replicas: HashSet<ReplicaId>,
     pub lamport_clock: clock::Lamport,
-    subscriptions: Topic,
+    subscriptions: Topic<usize>,
     edit_id_resolvers: HashMap<clock::Lamport, Vec<oneshot::Sender<()>>>,
     wait_for_version_txs: Vec<(clock::Global, oneshot::Sender<()>)>,
 }
@@ -1619,7 +1619,7 @@ impl Buffer {
         self.edited_ranges_for_edit_ids(&transaction.edit_ids)
     }
 
-    pub fn subscribe(&mut self) -> Subscription {
+    pub fn subscribe(&mut self) -> Subscription<usize> {
         self.subscriptions.subscribe()
     }
 

crates/vim/src/helix.rs 🔗

@@ -6,8 +6,8 @@ mod select;
 
 use editor::display_map::DisplaySnapshot;
 use editor::{
-    DisplayPoint, Editor, EditorSettings, HideMouseCursorOrigin, SelectionEffects, ToOffset,
-    ToPoint, movement,
+    DisplayPoint, Editor, EditorSettings, HideMouseCursorOrigin, MultiBufferOffset,
+    SelectionEffects, ToOffset, ToPoint, movement,
 };
 use gpui::actions;
 use gpui::{Context, Window};
@@ -523,7 +523,7 @@ impl Vim {
                         ..range.end.to_offset(&display_map, Bias::Left);
 
                     if !byte_range.is_empty() {
-                        let replacement_text = text.repeat(byte_range.len());
+                        let replacement_text = text.repeat(byte_range.end - byte_range.start);
                         edits.push((byte_range, replacement_text));
                     }
                 }
@@ -620,7 +620,7 @@ impl Vim {
         self.update_editor(cx, |_, editor, cx| {
             let newest = editor
                 .selections
-                .newest::<usize>(&editor.display_snapshot(cx));
+                .newest::<MultiBufferOffset>(&editor.display_snapshot(cx));
             editor.change_selections(Default::default(), window, cx, |s| s.select(vec![newest]));
         });
     }

crates/vim/src/helix/boundary.rs 🔗

@@ -1,10 +1,7 @@
-use std::{
-    cmp::Ordering,
-    ops::{Deref, DerefMut, Range},
-};
+use std::{cmp::Ordering, ops::Range};
 
 use editor::{
-    DisplayPoint,
+    DisplayPoint, MultiBufferOffset,
     display_map::{DisplaySnapshot, ToDisplayPoint},
     movement,
 };
@@ -104,8 +101,8 @@ trait BoundedObject {
             let next_end = self.next_end(map, end_search_start, outer)?;
             let maybe_next_start = self.next_start(map, start_search_start, outer);
             if let Some(next_start) = maybe_next_start
-                && (*next_start < *next_end
-                    || *next_start == *next_end && self.can_be_zero_width(outer))
+                && (next_start.0 < next_end.0
+                    || next_start.0 == next_end.0 && self.can_be_zero_width(outer))
                 && !self.ambiguous_outer()
             {
                 let closing = self.close_at_end(next_start, map, outer)?;
@@ -133,8 +130,8 @@ trait BoundedObject {
             let previous_start = self.previous_start(map, start_search_end, outer)?;
             let maybe_previous_end = self.previous_end(map, end_search_end, outer);
             if let Some(previous_end) = maybe_previous_end
-                && (*previous_end > *previous_start
-                    || *previous_end == *previous_start && self.can_be_zero_width(outer))
+                && (previous_end.0 > previous_start.0
+                    || previous_end.0 == previous_start.0 && self.can_be_zero_width(outer))
                 && !self.ambiguous_outer()
             {
                 let closing = self.close_at_start(previous_end, map, outer)?;
@@ -151,30 +148,22 @@ trait BoundedObject {
     }
 }
 
-#[derive(Clone, Copy, PartialEq, Debug)]
-struct Offset(usize);
-impl Deref for Offset {
-    type Target = usize;
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-impl DerefMut for Offset {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
-    }
-}
+#[derive(Clone, Copy, PartialEq, Debug, PartialOrd, Ord, Eq)]
+struct Offset(MultiBufferOffset);
 impl Offset {
     fn next(self, map: &DisplaySnapshot) -> Option<Self> {
-        let next = Self(map.buffer_snapshot().clip_offset(*self + 1, Bias::Right));
-        (*next > *self).then(|| next)
+        let next = Self(
+            map.buffer_snapshot()
+                .clip_offset(self.0 + 1usize, Bias::Right),
+        );
+        (next.0 > self.0).then(|| next)
     }
     fn previous(self, map: &DisplaySnapshot) -> Option<Self> {
-        if *self == 0 {
+        if self.0 == MultiBufferOffset(0) {
             return None;
         }
         Some(Self(
-            map.buffer_snapshot().clip_offset(*self - 1, Bias::Left),
+            map.buffer_snapshot().clip_offset(self.0 - 1, Bias::Left),
         ))
     }
     fn range(
@@ -211,7 +200,7 @@ impl<B: BoundedObject> HelixTextObject for B {
             let max_end = self.close_at_end(search_start, map, find_outer)?;
             let min_start = self.close_at_start(max_end, map, find_outer)?;
 
-            (*min_start <= *relative_to.start).then(|| min_start..max_end)
+            (min_start <= relative_to.start).then(|| min_start..max_end)
         })
     }
 
@@ -279,8 +268,8 @@ fn relative_range<B: BoundedObject>(
         min_start..max_end
     };
 
-    let start = wanted_range.start.clone().to_display_point(map);
-    let end = wanted_range.end.clone().to_display_point(map);
+    let start = wanted_range.start.0.to_display_point(map);
+    let end = wanted_range.end.0.to_display_point(map);
 
     Some(start..end)
 }
@@ -390,7 +379,7 @@ impl ImmediateBoundary {
 impl BoundedObject for ImmediateBoundary {
     fn next_start(&self, map: &DisplaySnapshot, from: Offset, outer: bool) -> Option<Offset> {
         try_find_boundary(map, from, |left, right| {
-            let classifier = map.buffer_snapshot().char_classifier_at(*from);
+            let classifier = map.buffer_snapshot().char_classifier_at(from.0);
             if outer {
                 self.is_outer_start(left, right, classifier)
             } else {
@@ -400,7 +389,7 @@ impl BoundedObject for ImmediateBoundary {
     }
     fn next_end(&self, map: &DisplaySnapshot, from: Offset, outer: bool) -> Option<Offset> {
         try_find_boundary(map, from, |left, right| {
-            let classifier = map.buffer_snapshot().char_classifier_at(*from);
+            let classifier = map.buffer_snapshot().char_classifier_at(from.0);
             if outer {
                 self.is_outer_end(left, right, classifier)
             } else {
@@ -410,7 +399,7 @@ impl BoundedObject for ImmediateBoundary {
     }
     fn previous_start(&self, map: &DisplaySnapshot, from: Offset, outer: bool) -> Option<Offset> {
         try_find_preceding_boundary(map, from, |left, right| {
-            let classifier = map.buffer_snapshot().char_classifier_at(*from);
+            let classifier = map.buffer_snapshot().char_classifier_at(from.0);
             if outer {
                 self.is_outer_start(left, right, classifier)
             } else {
@@ -420,7 +409,7 @@ impl BoundedObject for ImmediateBoundary {
     }
     fn previous_end(&self, map: &DisplaySnapshot, from: Offset, outer: bool) -> Option<Offset> {
         try_find_preceding_boundary(map, from, |left, right| {
-            let classifier = map.buffer_snapshot().char_classifier_at(*from);
+            let classifier = map.buffer_snapshot().char_classifier_at(from.0);
             if outer {
                 self.is_outer_end(left, right, classifier)
             } else {
@@ -572,7 +561,7 @@ impl FuzzyBoundary {
         boundary_kind: Boundary,
     ) -> Option<Offset> {
         let generate_boundary_data = |left, right, point: Offset| {
-            let classifier = map.buffer_snapshot().char_classifier_at(*from);
+            let classifier = map.buffer_snapshot().char_classifier_at(from.0);
             let reach_boundary = if outer && boundary_kind == Boundary::Start {
                 self.is_near_potential_outer_start(left, right, &classifier)
             } else if !outer && boundary_kind == Boundary::Start {
@@ -598,9 +587,9 @@ impl FuzzyBoundary {
                 Ordering::Greater => !backward,
             });
         if backward {
-            boundaries.max_by_key(|boundary| **boundary)
+            boundaries.max_by_key(|boundary| *boundary)
         } else {
-            boundaries.min_by_key(|boundary| **boundary)
+            boundaries.min_by_key(|boundary| *boundary)
         }
     }
 }
@@ -662,15 +651,15 @@ fn try_find_boundary_data<T>(
 ) -> Option<T> {
     let mut prev_ch = map
         .buffer_snapshot()
-        .reversed_chars_at(*from)
+        .reversed_chars_at(from.0)
         .next()
         .unwrap_or('\0');
 
-    for ch in map.buffer_snapshot().chars_at(*from).chain(['\0']) {
+    for ch in map.buffer_snapshot().chars_at(from.0).chain(['\0']) {
         if let Some(boundary_information) = boundary_information(prev_ch, ch, from) {
             return Some(boundary_information);
         }
-        *from += ch.len_utf8();
+        from.0 += ch.len_utf8();
         prev_ch = ch;
     }
 
@@ -702,13 +691,21 @@ fn try_find_preceding_boundary_data<T>(
     mut from: Offset,
     is_boundary: impl Fn(char, char, Offset) -> Option<T>,
 ) -> Option<T> {
-    let mut prev_ch = map.buffer_snapshot().chars_at(*from).next().unwrap_or('\0');
+    let mut prev_ch = map
+        .buffer_snapshot()
+        .chars_at(from.0)
+        .next()
+        .unwrap_or('\0');
 
-    for ch in map.buffer_snapshot().reversed_chars_at(*from).chain(['\0']) {
+    for ch in map
+        .buffer_snapshot()
+        .reversed_chars_at(from.0)
+        .chain(['\0'])
+    {
         if let Some(boundary_information) = is_boundary(ch, prev_ch, from) {
             return Some(boundary_information);
         }
-        from.0 = from.0.saturating_sub(ch.len_utf8());
+        from.0.0 = from.0.0.saturating_sub(ch.len_utf8());
         prev_ch = ch;
     }
 

crates/vim/src/helix/duplicate.rs 🔗

@@ -1,6 +1,6 @@
 use std::ops::Range;
 
-use editor::{DisplayPoint, display_map::DisplaySnapshot};
+use editor::{DisplayPoint, MultiBufferOffset, display_map::DisplaySnapshot};
 use gpui::Context;
 use text::Bias;
 use ui::Window;
@@ -111,7 +111,7 @@ fn find_next_valid_duplicate_space(
 fn display_point_range_to_offset_range(
     range: &Range<DisplayPoint>,
     map: &DisplaySnapshot,
-) -> Range<usize> {
+) -> Range<MultiBufferOffset> {
     range.start.to_offset(map, Bias::Left)..range.end.to_offset(map, Bias::Right)
 }
 

crates/vim/src/helix/paste.rs 🔗

@@ -125,7 +125,7 @@ impl Vim {
                     s.select_ranges(new_selections.into_iter().map(|(anchor, len)| {
                         let offset = anchor.to_offset(&snapshot);
                         if action.before {
-                            offset.saturating_sub(len)..offset
+                            offset.saturating_sub_usize(len)..offset
                         } else {
                             offset..(offset + len)
                         }

crates/vim/src/motion.rs 🔗

@@ -1,5 +1,5 @@
 use editor::{
-    Anchor, Bias, DisplayPoint, Editor, RowExt, ToOffset, ToPoint,
+    Anchor, Bias, BufferOffset, DisplayPoint, Editor, MultiBufferOffset, RowExt, ToOffset, ToPoint,
     display_map::{DisplayRow, DisplaySnapshot, FoldPoint, ToDisplayPoint},
     movement::{
         self, FindRange, TextLayoutDetails, find_boundary, find_preceding_boundary_display_point,
@@ -2143,7 +2143,7 @@ pub(crate) fn sentence_backwards(
             if start_of_next_sentence < start {
                 times = times.saturating_sub(1);
             }
-            if times == 0 || offset == 0 {
+            if times == 0 || offset.0 == 0 {
                 return map.clip_point(
                     start_of_next_sentence
                         .to_offset(&map.buffer_snapshot())
@@ -2207,7 +2207,7 @@ pub(crate) fn sentence_forwards(
     map.max_point()
 }
 
-fn next_non_blank(map: &DisplaySnapshot, start: usize) -> usize {
+fn next_non_blank(map: &DisplaySnapshot, start: MultiBufferOffset) -> MultiBufferOffset {
     for (c, o) in map.buffer_chars_at(start) {
         if c == '\n' || !c.is_whitespace() {
             return o;
@@ -2219,7 +2219,10 @@ fn next_non_blank(map: &DisplaySnapshot, start: usize) -> usize {
 
 // given the offset after a ., !, or ? find the start of the next sentence.
 // if this is not a sentence boundary, returns None.
-fn start_of_next_sentence(map: &DisplaySnapshot, end_of_sentence: usize) -> Option<usize> {
+fn start_of_next_sentence(
+    map: &DisplaySnapshot,
+    end_of_sentence: MultiBufferOffset,
+) -> Option<MultiBufferOffset> {
     let chars = map.buffer_chars_at(end_of_sentence);
     let mut seen_space = false;
 
@@ -2253,10 +2256,10 @@ fn go_to_line(map: &DisplaySnapshot, display_point: DisplayPoint, line: usize) -
             .clip_point(Point::new((line - 1) as u32, point.column), Bias::Left),
     );
     let buffer_range = excerpt.buffer_range();
-    if offset >= buffer_range.start && offset <= buffer_range.end {
+    if offset >= buffer_range.start.0 && offset <= buffer_range.end.0 {
         let point = map
             .buffer_snapshot()
-            .offset_to_point(excerpt.map_offset_from_buffer(offset));
+            .offset_to_point(excerpt.map_offset_from_buffer(BufferOffset(offset)));
         return map.clip_point(map.point_to_display_point(point, Bias::Left), Bias::Left);
     }
     let mut last_position = None;
@@ -2360,6 +2363,9 @@ fn matching_tag(map: &DisplaySnapshot, head: DisplayPoint) -> Option<DisplayPoin
 }
 
 fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
+    if !map.is_singleton() {
+        return display_point;
+    }
     // https://github.com/vim/vim/blob/1d87e11a1ef201b26ed87585fba70182ad0c468a/runtime/doc/motion.txt#L1200
     let display_point = map.clip_at_line_end(display_point);
     let point = display_point.to_point(map);
@@ -2375,9 +2381,10 @@ fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint
     // Attempt to find the smallest enclosing bracket range that also contains
     // the offset, which only happens if the cursor is currently in a bracket.
     let range_filter = |_buffer: &language::BufferSnapshot,
-                        opening_range: Range<usize>,
-                        closing_range: Range<usize>| {
-        opening_range.contains(&offset) || closing_range.contains(&offset)
+                        opening_range: Range<BufferOffset>,
+                        closing_range: Range<BufferOffset>| {
+        opening_range.contains(&BufferOffset(offset.0))
+            || closing_range.contains(&BufferOffset(offset.0))
     };
 
     let bracket_ranges = snapshot
@@ -2840,7 +2847,7 @@ fn method_motion(
 
     for _ in 0..times {
         let point = map.display_point_to_point(display_point, Bias::Left);
-        let offset = point.to_offset(&map.buffer_snapshot());
+        let offset = point.to_offset(&map.buffer_snapshot()).0;
         let range = if direction == Direction::Prev {
             0..offset
         } else {
@@ -2869,7 +2876,7 @@ fn method_motion(
         } else {
             possibilities.min().unwrap_or(offset)
         };
-        let new_point = map.clip_point(dest.to_display_point(map), Bias::Left);
+        let new_point = map.clip_point(MultiBufferOffset(dest).to_display_point(map), Bias::Left);
         if new_point == display_point {
             break;
         }
@@ -2890,7 +2897,7 @@ fn comment_motion(
 
     for _ in 0..times {
         let point = map.display_point_to_point(display_point, Bias::Left);
-        let offset = point.to_offset(&map.buffer_snapshot());
+        let offset = point.to_offset(&map.buffer_snapshot()).0;
         let range = if direction == Direction::Prev {
             0..offset
         } else {
@@ -2923,7 +2930,7 @@ fn comment_motion(
         } else {
             possibilities.min().unwrap_or(offset)
         };
-        let new_point = map.clip_point(dest.to_display_point(map), Bias::Left);
+        let new_point = map.clip_point(MultiBufferOffset(dest).to_display_point(map), Bias::Left);
         if new_point == display_point {
             break;
         }
@@ -2946,7 +2953,7 @@ fn section_motion(
                 .display_point_to_point(display_point, Bias::Left)
                 .to_offset(&map.buffer_snapshot());
             let range = if direction == Direction::Prev {
-                0..offset
+                MultiBufferOffset(0)..offset
             } else {
                 offset..map.buffer_snapshot().len()
             };
@@ -2977,7 +2984,7 @@ fn section_motion(
                 let relevant = if is_start { range.start } else { range.end };
                 if direction == Direction::Prev && relevant < offset {
                     Some(relevant)
-                } else if direction == Direction::Next && relevant > offset + 1 {
+                } else if direction == Direction::Next && relevant > offset + 1usize {
                     Some(relevant)
                 } else {
                     None
@@ -2985,7 +2992,7 @@ fn section_motion(
             });
 
             let offset = if direction == Direction::Prev {
-                possibilities.max().unwrap_or(0)
+                possibilities.max().unwrap_or(MultiBufferOffset(0))
             } else {
                 possibilities.min().unwrap_or(map.buffer_snapshot().len())
             };

crates/vim/src/normal/increment.rs 🔗

@@ -211,9 +211,14 @@ fn find_target(
     let mut pre_char = String::new();
 
     // Backward scan to find the start of the number, but stop at start_offset
-    for ch in snapshot.reversed_chars_at(offset + if offset < snapshot.len() { 1 } else { 0 }) {
+    let next_offset = if offset < snapshot.len() {
+        offset + 1usize
+    } else {
+        offset
+    };
+    for ch in snapshot.reversed_chars_at(next_offset) {
         // Search boundaries
-        if offset == 0 || ch.is_whitespace() || (need_range && offset <= start_offset) {
+        if offset.0 == 0 || ch.is_whitespace() || (need_range && offset <= start_offset) {
             break;
         }
 

crates/vim/src/normal/paste.rs 🔗

@@ -1,4 +1,7 @@
-use editor::{DisplayPoint, RowExt, SelectionEffects, display_map::ToDisplayPoint, movement};
+use editor::{
+    DisplayPoint, MultiBufferOffset, RowExt, SelectionEffects, display_map::ToDisplayPoint,
+    movement,
+};
 use gpui::{Action, Context, Window};
 use language::{Bias, SelectionGoal};
 use schemars::JsonSchema;
@@ -174,7 +177,10 @@ impl Vim {
                     original_indent_columns.push(original_indent_column);
                 }
 
-                let cursor_offset = editor.selections.last::<usize>(&display_map).head();
+                let cursor_offset = editor
+                    .selections
+                    .last::<MultiBufferOffset>(&display_map)
+                    .head();
                 if editor
                     .buffer()
                     .read(cx)

crates/vim/src/object.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
     state::{Mode, Operator},
 };
 use editor::{
-    Bias, DisplayPoint, Editor, ToOffset,
+    Bias, BufferOffset, DisplayPoint, Editor, MultiBufferOffset, ToOffset,
     display_map::{DisplaySnapshot, ToDisplayPoint},
     movement::{self, FindRange},
 };
@@ -81,8 +81,8 @@ pub struct CandidateRange {
 #[derive(Debug, Clone)]
 pub struct CandidateWithRanges {
     candidate: CandidateRange,
-    open_range: Range<usize>,
-    close_range: Range<usize>,
+    open_range: Range<MultiBufferOffset>,
+    close_range: Range<MultiBufferOffset>,
 }
 
 /// Selects text at the same indentation level.
@@ -120,7 +120,7 @@ struct CurlyBrackets {
     opening: bool,
 }
 
-fn cover_or_next<I: Iterator<Item = (Range<usize>, Range<usize>)>>(
+fn cover_or_next<I: Iterator<Item = (Range<MultiBufferOffset>, Range<MultiBufferOffset>)>>(
     candidates: Option<I>,
     caret: DisplayPoint,
     map: &DisplaySnapshot,
@@ -128,7 +128,7 @@ fn cover_or_next<I: Iterator<Item = (Range<usize>, Range<usize>)>>(
     let caret_offset = caret.to_offset(map, Bias::Left);
     let mut covering = vec![];
     let mut next_ones = vec![];
-    let snapshot = &map.buffer_snapshot();
+    let snapshot = map.buffer_snapshot();
 
     if let Some(ranges) = candidates {
         for (open_range, close_range) in ranges {
@@ -171,7 +171,7 @@ fn cover_or_next<I: Iterator<Item = (Range<usize>, Range<usize>)>>(
     if !next_ones.is_empty() {
         return next_ones.into_iter().min_by_key(|r| {
             let start = r.candidate.start.to_offset(map, Bias::Left);
-            (start as isize - caret_offset as isize).abs()
+            (start.0 as isize - caret_offset.0 as isize).abs()
         });
     }
 
@@ -181,8 +181,8 @@ fn cover_or_next<I: Iterator<Item = (Range<usize>, Range<usize>)>>(
 type DelimiterPredicate = dyn Fn(&BufferSnapshot, usize, usize) -> bool;
 
 struct DelimiterRange {
-    open: Range<usize>,
-    close: Range<usize>,
+    open: Range<MultiBufferOffset>,
+    close: Range<MultiBufferOffset>,
 }
 
 impl DelimiterRange {
@@ -221,14 +221,14 @@ fn find_mini_delimiters(
         .buffer_snapshot()
         .bracket_ranges(visible_line_range)
         .map(|ranges| {
-            ranges.filter_map(move |(open, close)| {
+            ranges.filter_map(|(open, close)| {
                 // Convert the ranges from multibuffer space to buffer space as
                 // that is what `is_valid_delimiter` expects, otherwise it might
                 // panic as the values might be out of bounds.
                 let buffer_open = excerpt.map_range_to_buffer(open.clone());
                 let buffer_close = excerpt.map_range_to_buffer(close.clone());
 
-                if is_valid_delimiter(buffer, buffer_open.start, buffer_close.start) {
+                if is_valid_delimiter(buffer, buffer_open.start.0, buffer_close.start.0) {
                     Some((open, close))
                 } else {
                     None
@@ -252,8 +252,12 @@ fn find_mini_delimiters(
 
     Some(
         DelimiterRange {
-            open: open_bracket,
-            close: close_bracket,
+            open: excerpt.map_range_from_buffer(
+                BufferOffset(open_bracket.start)..BufferOffset(open_bracket.end),
+            ),
+            close: excerpt.map_range_from_buffer(
+                BufferOffset(close_bracket.start)..BufferOffset(close_bracket.end),
+            ),
         }
         .to_display_range(map, around),
     )
@@ -899,7 +903,7 @@ pub fn surrounding_html_tag(
     // Find the most closest to current offset
     let mut cursor = buffer.syntax_layer_at(offset)?.node().walk();
     let mut last_child_node = cursor.node();
-    while cursor.goto_first_child_for_byte(offset).is_some() {
+    while cursor.goto_first_child_for_byte(offset.0).is_some() {
         last_child_node = cursor.node();
     }
 
@@ -916,10 +920,16 @@ pub fn surrounding_html_tag(
                     - range.start.to_offset(map, Bias::Left)
                     <= 1
                 {
-                    offset <= last_child.end_byte()
+                    offset.0 <= last_child.end_byte()
                 } else {
-                    range.start.to_offset(map, Bias::Left) >= first_child.start_byte()
-                        && range.end.to_offset(map, Bias::Left) <= last_child.start_byte() + 1
+                    excerpt
+                        .map_offset_to_buffer(range.start.to_offset(map, Bias::Left))
+                        .0
+                        >= first_child.start_byte()
+                        && excerpt
+                            .map_offset_to_buffer(range.end.to_offset(map, Bias::Left))
+                            .0
+                            <= last_child.start_byte() + 1
                 };
                 if open_tag.is_some() && open_tag == close_tag && is_valid {
                     let range = if around {
@@ -927,6 +937,7 @@ pub fn surrounding_html_tag(
                     } else {
                         first_child.byte_range().end..last_child.byte_range().start
                     };
+                    let range = BufferOffset(range.start)..BufferOffset(range.end);
                     if excerpt.contains_buffer_range(range.clone()) {
                         let result = excerpt.map_range_from_buffer(range);
                         return Some(
@@ -1093,7 +1104,8 @@ fn text_object(
         .collect();
     matches.sort_by_key(|r| r.end - r.start);
     if let Some(buffer_range) = matches.first() {
-        let range = excerpt.map_range_from_buffer(buffer_range.clone());
+        let buffer_range = BufferOffset(buffer_range.start)..BufferOffset(buffer_range.end);
+        let range = excerpt.map_range_from_buffer(buffer_range);
         return Some(range.start.to_display_point(map)..range.end.to_display_point(map));
     }
 
@@ -1113,10 +1125,12 @@ fn text_object(
     if let Some(buffer_range) = matches.first()
         && !buffer_range.is_empty()
     {
-        let range = excerpt.map_range_from_buffer(buffer_range.clone());
+        let buffer_range = BufferOffset(buffer_range.start)..BufferOffset(buffer_range.end);
+        let range = excerpt.map_range_from_buffer(buffer_range);
         return Some(range.start.to_display_point(map)..range.end.to_display_point(map));
     }
-    let buffer_range = excerpt.map_range_from_buffer(around_range.clone());
+    let around_range = BufferOffset(around_range.start)..BufferOffset(around_range.end);
+    let buffer_range = excerpt.map_range_from_buffer(around_range);
     return Some(buffer_range.start.to_display_point(map)..buffer_range.end.to_display_point(map));
 }
 
@@ -1134,9 +1148,9 @@ fn argument(
 
     fn comma_delimited_range_at(
         buffer: &BufferSnapshot,
-        mut offset: usize,
+        mut offset: BufferOffset,
         include_comma: bool,
-    ) -> Option<Range<usize>> {
+    ) -> Option<Range<BufferOffset>> {
         // Seek to the first non-whitespace character
         offset += buffer
             .chars_at(offset)
@@ -1151,7 +1165,7 @@ fn argument(
             }
 
             // If the cursor is outside the brackets, ignore them
-            if open.start == offset || close.end == offset {
+            if open.start == offset.0 || close.end == offset.0 {
                 return false;
             }
 
@@ -1167,7 +1181,7 @@ fn argument(
         let (open_bracket, close_bracket) =
             buffer.innermost_enclosing_bracket_ranges(offset..offset, Some(&bracket_filter))?;
 
-        let inner_bracket_range = open_bracket.end..close_bracket.start;
+        let inner_bracket_range = BufferOffset(open_bracket.end)..BufferOffset(close_bracket.start);
 
         let layer = buffer.syntax_layer_at(offset)?;
         let node = layer.node();
@@ -1186,7 +1200,7 @@ fn argument(
             parent_covers_bracket_range = covers_bracket_range;
 
             // Unable to find a child node with a parent that covers the bracket range, so no argument to select
-            cursor.goto_first_child_for_byte(offset)?;
+            cursor.goto_first_child_for_byte(offset.0)?;
         }
 
         let mut argument_node = cursor.node();
@@ -1256,7 +1270,7 @@ fn argument(
             }
         }
 
-        Some(start..end)
+        Some(BufferOffset(start)..BufferOffset(end))
     }
 
     let result = comma_delimited_range_at(buffer, excerpt.map_offset_to_buffer(offset), around)?;
@@ -1387,7 +1401,7 @@ fn is_possible_sentence_start(character: char) -> bool {
 const SENTENCE_END_PUNCTUATION: &[char] = &['.', '!', '?'];
 const SENTENCE_END_FILLERS: &[char] = &[')', ']', '"', '\''];
 const SENTENCE_END_WHITESPACE: &[char] = &[' ', '\t', '\n'];
-fn is_sentence_end(map: &DisplaySnapshot, offset: usize) -> bool {
+fn is_sentence_end(map: &DisplaySnapshot, offset: MultiBufferOffset) -> bool {
     let mut next_chars = map.buffer_chars_at(offset).peekable();
     if let Some((char, _)) = next_chars.next() {
         // We are at a double newline. This position is a sentence end.

crates/vim/src/surrounds.rs 🔗

@@ -4,7 +4,7 @@ use crate::{
     object::{Object, surrounding_markers},
     state::Mode,
 };
-use editor::{Bias, movement};
+use editor::{Bias, MultiBufferOffset, movement};
 use gpui::{Context, Window};
 use language::BracketPair;
 
@@ -175,7 +175,7 @@ impl Vim {
                         while let Some((ch, offset)) = chars_and_offset.next() {
                             if ch.to_string() == pair.start {
                                 let start = offset;
-                                let mut end = start + 1;
+                                let mut end = start + 1usize;
                                 if surround
                                     && let Some((next_ch, _)) = chars_and_offset.peek()
                                     && next_ch.eq(&' ')
@@ -193,7 +193,7 @@ impl Vim {
                         while let Some((ch, offset)) = reverse_chars_and_offsets.next() {
                             if ch.to_string() == pair.end {
                                 let mut start = offset;
-                                let end = start + 1;
+                                let end = start + 1usize;
                                 if surround
                                     && let Some((next_ch, _)) = reverse_chars_and_offsets.peek()
                                     && next_ch.eq(&' ')
@@ -282,7 +282,7 @@ impl Vim {
                             // that the end replacement string does not exceed
                             // this value. Helpful when dealing with newlines.
                             let mut edit_len = 0;
-                            let mut open_range_end = 0;
+                            let mut open_range_end = MultiBufferOffset(0);
                             let mut chars_and_offset = display_map
                                 .buffer_chars_at(range.start.to_offset(&display_map, Bias::Left))
                                 .peekable();
@@ -291,7 +291,7 @@ impl Vim {
                                 if ch.to_string() == will_replace_pair.start {
                                     let mut open_str = pair.start.clone();
                                     let start = offset;
-                                    open_range_end = start + 1;
+                                    open_range_end = start + 1usize;
                                     while let Some((next_ch, _)) = chars_and_offset.next()
                                         && next_ch == ' '
                                     {
@@ -322,7 +322,7 @@ impl Vim {
                                 if ch.to_string() == will_replace_pair.end {
                                     let mut close_str = String::new();
                                     let mut start = offset;
-                                    let end = start + 1;
+                                    let end = start + 1usize;
                                     while let Some((next_ch, _)) = reverse_chars_and_offsets.next()
                                         && next_ch == ' '
                                         && close_str.len() < edit_len - 1

crates/vim/src/test.rs 🔗

@@ -7,7 +7,7 @@ use std::{sync::Arc, time::Duration};
 use collections::HashMap;
 use command_palette::CommandPalette;
 use editor::{
-    AnchorRangeExt, DisplayPoint, Editor, EditorMode, MultiBuffer,
+    AnchorRangeExt, DisplayPoint, Editor, EditorMode, MultiBuffer, MultiBufferOffset,
     actions::{DeleteLine, WrapSelectionsInTag},
     code_context_menus::CodeContextMenu,
     display_map::DisplayRow,
@@ -908,6 +908,9 @@ fn assert_pending_input(cx: &mut VimTestContext, expected: &str) {
                 .map(|highlight| highlight.to_offset(&snapshot.buffer_snapshot()))
                 .collect::<Vec<_>>(),
             ranges
+                .iter()
+                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
+                .collect::<Vec<_>>()
         )
     });
 }
@@ -967,7 +970,7 @@ async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
                 .iter()
                 .map(|highlight| highlight.to_offset(&snapshot.buffer_snapshot()))
                 .collect::<Vec<_>>(),
-            vec![0..1]
+            vec![MultiBufferOffset(0)..MultiBufferOffset(1)]
         )
     });
     cx.executor().advance_clock(Duration::from_millis(500));

crates/vim/src/vim.rs 🔗

@@ -21,8 +21,8 @@ mod visual;
 
 use collections::HashMap;
 use editor::{
-    Anchor, Bias, Editor, EditorEvent, EditorSettings, HideMouseCursorOrigin, SelectionEffects,
-    ToPoint,
+    Anchor, Bias, Editor, EditorEvent, EditorSettings, HideMouseCursorOrigin, MultiBufferOffset,
+    SelectionEffects, ToPoint,
     actions::Paste,
     movement::{self, FindRange},
 };
@@ -1388,7 +1388,7 @@ impl Vim {
         let newest_selection_empty = editor.update(cx, |editor, cx| {
             editor
                 .selections
-                .newest::<usize>(&editor.display_snapshot(cx))
+                .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
                 .is_empty()
         });
         let editor = editor.read(cx);
@@ -1488,7 +1488,7 @@ impl Vim {
             let snapshot = &editor.snapshot(window, cx);
             let selection = editor
                 .selections
-                .newest::<usize>(&snapshot.display_snapshot);
+                .newest::<MultiBufferOffset>(&snapshot.display_snapshot);
 
             let snapshot = snapshot.buffer_snapshot();
             let (range, kind) =

crates/vim/src/visual.rs 🔗

@@ -2,7 +2,7 @@ use std::sync::Arc;
 
 use collections::HashMap;
 use editor::{
-    Bias, DisplayPoint, Editor, SelectionEffects,
+    Bias, DisplayPoint, Editor, MultiBufferOffset, SelectionEffects,
     display_map::{DisplaySnapshot, ToDisplayPoint},
     movement,
 };
@@ -778,7 +778,7 @@ impl Vim {
                     {
                         let range = row_range.start.to_offset(&display_map, Bias::Right)
                             ..row_range.end.to_offset(&display_map, Bias::Right);
-                        let text = text.repeat(range.len());
+                        let text = text.repeat(range.end - range.start);
                         edits.push((range, text));
                     }
                 }
@@ -844,8 +844,8 @@ impl Vim {
             return;
         };
         let vim_is_normal = self.mode == Mode::Normal;
-        let mut start_selection = 0usize;
-        let mut end_selection = 0usize;
+        let mut start_selection = MultiBufferOffset(0);
+        let mut end_selection = MultiBufferOffset(0);
 
         self.update_editor(cx, |_, editor, _| {
             editor.set_collapse_matches(false);
@@ -868,7 +868,7 @@ impl Vim {
         self.update_editor(cx, |_, editor, cx| {
             let latest = editor
                 .selections
-                .newest::<usize>(&editor.display_snapshot(cx));
+                .newest::<MultiBufferOffset>(&editor.display_snapshot(cx));
             start_selection = latest.start;
             end_selection = latest.end;
         });
@@ -891,7 +891,7 @@ impl Vim {
         self.update_editor(cx, |_, editor, cx| {
             let latest = editor
                 .selections
-                .newest::<usize>(&editor.display_snapshot(cx));
+                .newest::<MultiBufferOffset>(&editor.display_snapshot(cx));
             if vim_is_normal {
                 start_selection = latest.start;
                 end_selection = latest.end;

crates/zed/src/zed.rs 🔗

@@ -2241,7 +2241,9 @@ mod tests {
     use super::*;
     use assets::Assets;
     use collections::HashSet;
-    use editor::{DisplayPoint, Editor, SelectionEffects, display_map::DisplayRow};
+    use editor::{
+        DisplayPoint, Editor, MultiBufferOffset, SelectionEffects, display_map::DisplayRow,
+    };
     use gpui::{
         Action, AnyWindowHandle, App, AssetSource, BorrowAppContext, SemanticVersion,
         TestAppContext, UpdateGlobal, VisualTestContext, WindowHandle, actions,
@@ -3508,7 +3510,11 @@ mod tests {
                     assert!(!editor.is_dirty(cx));
                     assert_eq!(editor.title(cx), "untitled");
                     assert!(Arc::ptr_eq(
-                        &editor.buffer().read(cx).language_at(0, cx).unwrap(),
+                        &editor
+                            .buffer()
+                            .read(cx)
+                            .language_at(MultiBufferOffset(0), cx)
+                            .unwrap(),
                         &languages::PLAIN_TEXT
                     ));
                     editor.handle_input("hi", window, cx);
@@ -3542,7 +3548,12 @@ mod tests {
                     assert!(!editor.is_dirty(cx));
                     assert_eq!(editor.title(cx), "the-new-name.rs");
                     assert_eq!(
-                        editor.buffer().read(cx).language_at(0, cx).unwrap().name(),
+                        editor
+                            .buffer()
+                            .read(cx)
+                            .language_at(MultiBufferOffset(0), cx)
+                            .unwrap()
+                            .name(),
                         "Rust".into()
                     );
                 });
@@ -3648,7 +3659,11 @@ mod tests {
             .update(cx, |_, window, cx| {
                 editor.update(cx, |editor, cx| {
                     assert!(Arc::ptr_eq(
-                        &editor.buffer().read(cx).language_at(0, cx).unwrap(),
+                        &editor
+                            .buffer()
+                            .read(cx)
+                            .language_at(MultiBufferOffset(0), cx)
+                            .unwrap(),
                         &languages::PLAIN_TEXT
                     ));
                     editor.handle_input("hi", window, cx);
@@ -3672,7 +3687,12 @@ mod tests {
                 editor.update(cx, |editor, cx| {
                     assert!(!editor.is_dirty(cx));
                     assert_eq!(
-                        editor.buffer().read(cx).language_at(0, cx).unwrap().name(),
+                        editor
+                            .buffer()
+                            .read(cx)
+                            .language_at(MultiBufferOffset(0), cx)
+                            .unwrap()
+                            .name(),
                         "Rust".into()
                     )
                 });