Introduce DisplayRow, MultiBufferRow newtypes and BufferRow type alias (#11656)

Kirill Bulatov and Piotr created

Part of https://github.com/zed-industries/zed/issues/8081

To avoid confusion and bugs when converting between various row `u32`'s,
use different types for each.
Further PRs should split `Point` into buffer and multi buffer variants
and make the code more readable.

Release Notes:

- N/A

---------

Co-authored-by: Piotr <piotr@zed.dev>

Change summary

Cargo.lock                                    |   3 
crates/assistant/src/assistant_panel.rs       |  12 
crates/assistant/src/codegen.rs               |   3 
crates/collab/Cargo.toml                      |   1 
crates/collab/src/tests/editor_tests.rs       |  64 ++
crates/diagnostics/src/diagnostics_tests.rs   |  83 ++--
crates/editor/src/actions.rs                  |   6 
crates/editor/src/display_map.rs              | 286 +++++++++------
crates/editor/src/display_map/block_map.rs    |  54 +-
crates/editor/src/display_map/fold_map.rs     |  10 
crates/editor/src/display_map/inlay_map.rs    |  20 
crates/editor/src/editor.rs                   | 396 +++++++++++++-------
crates/editor/src/editor_tests.rs             | 376 ++++++++++---------
crates/editor/src/element.rs                  | 378 +++++++++++--------
crates/editor/src/git.rs                      |  72 ++-
crates/editor/src/git/blame.rs                |  15 
crates/editor/src/hover_popover.rs            |   6 
crates/editor/src/hunk_diff.rs                |  43 +-
crates/editor/src/movement.rs                 |  87 ++--
crates/editor/src/scroll.rs                   |  12 
crates/editor/src/scroll/actions.rs           |   5 
crates/editor/src/scroll/autoscroll.rs        |  30 
crates/editor/src/selections_collection.rs    |   5 
crates/editor/src/test.rs                     |  41 +
crates/editor/src/test/editor_test_context.rs |   3 
crates/git/src/diff.rs                        |  12 
crates/go_to_line/src/go_to_line.rs           |   1 
crates/language/src/buffer.rs                 |   4 
crates/multi_buffer/Cargo.toml                |   1 
crates/multi_buffer/src/multi_buffer.rs       | 169 +++++---
crates/outline/src/outline.rs                 |   1 
crates/search/src/buffer_search.rs            |  84 ++-
crates/search/src/project_search.rs           |  20 
crates/vim/Cargo.toml                         |   1 
crates/vim/src/motion.rs                      |  53 +-
crates/vim/src/normal.rs                      |   7 
crates/vim/src/normal/case.rs                 |   9 
crates/vim/src/normal/delete.rs               |   5 
crates/vim/src/normal/paste.rs                |   5 
crates/vim/src/normal/scroll.rs               |  12 
crates/vim/src/normal/search.rs               |   4 
crates/vim/src/object.rs                      |  28 
crates/vim/src/test.rs                        |   4 
crates/vim/src/utils.rs                       |   9 
crates/vim/src/visual.rs                      |  21 
crates/zed/src/zed.rs                         |  65 +-
46 files changed, 1,463 insertions(+), 1,063 deletions(-)

Detailed changes

Cargo.lock šŸ”—

@@ -2325,6 +2325,7 @@ dependencies = [
  "log",
  "lsp",
  "menu",
+ "multi_buffer",
  "nanoid",
  "node_runtime",
  "notifications",
@@ -6282,6 +6283,7 @@ dependencies = [
  "log",
  "parking_lot",
  "rand 0.8.5",
+ "serde",
  "settings",
  "smallvec",
  "sum_tree",
@@ -11315,6 +11317,7 @@ dependencies = [
  "language",
  "log",
  "lsp",
+ "multi_buffer",
  "nvim-rs",
  "parking_lot",
  "regex",

crates/assistant/src/assistant_panel.rs šŸ”—

@@ -18,7 +18,7 @@ use editor::{
     },
     scroll::{Autoscroll, AutoscrollStrategy},
     Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, MultiBufferSnapshot,
-    ToOffset as _, ToPoint,
+    RowExt, ToOffset as _, ToPoint,
 };
 use file_icons::FileIcons;
 use fs::Fs;
@@ -32,6 +32,7 @@ use gpui::{
     View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext,
 };
 use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _};
+use multi_buffer::MultiBufferRow;
 use parking_lot::Mutex;
 use project::Project;
 use search::{buffer_search::DivRegistrar, BufferSearchBar};
@@ -306,7 +307,7 @@ impl AssistantPanel {
             if point_selection.end.column == 0 {
                 point_selection.end.row -= 1;
             }
-            point_selection.end.column = snapshot.line_len(point_selection.end.row);
+            point_selection.end.column = snapshot.line_len(MultiBufferRow(point_selection.end.row));
         }
 
         let codegen_kind = if point_selection.start == point_selection.end {
@@ -2168,7 +2169,7 @@ impl ConversationEditor {
                         let snapshot = editor.snapshot(cx);
                         let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
                         let scroll_top =
-                            cursor_point.row() as f32 - scroll_position.offset_before_cursor.y;
+                            cursor_point.row().as_f32() - scroll_position.offset_before_cursor.y;
                         editor.set_scroll_position(
                             point(scroll_position.offset_before_cursor.x, scroll_top),
                             cx,
@@ -2236,7 +2237,10 @@ impl ConversationEditor {
         self.editor.update(cx, |editor, cx| {
             let snapshot = editor.snapshot(cx);
             let cursor = editor.selections.newest_anchor().head();
-            let cursor_row = cursor.to_display_point(&snapshot.display_snapshot).row() as f32;
+            let cursor_row = cursor
+                .to_display_point(&snapshot.display_snapshot)
+                .row()
+                .as_f32();
             let scroll_position = editor
                 .scroll_manager
                 .anchor()

crates/assistant/src/codegen.rs šŸ”—

@@ -7,6 +7,7 @@ use editor::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
 use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
 use gpui::{EventEmitter, Model, ModelContext, Task};
 use language::{Rope, TransactionId};
+use multi_buffer::MultiBufferRow;
 use std::{cmp, future, ops::Range};
 
 pub enum Event {
@@ -100,7 +101,7 @@ impl Codegen {
             .suggested_indents(selection_start.row..selection_start.row + 1, cx)
             .into_values()
             .next()
-            .unwrap_or_else(|| snapshot.indent_size_for_line(selection_start.row));
+            .unwrap_or_else(|| snapshot.indent_size_for_line(MultiBufferRow(selection_start.row)));
 
         let response = CompletionProvider::global(cx).complete(prompt);
         self.generation = cx.spawn(|this, mut cx| {

crates/collab/Cargo.toml šŸ”—

@@ -90,6 +90,7 @@ language = { workspace = true, features = ["test-support"] }
 live_kit_client = { workspace = true, features = ["test-support"] }
 lsp = { workspace = true, features = ["test-support"] }
 menu.workspace = true
+multi_buffer = { workspace = true, features = ["test-support"] }
 node_runtime.workspace = true
 notifications = { workspace = true, features = ["test-support"] }
 pretty_assertions.workspace = true

crates/collab/src/tests/editor_tests.rs šŸ”—

@@ -9,6 +9,7 @@ use editor::{
         ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, Redo, Rename,
         RevertSelectedHunks, ToggleCodeActions, Undo,
     },
+    display_map::DisplayRow,
     test::{
         editor_hunks,
         editor_test_context::{AssertionContextManager, EditorTestContext},
@@ -24,6 +25,7 @@ use language::{
     language_settings::{AllLanguageSettings, InlayHintSettings},
     FakeLspAdapter,
 };
+use multi_buffer::MultiBufferRow;
 use project::{
     project_settings::{InlineBlameSettings, ProjectSettings},
     SERVER_PROGRESS_DEBOUNCE_TIMEOUT,
@@ -2114,14 +2116,30 @@ struct Row10;"#};
         assert_eq!(
             all_hunks,
             vec![
-                ("".to_string(), DiffHunkStatus::Added, 1..3),
-                ("struct Row2;\n".to_string(), DiffHunkStatus::Removed, 4..4),
-                ("struct Row5;\n".to_string(), DiffHunkStatus::Modified, 6..7),
-                ("struct Row8;\n".to_string(), DiffHunkStatus::Removed, 9..9),
+                (
+                    "".to_string(),
+                    DiffHunkStatus::Added,
+                    DisplayRow(1)..DisplayRow(3)
+                ),
+                (
+                    "struct Row2;\n".to_string(),
+                    DiffHunkStatus::Removed,
+                    DisplayRow(4)..DisplayRow(4)
+                ),
+                (
+                    "struct Row5;\n".to_string(),
+                    DiffHunkStatus::Modified,
+                    DisplayRow(6)..DisplayRow(7)
+                ),
+                (
+                    "struct Row8;\n".to_string(),
+                    DiffHunkStatus::Removed,
+                    DisplayRow(9)..DisplayRow(9)
+                ),
                 (
                     "struct Row10;".to_string(),
                     DiffHunkStatus::Modified,
-                    10..10,
+                    DisplayRow(10)..DisplayRow(10),
                 ),
             ]
         );
@@ -2133,23 +2151,35 @@ struct Row10;"#};
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
             expanded_hunks_background_highlights(editor, cx),
-            vec![1..=2, 8..=8],
+            vec![DisplayRow(1)..=DisplayRow(2), DisplayRow(8)..=DisplayRow(8)],
         );
         assert_eq!(
             all_hunks,
             vec![
-                ("".to_string(), DiffHunkStatus::Added, 1..3),
-                ("struct Row2;\n".to_string(), DiffHunkStatus::Removed, 5..5),
-                ("struct Row5;\n".to_string(), DiffHunkStatus::Modified, 8..9),
+                (
+                    "".to_string(),
+                    DiffHunkStatus::Added,
+                    DisplayRow(1)..DisplayRow(3)
+                ),
+                (
+                    "struct Row2;\n".to_string(),
+                    DiffHunkStatus::Removed,
+                    DisplayRow(5)..DisplayRow(5)
+                ),
+                (
+                    "struct Row5;\n".to_string(),
+                    DiffHunkStatus::Modified,
+                    DisplayRow(8)..DisplayRow(9)
+                ),
                 (
                     "struct Row8;\n".to_string(),
                     DiffHunkStatus::Removed,
-                    12..12
+                    DisplayRow(12)..DisplayRow(12)
                 ),
                 (
                     "struct Row10;".to_string(),
                     DiffHunkStatus::Modified,
-                    13..13,
+                    DisplayRow(13)..DisplayRow(13),
                 ),
             ]
         );
@@ -2173,7 +2203,7 @@ struct Row10;"#};
             vec![(
                 "struct Row10;".to_string(),
                 DiffHunkStatus::Modified,
-                10..10,
+                DisplayRow(10)..DisplayRow(10),
             )]
         );
         assert_eq!(all_expanded_hunks, Vec::new());
@@ -2184,14 +2214,14 @@ struct Row10;"#};
         let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
         assert_eq!(
             expanded_hunks_background_highlights(editor, cx),
-            vec![5..=5]
+            vec![DisplayRow(5)..=DisplayRow(5)]
         );
         assert_eq!(
             all_hunks,
             vec![(
                 "struct Row10;".to_string(),
                 DiffHunkStatus::Modified,
-                10..10,
+                DisplayRow(10)..DisplayRow(10),
             )]
         );
         assert_eq!(all_expanded_hunks, Vec::new());
@@ -2330,7 +2360,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
         let blame = editor_b.blame().expect("editor_b should have blame now");
         let entries = blame.update(cx, |blame, cx| {
             blame
-                .blame_for_rows((0..4).map(Some), cx)
+                .blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
                 .collect::<Vec<_>>()
         });
 
@@ -2369,7 +2399,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
         let blame = editor_b.blame().expect("editor_b should have blame now");
         let entries = blame.update(cx, |blame, cx| {
             blame
-                .blame_for_rows((0..4).map(Some), cx)
+                .blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
                 .collect::<Vec<_>>()
         });
 
@@ -2396,7 +2426,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
         let blame = editor_b.blame().expect("editor_b should have blame now");
         let entries = blame.update(cx, |blame, cx| {
             blame
-                .blame_for_rows((0..4).map(Some), cx)
+                .blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
                 .collect::<Vec<_>>()
         });
 

crates/diagnostics/src/diagnostics_tests.rs šŸ”—

@@ -1,7 +1,7 @@
 use super::*;
 use collections::HashMap;
 use editor::{
-    display_map::{BlockContext, TransformBlock},
+    display_map::{BlockContext, DisplayRow, TransformBlock},
     DisplayPoint, GutterDimensions,
 };
 use gpui::{px, AvailableSpace, Stateful, TestAppContext, VisualTestContext};
@@ -158,11 +158,11 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
     assert_eq!(
         editor_blocks(&editor, cx),
         [
-            (0, "path header block".into()),
-            (2, "diagnostic header".into()),
-            (15, "collapsed context".into()),
-            (16, "diagnostic header".into()),
-            (25, "collapsed context".into()),
+            (DisplayRow(0), "path header block".into()),
+            (DisplayRow(2), "diagnostic header".into()),
+            (DisplayRow(15), "collapsed context".into()),
+            (DisplayRow(16), "diagnostic header".into()),
+            (DisplayRow(25), "collapsed context".into()),
         ]
     );
     assert_eq!(
@@ -210,7 +210,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
     editor.update(cx, |editor, cx| {
         assert_eq!(
             editor.selections.display_ranges(cx),
-            [DisplayPoint::new(12, 6)..DisplayPoint::new(12, 6)]
+            [DisplayPoint::new(DisplayRow(12), 6)..DisplayPoint::new(DisplayRow(12), 6)]
         );
     });
 
@@ -243,13 +243,13 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
     assert_eq!(
         editor_blocks(&editor, cx),
         [
-            (0, "path header block".into()),
-            (2, "diagnostic header".into()),
-            (7, "path header block".into()),
-            (9, "diagnostic header".into()),
-            (22, "collapsed context".into()),
-            (23, "diagnostic header".into()),
-            (32, "collapsed context".into()),
+            (DisplayRow(0), "path header block".into()),
+            (DisplayRow(2), "diagnostic header".into()),
+            (DisplayRow(7), "path header block".into()),
+            (DisplayRow(9), "diagnostic header".into()),
+            (DisplayRow(22), "collapsed context".into()),
+            (DisplayRow(23), "diagnostic header".into()),
+            (DisplayRow(32), "collapsed context".into()),
         ]
     );
 
@@ -309,7 +309,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
     editor.update(cx, |editor, cx| {
         assert_eq!(
             editor.selections.display_ranges(cx),
-            [DisplayPoint::new(19, 6)..DisplayPoint::new(19, 6)]
+            [DisplayPoint::new(DisplayRow(19), 6)..DisplayPoint::new(DisplayRow(19), 6)]
         );
     });
 
@@ -355,15 +355,15 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
     assert_eq!(
         editor_blocks(&editor, cx),
         [
-            (0, "path header block".into()),
-            (2, "diagnostic header".into()),
-            (7, "collapsed context".into()),
-            (8, "diagnostic header".into()),
-            (13, "path header block".into()),
-            (15, "diagnostic header".into()),
-            (28, "collapsed context".into()),
-            (29, "diagnostic header".into()),
-            (38, "collapsed context".into()),
+            (DisplayRow(0), "path header block".into()),
+            (DisplayRow(2), "diagnostic header".into()),
+            (DisplayRow(7), "collapsed context".into()),
+            (DisplayRow(8), "diagnostic header".into()),
+            (DisplayRow(13), "path header block".into()),
+            (DisplayRow(15), "diagnostic header".into()),
+            (DisplayRow(28), "collapsed context".into()),
+            (DisplayRow(29), "diagnostic header".into()),
+            (DisplayRow(38), "collapsed context".into()),
         ]
     );
 
@@ -493,8 +493,8 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
     assert_eq!(
         editor_blocks(&editor, cx),
         [
-            (0, "path header block".into()),
-            (2, "diagnostic header".into()),
+            (DisplayRow(0), "path header block".into()),
+            (DisplayRow(2), "diagnostic header".into()),
         ]
     );
     assert_eq!(
@@ -539,10 +539,10 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
     assert_eq!(
         editor_blocks(&editor, cx),
         [
-            (0, "path header block".into()),
-            (2, "diagnostic header".into()),
-            (6, "collapsed context".into()),
-            (7, "diagnostic header".into()),
+            (DisplayRow(0), "path header block".into()),
+            (DisplayRow(2), "diagnostic header".into()),
+            (DisplayRow(6), "collapsed context".into()),
+            (DisplayRow(7), "diagnostic header".into()),
         ]
     );
     assert_eq!(
@@ -605,10 +605,10 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
     assert_eq!(
         editor_blocks(&editor, cx),
         [
-            (0, "path header block".into()),
-            (2, "diagnostic header".into()),
-            (7, "collapsed context".into()),
-            (8, "diagnostic header".into()),
+            (DisplayRow(0), "path header block".into()),
+            (DisplayRow(2), "diagnostic header".into()),
+            (DisplayRow(7), "collapsed context".into()),
+            (DisplayRow(8), "diagnostic header".into()),
         ]
     );
     assert_eq!(
@@ -661,10 +661,10 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
     assert_eq!(
         editor_blocks(&editor, cx),
         [
-            (0, "path header block".into()),
-            (2, "diagnostic header".into()),
-            (7, "collapsed context".into()),
-            (8, "diagnostic header".into()),
+            (DisplayRow(0), "path header block".into()),
+            (DisplayRow(2), "diagnostic header".into()),
+            (DisplayRow(7), "collapsed context".into()),
+            (DisplayRow(8), "diagnostic header".into()),
         ]
     );
     assert_eq!(
@@ -958,14 +958,17 @@ fn random_diagnostic(
     }
 }
 
-fn editor_blocks(editor: &View<Editor>, cx: &mut VisualTestContext) -> Vec<(u32, SharedString)> {
+fn editor_blocks(
+    editor: &View<Editor>,
+    cx: &mut VisualTestContext,
+) -> Vec<(DisplayRow, SharedString)> {
     let mut blocks = Vec::new();
     cx.draw(gpui::Point::default(), AvailableSpace::min_size(), |cx| {
         editor.update(cx, |editor, cx| {
             let snapshot = editor.snapshot(cx);
             blocks.extend(
                 snapshot
-                    .blocks_in_range(0..snapshot.max_point().row())
+                    .blocks_in_range(DisplayRow(0)..snapshot.max_point().row())
                     .enumerate()
                     .filter_map(|(ix, (row, block))| {
                         let name: SharedString = match block {

crates/editor/src/actions.rs šŸ”—

@@ -54,7 +54,7 @@ pub struct SelectToEndOfLine {
 pub struct ToggleCodeActions {
     // Display row from which the action was deployed.
     #[serde(default)]
-    pub deployed_from_indicator: Option<u32>,
+    pub deployed_from_indicator: Option<DisplayRow>,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default)]
@@ -77,12 +77,12 @@ pub struct ToggleComments {
 
 #[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct FoldAt {
-    pub buffer_row: u32,
+    pub buffer_row: MultiBufferRow,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct UnfoldAt {
-    pub buffer_row: u32,
+    pub buffer_row: MultiBufferRow,
 }
 
 #[derive(PartialEq, Clone, Deserialize, Default)]

crates/editor/src/display_map.rs šŸ”—

@@ -23,8 +23,8 @@ mod inlay_map;
 mod tab_map;
 mod wrap_map;
 
-use crate::EditorStyle;
 use crate::{hover_links::InlayHighlight, movement::TextLayoutDetails, InlayId};
+use crate::{EditorStyle, RowExt};
 pub use block_map::{BlockMap, BlockPoint};
 use collections::{HashMap, HashSet};
 use fold_map::FoldMap;
@@ -34,7 +34,11 @@ use language::{
     language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
 };
 use lsp::DiagnosticSeverity;
-use multi_buffer::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
+use multi_buffer::{
+    Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
+    ToOffset, ToPoint,
+};
+use serde::Deserialize;
 use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
 use sum_tree::{Bias, TreeMap};
 use tab_map::TabMap;
@@ -42,10 +46,11 @@ use tab_map::TabMap;
 use wrap_map::WrapMap;
 
 pub use block_map::{
-    BlockBufferRows as DisplayBufferRows, BlockChunks as DisplayChunks, BlockContext,
-    BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
+    BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockDisposition, BlockId,
+    BlockProperties, BlockStyle, RenderBlock, TransformBlock,
 };
 
+use self::block_map::BlockRow;
 pub use self::fold_map::{Fold, FoldId, FoldPoint};
 pub use self::inlay_map::{InlayOffset, InlayPoint};
 pub(crate) use inlay_map::Inlay;
@@ -65,6 +70,17 @@ pub trait ToDisplayPoint {
 type TextHighlights = TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
 type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>;
 
+#[derive(Clone)]
+pub struct DisplayBufferRows<'a>(BlockBufferRows<'a>);
+
+impl<'a> Iterator for DisplayBufferRows<'a> {
+    type Item = Option<DisplayRow>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.0.next().map(|row| row.map(|r| DisplayRow(r.0)))
+    }
+}
+
 /// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
 /// folding, hard tabs, soft wrapping, custom blocks (like diagnostics), and highlighting.
 ///
@@ -382,15 +398,15 @@ impl DisplaySnapshot {
         self.buffer_snapshot.len() == 0
     }
 
-    pub fn buffer_rows(&self, start_row: u32) -> DisplayBufferRows {
-        self.block_snapshot.buffer_rows(start_row)
+    pub fn display_rows(&self, start_row: DisplayRow) -> DisplayBufferRows {
+        DisplayBufferRows(self.block_snapshot.buffer_rows(BlockRow(start_row.0)))
     }
 
-    pub fn max_buffer_row(&self) -> u32 {
+    pub fn max_buffer_row(&self) -> MultiBufferRow {
         self.buffer_snapshot.max_buffer_row()
     }
 
-    pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
+    pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
         loop {
             let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
             let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left);
@@ -408,7 +424,7 @@ impl DisplaySnapshot {
         }
     }
 
-    pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
+    pub fn next_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
         loop {
             let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
             let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right);
@@ -429,13 +445,14 @@ impl DisplaySnapshot {
     // used by line_mode selections and tries to match vim behaviour
     pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
         let new_start = if range.start.row == 0 {
-            Point::new(0, 0)
-        } else if range.start.row == self.max_buffer_row()
-            || (range.end.column > 0 && range.end.row == self.max_buffer_row())
+            MultiBufferPoint::new(0, 0)
+        } else if range.start.row == self.max_buffer_row().0
+            || (range.end.column > 0 && range.end.row == self.max_buffer_row().0)
         {
-            Point::new(
+            MultiBufferPoint::new(
                 range.start.row - 1,
-                self.buffer_snapshot.line_len(range.start.row - 1),
+                self.buffer_snapshot
+                    .line_len(MultiBufferRow(range.start.row - 1)),
             )
         } else {
             self.prev_line_boundary(range.start).0
@@ -443,9 +460,9 @@ impl DisplaySnapshot {
 
         let new_end = if range.end.column == 0 {
             range.end
-        } else if range.end.row < self.max_buffer_row() {
+        } else if range.end.row < self.max_buffer_row().0 {
             self.buffer_snapshot
-                .clip_point(Point::new(range.end.row + 1, 0), Bias::Left)
+                .clip_point(MultiBufferPoint::new(range.end.row + 1, 0), Bias::Left)
         } else {
             self.buffer_snapshot.max_point()
         };
@@ -453,7 +470,7 @@ impl DisplaySnapshot {
         new_start..new_end
     }
 
-    fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint {
+    fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
         let inlay_point = self.inlay_snapshot.to_inlay_point(point);
         let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
         let tab_point = self.tab_snapshot.to_tab_point(fold_point);
@@ -509,10 +526,10 @@ impl DisplaySnapshot {
     }
 
     /// Returns text chunks starting at the given display row until the end of the file
-    pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
+    pub fn text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
         self.block_snapshot
             .chunks(
-                display_row..self.max_point().row() + 1,
+                display_row.0..self.max_point().row().next_row().0,
                 false,
                 Highlights::default(),
             )
@@ -520,8 +537,8 @@ impl DisplaySnapshot {
     }
 
     /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
-    pub fn reverse_text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
-        (0..=display_row).rev().flat_map(|row| {
+    pub fn reverse_text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
+        (0..=display_row.0).rev().flat_map(|row| {
             self.block_snapshot
                 .chunks(row..row + 1, false, Highlights::default())
                 .map(|h| h.text)
@@ -533,12 +550,12 @@ impl DisplaySnapshot {
 
     pub fn chunks(
         &self,
-        display_rows: Range<u32>,
+        display_rows: Range<DisplayRow>,
         language_aware: bool,
         highlight_styles: HighlightStyles,
     ) -> DisplayChunks<'_> {
         self.block_snapshot.chunks(
-            display_rows,
+            display_rows.start.0..display_rows.end.0,
             language_aware,
             Highlights {
                 text_highlights: Some(&self.text_highlights),
@@ -550,7 +567,7 @@ impl DisplaySnapshot {
 
     pub fn highlighted_chunks<'a>(
         &'a self,
-        display_rows: Range<u32>,
+        display_rows: Range<DisplayRow>,
         language_aware: bool,
         editor_style: &'a EditorStyle,
     ) -> impl Iterator<Item = HighlightedChunk<'a>> {
@@ -610,7 +627,7 @@ impl DisplaySnapshot {
 
     pub fn layout_row(
         &self,
-        display_row: u32,
+        display_row: DisplayRow,
         TextLayoutDetails {
             text_system,
             editor_style,
@@ -623,7 +640,7 @@ impl DisplaySnapshot {
         let mut runs = Vec::new();
         let mut line = String::new();
 
-        let range = display_row..display_row + 1;
+        let range = display_row..display_row.next_row();
         for chunk in self.highlighted_chunks(range, false, &editor_style) {
             line.push_str(chunk.chunk);
 
@@ -663,7 +680,7 @@ impl DisplaySnapshot {
 
     pub fn display_column_for_x(
         &self,
-        display_row: u32,
+        display_row: DisplayRow,
         x: Pixels,
         details: &TextLayoutDetails,
     ) -> u32 {
@@ -732,7 +749,7 @@ impl DisplaySnapshot {
 
     pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint {
         let mut point = point.0;
-        if point.column == self.line_len(point.row) {
+        if point.column == self.line_len(DisplayRow(point.row)) {
             point.column = point.column.saturating_sub(1);
             point = self.block_snapshot.clip_point(point, Bias::Left);
         }
@@ -748,36 +765,38 @@ impl DisplaySnapshot {
 
     pub fn blocks_in_range(
         &self,
-        rows: Range<u32>,
-    ) -> impl Iterator<Item = (u32, &TransformBlock)> {
-        self.block_snapshot.blocks_in_range(rows)
+        rows: Range<DisplayRow>,
+    ) -> impl Iterator<Item = (DisplayRow, &TransformBlock)> {
+        self.block_snapshot
+            .blocks_in_range(rows.start.0..rows.end.0)
+            .map(|(row, block)| (DisplayRow(row), block))
     }
 
     pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
         self.fold_snapshot.intersects_fold(offset)
     }
 
-    pub fn is_line_folded(&self, buffer_row: u32) -> bool {
+    pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
         self.fold_snapshot.is_line_folded(buffer_row)
     }
 
-    pub fn is_block_line(&self, display_row: u32) -> bool {
-        self.block_snapshot.is_block_line(display_row)
+    pub fn is_block_line(&self, display_row: DisplayRow) -> bool {
+        self.block_snapshot.is_block_line(BlockRow(display_row.0))
     }
 
-    pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
+    pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
         let wrap_row = self
             .block_snapshot
-            .to_wrap_point(BlockPoint::new(display_row, 0))
+            .to_wrap_point(BlockPoint::new(display_row.0, 0))
             .row();
         self.wrap_snapshot.soft_wrap_indent(wrap_row)
     }
 
     pub fn text(&self) -> String {
-        self.text_chunks(0).collect()
+        self.text_chunks(DisplayRow(0)).collect()
     }
 
-    pub fn line(&self, display_row: u32) -> String {
+    pub fn line(&self, display_row: DisplayRow) -> String {
         let mut result = String::new();
         for chunk in self.text_chunks(display_row) {
             if let Some(ix) = chunk.find('\n') {
@@ -790,7 +809,7 @@ impl DisplaySnapshot {
         result
     }
 
-    pub fn line_indent_for_buffer_row(&self, buffer_row: u32) -> (u32, bool) {
+    pub fn line_indent_for_buffer_row(&self, buffer_row: MultiBufferRow) -> (u32, bool) {
         let (buffer, range) = self
             .buffer_snapshot
             .buffer_line_for_row(buffer_row)
@@ -812,15 +831,15 @@ impl DisplaySnapshot {
         (indent_size, is_blank)
     }
 
-    pub fn line_len(&self, row: u32) -> u32 {
-        self.block_snapshot.line_len(row)
+    pub fn line_len(&self, row: DisplayRow) -> u32 {
+        self.block_snapshot.line_len(BlockRow(row.0))
     }
 
-    pub fn longest_row(&self) -> u32 {
-        self.block_snapshot.longest_row()
+    pub fn longest_row(&self) -> DisplayRow {
+        DisplayRow(self.block_snapshot.longest_row())
     }
 
-    pub fn fold_for_line(&self, buffer_row: u32) -> Option<FoldStatus> {
+    pub fn fold_for_line(&self, buffer_row: MultiBufferRow) -> Option<FoldStatus> {
         if self.is_line_folded(buffer_row) {
             Some(FoldStatus::Folded)
         } else if self.is_foldable(buffer_row) {
@@ -830,7 +849,7 @@ impl DisplaySnapshot {
         }
     }
 
-    pub fn is_foldable(&self, buffer_row: u32) -> bool {
+    pub fn is_foldable(&self, buffer_row: MultiBufferRow) -> bool {
         let max_row = self.buffer_snapshot.max_buffer_row();
         if buffer_row >= max_row {
             return false;
@@ -841,8 +860,9 @@ impl DisplaySnapshot {
             return false;
         }
 
-        for next_row in (buffer_row + 1)..=max_row {
-            let (next_indent_size, next_line_is_blank) = self.line_indent_for_buffer_row(next_row);
+        for next_row in (buffer_row.0 + 1)..=max_row.0 {
+            let (next_indent_size, next_line_is_blank) =
+                self.line_indent_for_buffer_row(MultiBufferRow(next_row));
             if next_indent_size > indent_size {
                 return true;
             } else if !next_line_is_blank {
@@ -853,20 +873,22 @@ impl DisplaySnapshot {
         false
     }
 
-    pub fn foldable_range(&self, buffer_row: u32) -> Option<Range<Point>> {
-        let start = Point::new(buffer_row, self.buffer_snapshot.line_len(buffer_row));
-        if self.is_foldable(start.row) && !self.is_line_folded(start.row) {
+    pub fn foldable_range(&self, buffer_row: MultiBufferRow) -> Option<Range<Point>> {
+        let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
+        if self.is_foldable(MultiBufferRow(start.row))
+            && !self.is_line_folded(MultiBufferRow(start.row))
+        {
             let (start_indent, _) = self.line_indent_for_buffer_row(buffer_row);
             let max_point = self.buffer_snapshot.max_point();
             let mut end = None;
 
-            for row in (buffer_row + 1)..=max_point.row {
-                let (indent, is_blank) = self.line_indent_for_buffer_row(row);
+            for row in (buffer_row.0 + 1)..=max_point.row {
+                let (indent, is_blank) = self.line_indent_for_buffer_row(MultiBufferRow(row));
                 if !is_blank && indent <= start_indent {
                     let prev_row = row - 1;
                     end = Some(Point::new(
                         prev_row,
-                        self.buffer_snapshot.line_len(prev_row),
+                        self.buffer_snapshot.line_len(MultiBufferRow(prev_row)),
                     ));
                     break;
                 }
@@ -903,27 +925,31 @@ impl Debug for DisplayPoint {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.write_fmt(format_args!(
             "DisplayPoint({}, {})",
-            self.row(),
+            self.row().0,
             self.column()
         ))
     }
 }
 
+#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
+#[serde(transparent)]
+pub struct DisplayRow(pub u32);
+
 impl DisplayPoint {
-    pub fn new(row: u32, column: u32) -> Self {
-        Self(BlockPoint(Point::new(row, column)))
+    pub fn new(row: DisplayRow, column: u32) -> Self {
+        Self(BlockPoint(Point::new(row.0, column)))
     }
 
     pub fn zero() -> Self {
-        Self::new(0, 0)
+        Self::new(DisplayRow(0), 0)
     }
 
     pub fn is_zero(&self) -> bool {
         self.0.is_zero()
     }
 
-    pub fn row(self) -> u32 {
-        self.0.row
+    pub fn row(self) -> DisplayRow {
+        DisplayRow(self.0.row)
     }
 
     pub fn column(self) -> u32 {
@@ -1171,7 +1197,7 @@ pub mod tests {
             let buffer = &snapshot.buffer_snapshot;
             for _ in 0..5 {
                 let row = rng.gen_range(0..=buffer.max_point().row);
-                let column = rng.gen_range(0..=buffer.line_len(row));
+                let column = rng.gen_range(0..=buffer.line_len(MultiBufferRow(row)));
                 let point = buffer.clip_point(Point::new(row, column), Left);
 
                 let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
@@ -1216,12 +1242,12 @@ pub mod tests {
             }
 
             // Movement
-            let min_point = snapshot.clip_point(DisplayPoint::new(0, 0), Left);
+            let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
             let max_point = snapshot.clip_point(snapshot.max_point(), Right);
             for _ in 0..5 {
-                let row = rng.gen_range(0..=snapshot.max_point().row());
-                let column = rng.gen_range(0..=snapshot.line_len(row));
-                let point = snapshot.clip_point(DisplayPoint::new(row, column), Left);
+                let row = rng.gen_range(0..=snapshot.max_point().row().0);
+                let column = rng.gen_range(0..=snapshot.line_len(DisplayRow(row)));
+                let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
 
                 log::info!("Moving from point {:?}", point);
 
@@ -1288,63 +1314,64 @@ pub mod tests {
 
             let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
             assert_eq!(
-                snapshot.text_chunks(0).collect::<String>(),
+                snapshot.text_chunks(DisplayRow(0)).collect::<String>(),
                 "one two \nthree four \nfive\nsix seven \neight"
             );
             assert_eq!(
-                snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
-                DisplayPoint::new(0, 7)
+                snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Left),
+                DisplayPoint::new(DisplayRow(0), 7)
             );
             assert_eq!(
-                snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Right),
-                DisplayPoint::new(1, 0)
+                snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Right),
+                DisplayPoint::new(DisplayRow(1), 0)
             );
             assert_eq!(
-                movement::right(&snapshot, DisplayPoint::new(0, 7)),
-                DisplayPoint::new(1, 0)
+                movement::right(&snapshot, DisplayPoint::new(DisplayRow(0), 7)),
+                DisplayPoint::new(DisplayRow(1), 0)
             );
             assert_eq!(
-                movement::left(&snapshot, DisplayPoint::new(1, 0)),
-                DisplayPoint::new(0, 7)
+                movement::left(&snapshot, DisplayPoint::new(DisplayRow(1), 0)),
+                DisplayPoint::new(DisplayRow(0), 7)
             );
 
-            let x = snapshot.x_for_display_point(DisplayPoint::new(1, 10), &text_layout_details);
+            let x = snapshot
+                .x_for_display_point(DisplayPoint::new(DisplayRow(1), 10), &text_layout_details);
             assert_eq!(
                 movement::up(
                     &snapshot,
-                    DisplayPoint::new(1, 10),
+                    DisplayPoint::new(DisplayRow(1), 10),
                     SelectionGoal::None,
                     false,
                     &text_layout_details,
                 ),
                 (
-                    DisplayPoint::new(0, 7),
+                    DisplayPoint::new(DisplayRow(0), 7),
                     SelectionGoal::HorizontalPosition(x.0)
                 )
             );
             assert_eq!(
                 movement::down(
                     &snapshot,
-                    DisplayPoint::new(0, 7),
+                    DisplayPoint::new(DisplayRow(0), 7),
                     SelectionGoal::HorizontalPosition(x.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(1, 10),
+                    DisplayPoint::new(DisplayRow(1), 10),
                     SelectionGoal::HorizontalPosition(x.0)
                 )
             );
             assert_eq!(
                 movement::down(
                     &snapshot,
-                    DisplayPoint::new(1, 10),
+                    DisplayPoint::new(DisplayRow(1), 10),
                     SelectionGoal::HorizontalPosition(x.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(2, 4),
+                    DisplayPoint::new(DisplayRow(2), 4),
                     SelectionGoal::HorizontalPosition(x.0)
                 )
             );
@@ -1356,7 +1383,7 @@ pub mod tests {
 
             let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
             assert_eq!(
-                snapshot.text_chunks(1).collect::<String>(),
+                snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
                 "three four \nfive\nsix and \nseven eight"
             );
 
@@ -1367,7 +1394,7 @@ pub mod tests {
 
             let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
             assert_eq!(
-                snapshot.text_chunks(1).collect::<String>(),
+                snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
                 "three \nfour five\nsix and \nseven \neight"
             )
         });
@@ -1388,9 +1415,18 @@ pub mod tests {
         buffer.update(cx, |buffer, cx| {
             buffer.edit(
                 vec![
-                    (Point::new(1, 0)..Point::new(1, 0), "\t"),
-                    (Point::new(1, 1)..Point::new(1, 1), "\t"),
-                    (Point::new(2, 1)..Point::new(2, 1), "\t"),
+                    (
+                        MultiBufferPoint::new(1, 0)..MultiBufferPoint::new(1, 0),
+                        "\t",
+                    ),
+                    (
+                        MultiBufferPoint::new(1, 1)..MultiBufferPoint::new(1, 1),
+                        "\t",
+                    ),
+                    (
+                        MultiBufferPoint::new(2, 1)..MultiBufferPoint::new(2, 1),
+                        "\t",
+                    ),
                 ],
                 None,
                 cx,
@@ -1399,7 +1435,7 @@ pub mod tests {
 
         assert_eq!(
             map.update(cx, |map, cx| map.snapshot(cx))
-                .text_chunks(1)
+                .text_chunks(DisplayRow(1))
                 .collect::<String>()
                 .lines()
                 .next(),
@@ -1407,7 +1443,7 @@ pub mod tests {
         );
         assert_eq!(
             map.update(cx, |map, cx| map.snapshot(cx))
-                .text_chunks(2)
+                .text_chunks(DisplayRow(2))
                 .collect::<String>()
                 .lines()
                 .next(),
@@ -1462,7 +1498,7 @@ pub mod tests {
         let map = cx
             .new_model(|cx| DisplayMap::new(buffer, font("Helvetica"), font_size, None, 1, 1, cx));
         assert_eq!(
-            cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
+            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
             vec![
                 ("fn ".to_string(), None),
                 ("outer".to_string(), Some(Hsla::blue())),
@@ -1473,7 +1509,7 @@ pub mod tests {
             ]
         );
         assert_eq!(
-            cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
+            cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
             vec![
                 ("    fn ".to_string(), Some(Hsla::red())),
                 ("inner".to_string(), Some(Hsla::blue())),
@@ -1482,10 +1518,13 @@ pub mod tests {
         );
 
         map.update(cx, |map, cx| {
-            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
+            map.fold(
+                vec![MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2)],
+                cx,
+            )
         });
         assert_eq!(
-            cx.update(|cx| syntax_chunks(0..2, &map, &theme, cx)),
+            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(2), &map, &theme, cx)),
             vec![
                 ("fn ".to_string(), None),
                 ("out".to_string(), Some(Hsla::blue())),
@@ -1548,7 +1587,7 @@ pub mod tests {
             DisplayMap::new(buffer, font("Courier"), font_size, Some(px(40.0)), 1, 1, cx)
         });
         assert_eq!(
-            cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
+            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
             [
                 ("fn \n".to_string(), None),
                 ("oute\nr".to_string(), Some(Hsla::blue())),
@@ -1556,15 +1595,18 @@ pub mod tests {
             ]
         );
         assert_eq!(
-            cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
+            cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
             [("{}\n\n".to_string(), None)]
         );
 
         map.update(cx, |map, cx| {
-            map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
+            map.fold(
+                vec![MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2)],
+                cx,
+            )
         });
         assert_eq!(
-            cx.update(|cx| syntax_chunks(1..4, &map, &theme, cx)),
+            cx.update(|cx| syntax_chunks(DisplayRow(1)..DisplayRow(4), &map, &theme, cx)),
             [
                 ("out".to_string(), Some(Hsla::blue())),
                 ("⋯\n".to_string(), None),
@@ -1636,7 +1678,7 @@ pub mod tests {
         });
 
         assert_eq!(
-            cx.update(|cx| chunks(0..10, &map, &theme, cx)),
+            cx.update(|cx| chunks(DisplayRow(0)..DisplayRow(10), &map, &theme, cx)),
             [
                 ("const ".to_string(), None, None),
                 ("a".to_string(), None, Some(Hsla::blue())),
@@ -1732,45 +1774,57 @@ pub mod tests {
         let map = map.update(cx, |map, cx| map.snapshot(cx));
         assert_eq!(map.text(), "āœ…       α\nβ   \nšŸ€Ī²      γ");
         assert_eq!(
-            map.text_chunks(0).collect::<String>(),
+            map.text_chunks(DisplayRow(0)).collect::<String>(),
             "āœ…       α\nβ   \nšŸ€Ī²      γ"
         );
-        assert_eq!(map.text_chunks(1).collect::<String>(), "β   \nšŸ€Ī²      γ");
-        assert_eq!(map.text_chunks(2).collect::<String>(), "šŸ€Ī²      γ");
+        assert_eq!(
+            map.text_chunks(DisplayRow(1)).collect::<String>(),
+            "β   \nšŸ€Ī²      γ"
+        );
+        assert_eq!(
+            map.text_chunks(DisplayRow(2)).collect::<String>(),
+            "šŸ€Ī²      γ"
+        );
 
-        let point = Point::new(0, "āœ…\t\t".len() as u32);
-        let display_point = DisplayPoint::new(0, "āœ…       ".len() as u32);
+        let point = MultiBufferPoint::new(0, "āœ…\t\t".len() as u32);
+        let display_point = DisplayPoint::new(DisplayRow(0), "āœ…       ".len() as u32);
         assert_eq!(point.to_display_point(&map), display_point);
         assert_eq!(display_point.to_point(&map), point);
 
-        let point = Point::new(1, "β\t".len() as u32);
-        let display_point = DisplayPoint::new(1, "β   ".len() as u32);
+        let point = MultiBufferPoint::new(1, "β\t".len() as u32);
+        let display_point = DisplayPoint::new(DisplayRow(1), "β   ".len() as u32);
         assert_eq!(point.to_display_point(&map), display_point);
         assert_eq!(display_point.to_point(&map), point,);
 
-        let point = Point::new(2, "šŸ€Ī²\t\t".len() as u32);
-        let display_point = DisplayPoint::new(2, "šŸ€Ī²      ".len() as u32);
+        let point = MultiBufferPoint::new(2, "šŸ€Ī²\t\t".len() as u32);
+        let display_point = DisplayPoint::new(DisplayRow(2), "šŸ€Ī²      ".len() as u32);
         assert_eq!(point.to_display_point(&map), display_point);
         assert_eq!(display_point.to_point(&map), point,);
 
         // Display points inside of expanded tabs
         assert_eq!(
-            DisplayPoint::new(0, "āœ…      ".len() as u32).to_point(&map),
-            Point::new(0, "āœ…\t".len() as u32),
+            DisplayPoint::new(DisplayRow(0), "āœ…      ".len() as u32).to_point(&map),
+            MultiBufferPoint::new(0, "āœ…\t".len() as u32),
         );
         assert_eq!(
-            DisplayPoint::new(0, "āœ… ".len() as u32).to_point(&map),
-            Point::new(0, "āœ…".len() as u32),
+            DisplayPoint::new(DisplayRow(0), "āœ… ".len() as u32).to_point(&map),
+            MultiBufferPoint::new(0, "āœ…".len() as u32),
         );
 
         // Clipping display points inside of multi-byte characters
         assert_eq!(
-            map.clip_point(DisplayPoint::new(0, "āœ…".len() as u32 - 1), Left),
-            DisplayPoint::new(0, 0)
+            map.clip_point(
+                DisplayPoint::new(DisplayRow(0), "āœ…".len() as u32 - 1),
+                Left
+            ),
+            DisplayPoint::new(DisplayRow(0), 0)
         );
         assert_eq!(
-            map.clip_point(DisplayPoint::new(0, "āœ…".len() as u32 - 1), Bias::Right),
-            DisplayPoint::new(0, "āœ…".len() as u32)
+            map.clip_point(
+                DisplayPoint::new(DisplayRow(0), "āœ…".len() as u32 - 1),
+                Bias::Right
+            ),
+            DisplayPoint::new(DisplayRow(0), "āœ…".len() as u32)
         );
     }
 
@@ -1785,12 +1839,12 @@ pub mod tests {
         });
         assert_eq!(
             map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
-            DisplayPoint::new(1, 11)
+            DisplayPoint::new(DisplayRow(1), 11)
         )
     }
 
     fn syntax_chunks(
-        rows: Range<u32>,
+        rows: Range<DisplayRow>,
         map: &Model<DisplayMap>,
         theme: &SyntaxTheme,
         cx: &mut AppContext,
@@ -1802,7 +1856,7 @@ pub mod tests {
     }
 
     fn chunks(
-        rows: Range<u32>,
+        rows: Range<DisplayRow>,
         map: &Model<DisplayMap>,
         theme: &SyntaxTheme,
         cx: &mut AppContext,

crates/editor/src/display_map/block_map.rs šŸ”—

@@ -6,7 +6,7 @@ use crate::{EditorStyle, GutterDimensions};
 use collections::{Bound, HashMap, HashSet};
 use gpui::{AnyElement, Pixels, WindowContext};
 use language::{BufferSnapshot, Chunk, Patch, Point};
-use multi_buffer::{Anchor, ExcerptId, ExcerptRange, ToPoint as _};
+use multi_buffer::{Anchor, ExcerptId, ExcerptRange, MultiBufferRow, ToPoint as _};
 use parking_lot::Mutex;
 use std::{
     cell::RefCell,
@@ -50,7 +50,7 @@ pub struct BlockId(usize);
 pub struct BlockPoint(pub Point);
 
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-struct BlockRow(u32);
+pub struct BlockRow(pub(super) u32);
 
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 struct WrapRow(u32);
@@ -163,7 +163,7 @@ pub struct BlockChunks<'a> {
 pub struct BlockBufferRows<'a> {
     transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
     input_buffer_rows: wrap_map::WrapBufferRows<'a>,
-    output_row: u32,
+    output_row: BlockRow,
     started: bool,
 }
 
@@ -357,7 +357,7 @@ impl BlockMap {
                         match block.disposition {
                             BlockDisposition::Above => position.column = 0,
                             BlockDisposition::Below => {
-                                position.column = buffer.line_len(position.row)
+                                position.column = buffer.line_len(MultiBufferRow(position.row))
                             }
                         }
                         let position = wrap_snapshot.make_wrap_point(position, Bias::Left);
@@ -372,7 +372,7 @@ impl BlockMap {
                             (
                                 wrap_snapshot
                                     .make_wrap_point(
-                                        Point::new(excerpt_boundary.row, 0),
+                                        Point::new(excerpt_boundary.row.0, 0),
                                         Bias::Left,
                                     )
                                     .row(),
@@ -633,12 +633,12 @@ impl BlockSnapshot {
         }
     }
 
-    pub fn buffer_rows(&self, start_row: u32) -> BlockBufferRows {
+    pub(super) fn buffer_rows(&self, start_row: BlockRow) -> BlockBufferRows {
         let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
-        cursor.seek(&BlockRow(start_row), Bias::Right, &());
+        cursor.seek(&start_row, Bias::Right, &());
         let (output_start, input_start) = cursor.start();
         let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) {
-            start_row - output_start.0
+            start_row.0 - output_start.0
         } else {
             0
         };
@@ -676,7 +676,7 @@ impl BlockSnapshot {
 
     pub fn max_point(&self) -> BlockPoint {
         let row = self.transforms.summary().output_rows - 1;
-        BlockPoint::new(row, self.line_len(row))
+        BlockPoint::new(row, self.line_len(BlockRow(row)))
     }
 
     pub fn longest_row(&self) -> u32 {
@@ -684,12 +684,12 @@ impl BlockSnapshot {
         self.to_block_point(WrapPoint::new(input_row, 0)).row
     }
 
-    pub fn line_len(&self, row: u32) -> u32 {
+    pub(super) fn line_len(&self, row: BlockRow) -> u32 {
         let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
-        cursor.seek(&BlockRow(row), Bias::Right, &());
+        cursor.seek(&BlockRow(row.0), Bias::Right, &());
         if let Some(transform) = cursor.item() {
             let (output_start, input_start) = cursor.start();
-            let overshoot = row - output_start.0;
+            let overshoot = row.0 - output_start.0;
             if transform.block.is_some() {
                 0
             } else {
@@ -700,9 +700,9 @@ impl BlockSnapshot {
         }
     }
 
-    pub fn is_block_line(&self, row: u32) -> bool {
+    pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
         let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
-        cursor.seek(&BlockRow(row), Bias::Right, &());
+        cursor.seek(&row, Bias::Right, &());
         cursor.item().map_or(false, |t| t.block.is_some())
     }
 
@@ -881,16 +881,16 @@ impl<'a> Iterator for BlockChunks<'a> {
 }
 
 impl<'a> Iterator for BlockBufferRows<'a> {
-    type Item = Option<u32>;
+    type Item = Option<BlockRow>;
 
     fn next(&mut self) -> Option<Self::Item> {
         if self.started {
-            self.output_row += 1;
+            self.output_row.0 += 1;
         } else {
             self.started = true;
         }
 
-        if self.output_row >= self.transforms.end(&()).0 .0 {
+        if self.output_row.0 >= self.transforms.end(&()).0 .0 {
             self.transforms.next(&());
         }
 
@@ -898,7 +898,7 @@ impl<'a> Iterator for BlockBufferRows<'a> {
         if transform.block.is_some() {
             Some(None)
         } else {
-            Some(self.input_buffer_rows.next().unwrap())
+            Some(self.input_buffer_rows.next().unwrap().map(BlockRow))
         }
     }
 }
@@ -1154,7 +1154,10 @@ mod tests {
         );
 
         assert_eq!(
-            snapshot.buffer_rows(0).collect::<Vec<_>>(),
+            snapshot
+                .buffer_rows(BlockRow(0))
+                .map(|row| row.map(|r| r.0))
+                .collect::<Vec<_>>(),
             &[
                 Some(0),
                 None,
@@ -1394,7 +1397,7 @@ mod tests {
                         position.column = 0;
                     }
                     BlockDisposition::Below => {
-                        position.column = buffer_snapshot.line_len(position.row);
+                        position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
                     }
                 };
                 let row = wraps_snapshot.make_wrap_point(position, Bias::Left).row();
@@ -1410,7 +1413,7 @@ mod tests {
             expected_blocks.extend(buffer_snapshot.excerpt_boundaries_in_range(0..).map(
                 |boundary| {
                     let position =
-                        wraps_snapshot.make_wrap_point(Point::new(boundary.row, 0), Bias::Left);
+                        wraps_snapshot.make_wrap_point(Point::new(boundary.row.0, 0), Bias::Left);
                     (
                         position.row(),
                         ExpectedBlock::ExcerptHeader {
@@ -1427,7 +1430,9 @@ mod tests {
             expected_blocks.sort_unstable();
             let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
 
-            let input_buffer_rows = buffer_snapshot.buffer_rows(0).collect::<Vec<_>>();
+            let input_buffer_rows = buffer_snapshot
+                .buffer_rows(MultiBufferRow(0))
+                .collect::<Vec<_>>();
             let mut expected_buffer_rows = Vec::new();
             let mut expected_text = String::new();
             let mut expected_block_positions = Vec::new();
@@ -1498,7 +1503,8 @@ mod tests {
                 );
                 assert_eq!(
                     blocks_snapshot
-                        .buffer_rows(start_row as u32)
+                        .buffer_rows(BlockRow(start_row as u32))
+                        .map(|row| row.map(|r| r.0))
                         .collect::<Vec<_>>(),
                     &expected_buffer_rows[start_row..]
                 );
@@ -1518,7 +1524,7 @@ mod tests {
                 let row = row as u32;
 
                 assert_eq!(
-                    blocks_snapshot.line_len(row),
+                    blocks_snapshot.line_len(BlockRow(row)),
                     line.len() as u32,
                     "invalid line len for row {}",
                     row

crates/editor/src/display_map/fold_map.rs šŸ”—

@@ -4,7 +4,7 @@ use super::{
 };
 use gpui::{ElementId, HighlightStyle, Hsla};
 use language::{Chunk, Edit, Point, TextSummary};
-use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferSnapshot, ToOffset};
+use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToOffset};
 use std::{
     cmp::{self, Ordering},
     iter,
@@ -629,17 +629,17 @@ impl FoldSnapshot {
         cursor.item().map_or(false, |t| t.output_text.is_some())
     }
 
-    pub fn is_line_folded(&self, buffer_row: u32) -> bool {
+    pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
         let mut inlay_point = self
             .inlay_snapshot
-            .to_inlay_point(Point::new(buffer_row, 0));
+            .to_inlay_point(Point::new(buffer_row.0, 0));
         let mut cursor = self.transforms.cursor::<InlayPoint>();
         cursor.seek(&inlay_point, Bias::Right, &());
         loop {
             match cursor.item() {
                 Some(transform) => {
                     let buffer_point = self.inlay_snapshot.to_buffer_point(inlay_point);
-                    if buffer_point.row != buffer_row {
+                    if buffer_point.row != buffer_row.0 {
                         return false;
                     } else if transform.output_text.is_some() {
                         return true;
@@ -1549,7 +1549,7 @@ mod tests {
                 .collect::<HashSet<_>>();
             for row in 0..=buffer_snapshot.max_point().row {
                 assert_eq!(
-                    snapshot.is_line_folded(row),
+                    snapshot.is_line_folded(MultiBufferRow(row)),
                     folded_buffer_rows.contains(&row),
                     "expected buffer row {}{} to be folded",
                     row,

crates/editor/src/display_map/inlay_map.rs šŸ”—

@@ -2,7 +2,9 @@ use crate::{HighlightStyles, InlayId};
 use collections::{BTreeMap, BTreeSet};
 use gpui::HighlightStyle;
 use language::{Chunk, Edit, Point, TextSummary};
-use multi_buffer::{Anchor, MultiBufferChunks, MultiBufferRows, MultiBufferSnapshot, ToOffset};
+use multi_buffer::{
+    Anchor, MultiBufferChunks, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, ToOffset,
+};
 use std::{
     any::TypeId,
     cmp,
@@ -182,7 +184,7 @@ pub struct InlayBufferRows<'a> {
     transforms: Cursor<'a, Transform, (InlayPoint, Point)>,
     buffer_rows: MultiBufferRows<'a>,
     inlay_row: u32,
-    max_buffer_row: u32,
+    max_buffer_row: MultiBufferRow,
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
@@ -375,7 +377,7 @@ impl<'a> InlayBufferRows<'a> {
         self.transforms.seek(&inlay_point, Bias::Left, &());
 
         let mut buffer_point = self.transforms.start().1;
-        let buffer_row = if row == 0 {
+        let buffer_row = MultiBufferRow(if row == 0 {
             0
         } else {
             match self.transforms.item() {
@@ -383,9 +385,9 @@ impl<'a> InlayBufferRows<'a> {
                     buffer_point += inlay_point.0 - self.transforms.start().0 .0;
                     buffer_point.row
                 }
-                _ => cmp::min(buffer_point.row + 1, self.max_buffer_row),
+                _ => cmp::min(buffer_point.row + 1, self.max_buffer_row.0),
             }
-        };
+        });
         self.inlay_row = inlay_point.row();
         self.buffer_rows.seek(buffer_row);
     }
@@ -986,17 +988,17 @@ impl InlaySnapshot {
         let inlay_point = InlayPoint::new(row, 0);
         cursor.seek(&inlay_point, Bias::Left, &());
 
-        let max_buffer_row = self.buffer.max_point().row;
+        let max_buffer_row = MultiBufferRow(self.buffer.max_point().row);
         let mut buffer_point = cursor.start().1;
         let buffer_row = if row == 0 {
-            0
+            MultiBufferRow(0)
         } else {
             match cursor.item() {
                 Some(Transform::Isomorphic(_)) => {
                     buffer_point += inlay_point.0 - cursor.start().0 .0;
-                    buffer_point.row
+                    MultiBufferRow(buffer_point.row)
                 }
-                _ => cmp::min(buffer_point.row + 1, max_buffer_row),
+                _ => cmp::min(MultiBufferRow(buffer_point.row + 1), max_buffer_row),
             }
         };
 

crates/editor/src/editor.rs šŸ”—

@@ -86,18 +86,18 @@ use language::{
     CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
     Point, Selection, SelectionGoal, TransactionId,
 };
-use language::{Runnable, RunnableRange};
+use language::{BufferRow, Runnable, RunnableRange};
 use task::{ResolvedTask, TaskTemplate, TaskVariables};
 
 use hover_links::{HoverLink, HoveredLinkState, InlayHighlight};
 use lsp::{DiagnosticSeverity, LanguageServerId};
 use mouse_context_menu::MouseContextMenu;
 use movement::TextLayoutDetails;
-use multi_buffer::ToOffsetUtf16;
 pub use multi_buffer::{
     Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
     ToPoint,
 };
+use multi_buffer::{MultiBufferPoint, MultiBufferRow, ToOffsetUtf16};
 use ordered_float::OrderedFloat;
 use parking_lot::{Mutex, RwLock};
 use project::project_settings::{GitGutterSetting, ProjectSettings};
@@ -410,7 +410,7 @@ struct RunnableTasks {
 #[derive(Clone)]
 struct ResolvedTasks {
     templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
-    position: text::Point,
+    position: Anchor,
 }
 
 /// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`]
@@ -505,7 +505,7 @@ pub struct Editor {
     >,
     last_bounds: Option<Bounds<Pixels>>,
     expect_bounds_change: Option<Bounds<Pixels>>,
-    tasks: HashMap<(BufferId, u32), (usize, RunnableTasks)>,
+    tasks: HashMap<(BufferId, BufferRow), (usize, RunnableTasks)>,
     tasks_update_task: Option<Task<()>>,
 }
 
@@ -804,7 +804,7 @@ impl ContextMenu {
 
 enum ContextMenuOrigin {
     EditorPoint(DisplayPoint),
-    GutterIndicator(u32),
+    GutterIndicator(DisplayRow),
 }
 
 #[derive(Clone)]
@@ -1299,7 +1299,7 @@ struct CodeActionsMenu {
     buffer: Model<Buffer>,
     selected_item: usize,
     scroll_handle: UniformListScrollHandle,
-    deployed_from_indicator: Option<u32>,
+    deployed_from_indicator: Option<DisplayRow>,
 }
 
 impl CodeActionsMenu {
@@ -2498,7 +2498,8 @@ impl Editor {
         let end_column = cmp::max(tail.column(), goal_column);
         let reversed = start_column < tail.column();
 
-        let selection_ranges = (start_row..=end_row)
+        let selection_ranges = (start_row.0..=end_row.0)
+            .map(DisplayRow)
             .filter_map(|row| {
                 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
                     let start = display_map
@@ -2898,7 +2899,8 @@ impl Editor {
                     .iter()
                     .map(|selection| {
                         let start_point = selection.start.to_point(&buffer);
-                        let mut indent = buffer.indent_size_for_line(start_point.row);
+                        let mut indent =
+                            buffer.indent_size_for_line(MultiBufferRow(start_point.row));
                         indent.len = cmp::min(indent.len, start_point.column);
                         let start = selection.start;
                         let end = selection.end;
@@ -2951,7 +2953,7 @@ impl Editor {
                                 let max_len_of_delimiter =
                                     delimiters.iter().map(|delimiter| delimiter.len()).max()?;
                                 let (snapshot, range) =
-                                    buffer.buffer_line_for_row(start_point.row)?;
+                                    buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
 
                                 let mut index_of_first_non_whitespace = 0;
                                 let comment_candidate = snapshot
@@ -3015,7 +3017,7 @@ impl Editor {
                     let mut cursor = new_selection.end.to_point(&buffer);
                     if extra_newline_inserted {
                         cursor.row -= 1;
-                        cursor.column = buffer.line_len(cursor.row);
+                        cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
                     }
                     new_selection.map(|_| cursor)
                 })
@@ -3075,7 +3077,7 @@ impl Editor {
                         IndentKind::Space => " ".repeat(indent.len as usize),
                         IndentKind::Tab => "\t".repeat(indent.len as usize),
                     };
-                    let point = Point::new(row, 0);
+                    let point = Point::new(row.0, 0);
                     indent_edits.push((point..point, text));
                 }
             }
@@ -3135,7 +3137,7 @@ impl Editor {
                         IndentKind::Space => " ".repeat(indent.len as usize),
                         IndentKind::Tab => "\t".repeat(indent.len as usize),
                     };
-                    let point = Point::new(row, 0);
+                    let point = Point::new(row.0, 0);
                     indent_edits.push((point..point, text));
                 }
             }
@@ -3852,17 +3854,13 @@ impl Editor {
 
             let spawned_test_task = this.update(&mut cx, |this, cx| {
                 if this.focus_handle.is_focused(cx) {
-                    let display_row = action
+                    let multibuffer_point = action
                         .deployed_from_indicator
-                        .map(|row| {
-                            DisplayPoint::new(row, 0)
-                                .to_point(&snapshot.display_snapshot)
-                                .row
-                        })
-                        .unwrap_or_else(|| this.selections.newest::<Point>(cx).head().row);
+                        .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
+                        .unwrap_or_else(|| this.selections.newest::<Point>(cx).head());
                     let (buffer, buffer_row) = snapshot
                         .buffer_snapshot
-                        .buffer_line_for_row(display_row)
+                        .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
                         .and_then(|(buffer_snapshot, range)| {
                             this.buffer
                                 .read(cx)
@@ -3934,7 +3932,9 @@ impl Editor {
                                         .map(|task| (kind.clone(), task))
                                 })
                                 .collect(),
-                            position: Point::new(buffer_row, tasks.1.column),
+                            position: snapshot
+                                .buffer_snapshot
+                                .anchor_before(Point::new(multibuffer_point.row, tasks.1.column)),
                         })
                     });
                     let spawn_straight_away = tasks
@@ -4496,7 +4496,7 @@ impl Editor {
     fn render_code_actions_indicator(
         &self,
         _style: &EditorStyle,
-        row: u32,
+        row: DisplayRow,
         is_active: bool,
         cx: &mut ViewContext<Self>,
     ) -> Option<IconButton> {
@@ -4525,7 +4525,7 @@ impl Editor {
         self.tasks.clear()
     }
 
-    fn insert_tasks(&mut self, key: (BufferId, u32), value: (usize, RunnableTasks)) {
+    fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: (usize, RunnableTasks)) {
         if let Some(_) = self.tasks.insert(key, value) {
             // This case should hopefully be rare, but just in case...
             log::error!("multiple different run targets found on a single line, only the last target will be rendered")
@@ -4536,7 +4536,7 @@ impl Editor {
         &self,
         _style: &EditorStyle,
         is_active: bool,
-        row: u32,
+        row: DisplayRow,
         cx: &mut ViewContext<Self>,
     ) -> IconButton {
         IconButton::new("code_actions_indicator", ui::IconName::Play)
@@ -4556,7 +4556,7 @@ impl Editor {
 
     pub fn render_fold_indicators(
         &mut self,
-        fold_data: Vec<Option<(FoldStatus, u32, bool)>>,
+        fold_data: Vec<Option<(FoldStatus, MultiBufferRow, bool)>>,
         _style: &EditorStyle,
         gutter_hovered: bool,
         _line_height: Pixels,
@@ -4775,7 +4775,7 @@ impl Editor {
     pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
         self.transact(cx, |this, cx| {
             this.select_autoclose_pair(cx);
-            let mut selections = this.selections.all::<Point>(cx);
+            let mut selections = this.selections.all::<MultiBufferPoint>(cx);
             if !this.selections.line_mode {
                 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
                 for selection in &mut selections {
@@ -4786,7 +4786,7 @@ impl Editor {
                                 .to_point(&display_map);
                         if let Some((buffer, line_buffer_range)) = display_map
                             .buffer_snapshot
-                            .buffer_line_for_row(old_head.row)
+                            .buffer_line_for_row(MultiBufferRow(old_head.row))
                         {
                             let indent_size =
                                 buffer.indent_size_for_line(line_buffer_range.start.row);
@@ -4800,7 +4800,7 @@ impl Editor {
                                 let indent_len = indent_len.get();
                                 new_head = cmp::min(
                                     new_head,
-                                    Point::new(
+                                    MultiBufferPoint::new(
                                         old_head.row,
                                         ((old_head.column - 1) / indent_len) * indent_len,
                                     ),
@@ -4875,8 +4875,10 @@ impl Editor {
             // If the selection is empty and the cursor is in the leading whitespace before the
             // suggested indentation, then auto-indent the line.
             let cursor = selection.head();
-            let current_indent = snapshot.indent_size_for_line(cursor.row);
-            if let Some(suggested_indent) = suggested_indents.get(&cursor.row).copied() {
+            let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
+            if let Some(suggested_indent) =
+                suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
+            {
                 if cursor.column < suggested_indent.len
                     && cursor.column <= current_indent.len
                     && current_indent.len <= suggested_indent.len
@@ -4996,7 +4998,7 @@ impl Editor {
         let mut delta_for_end_row = 0;
         let has_multiple_rows = start_row + 1 != end_row;
         for row in start_row..end_row {
-            let current_indent = snapshot.indent_size_for_line(row);
+            let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
             let indent_delta = match (current_indent.kind, indent_kind) {
                 (IndentKind::Space, IndentKind::Space) => {
                     let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
@@ -5054,11 +5056,11 @@ impl Editor {
                 // previous selection.
                 if let Some(last_row) = last_outdent {
                     if last_row == rows.start {
-                        rows.start += 1;
+                        rows.start = rows.start.next_row();
                     }
                 }
                 let has_multiple_rows = rows.len() > 1;
-                for row in rows {
+                for row in rows.iter_rows() {
                     let indent_size = snapshot.indent_size_for_line(row);
                     if indent_size.len > 0 {
                         let deletion_len = match indent_size.kind {
@@ -5080,8 +5082,9 @@ impl Editor {
                         } else {
                             selection.start.column - deletion_len
                         };
-                        deletion_ranges
-                            .push(Point::new(row, start)..Point::new(row, start + deletion_len));
+                        deletion_ranges.push(
+                            Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
+                        );
                         last_outdent = Some(row);
                     }
                 }
@@ -5127,23 +5130,23 @@ impl Editor {
             }
 
             let buffer = &display_map.buffer_snapshot;
-            let mut edit_start = Point::new(rows.start, 0).to_offset(buffer);
+            let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
             let edit_end;
             let cursor_buffer_row;
-            if buffer.max_point().row >= rows.end {
+            if buffer.max_point().row >= rows.end.0 {
                 // If there's a line after the range, delete the \n from the end of the row range
                 // and position the cursor on the next line.
-                edit_end = Point::new(rows.end, 0).to_offset(buffer);
+                edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
                 cursor_buffer_row = rows.end;
             } else {
                 // If there isn't a line after the range, delete the \n from the line before the
                 // start of the row range and position the cursor there.
                 edit_start = edit_start.saturating_sub(1);
                 edit_end = buffer.len();
-                cursor_buffer_row = rows.start.saturating_sub(1);
+                cursor_buffer_row = rows.start.previous_row();
             }
 
-            let mut cursor = Point::new(cursor_buffer_row, 0).to_display_point(&display_map);
+            let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
             *cursor.column_mut() =
                 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
 
@@ -5190,13 +5193,13 @@ impl Editor {
         if self.read_only(cx) {
             return;
         }
-        let mut row_ranges = Vec::<Range<u32>>::new();
+        let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
         for selection in self.selections.all::<Point>(cx) {
-            let start = selection.start.row;
+            let start = MultiBufferRow(selection.start.row);
             let end = if selection.start.row == selection.end.row {
-                selection.start.row + 1
+                MultiBufferRow(selection.start.row + 1)
             } else {
-                selection.end.row
+                MultiBufferRow(selection.end.row)
             };
 
             if let Some(last_row_range) = row_ranges.last_mut() {
@@ -5212,20 +5215,21 @@ impl Editor {
         let mut cursor_positions = Vec::new();
         for row_range in &row_ranges {
             let anchor = snapshot.anchor_before(Point::new(
-                row_range.end - 1,
-                snapshot.line_len(row_range.end - 1),
+                row_range.end.previous_row().0,
+                snapshot.line_len(row_range.end.previous_row()),
             ));
             cursor_positions.push(anchor..anchor);
         }
 
         self.transact(cx, |this, cx| {
             for row_range in row_ranges.into_iter().rev() {
-                for row in row_range.rev() {
-                    let end_of_line = Point::new(row, snapshot.line_len(row));
-                    let indent = snapshot.indent_size_for_line(row + 1);
-                    let start_of_next_line = Point::new(row + 1, indent.len);
+                for row in row_range.iter_rows().rev() {
+                    let end_of_line = Point::new(row.0, snapshot.line_len(row));
+                    let next_line_row = row.next_row();
+                    let indent = snapshot.indent_size_for_line(next_line_row);
+                    let start_of_next_line = Point::new(next_line_row.0, indent.len);
 
-                    let replace = if snapshot.line_len(row + 1) > indent.len {
+                    let replace = if snapshot.line_len(next_line_row) > indent.len {
                         " "
                     } else {
                         ""
@@ -5342,7 +5346,7 @@ impl Editor {
     fn prepare_revert_change(
         revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
         multi_buffer: &MultiBuffer,
-        hunk: &DiffHunk<u32>,
+        hunk: &DiffHunk<MultiBufferRow>,
         cx: &mut AppContext,
     ) -> Option<()> {
         let buffer = multi_buffer.buffer(hunk.buffer_id)?;
@@ -5396,8 +5400,11 @@ impl Editor {
                 &mut selections,
             );
 
-            let start_point = Point::new(start_row, 0);
-            let end_point = Point::new(end_row - 1, buffer.line_len(end_row - 1));
+            let start_point = Point::new(start_row.0, 0);
+            let end_point = Point::new(
+                end_row.previous_row().0,
+                buffer.line_len(end_row.previous_row()),
+            );
             let text = buffer
                 .text_for_range(start_point..end_point)
                 .collect::<String>();
@@ -5411,8 +5418,9 @@ impl Editor {
             edits.push((start_point..end_point, lines.join("\n")));
 
             // Selections must change based on added and removed line count
-            let start_row = start_point.row + added_lines as u32 - removed_lines as u32;
-            let end_row = start_row + lines_after.saturating_sub(1) as u32;
+            let start_row =
+                MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
+            let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
             new_selections.push(Selection {
                 id: selection.id,
                 start: start_row,
@@ -5438,8 +5446,8 @@ impl Editor {
             let new_selections = new_selections
                 .iter()
                 .map(|s| {
-                    let start_point = Point::new(s.start, 0);
-                    let end_point = Point::new(s.end, buffer.line_len(s.end));
+                    let start_point = Point::new(s.start.0, 0);
+                    let end_point = Point::new(s.end.0, buffer.line_len(s.end));
                     Selection {
                         id: s.id,
                         start: buffer.point_to_offset(start_point),
@@ -5602,14 +5610,17 @@ impl Editor {
 
             // Copy the text from the selected row region and splice it either at the start
             // or end of the region.
-            let start = Point::new(rows.start, 0);
-            let end = Point::new(rows.end - 1, buffer.line_len(rows.end - 1));
+            let start = Point::new(rows.start.0, 0);
+            let end = Point::new(
+                rows.end.previous_row().0,
+                buffer.line_len(rows.end.previous_row()),
+            );
             let text = buffer
                 .text_for_range(start..end)
                 .chain(Some("\n"))
                 .collect::<String>();
             let insert_location = if upwards {
-                Point::new(rows.end, 0)
+                Point::new(rows.end.0, 0)
             } else {
                 start
             };
@@ -5656,11 +5667,17 @@ impl Editor {
             );
 
             // Move the text spanned by the row range to be before the line preceding the row range
-            if start_row > 0 {
-                let range_to_move = Point::new(start_row - 1, buffer.line_len(start_row - 1))
-                    ..Point::new(end_row - 1, buffer.line_len(end_row - 1));
+            if start_row.0 > 0 {
+                let range_to_move = Point::new(
+                    start_row.previous_row().0,
+                    buffer.line_len(start_row.previous_row()),
+                )
+                    ..Point::new(
+                        end_row.previous_row().0,
+                        buffer.line_len(end_row.previous_row()),
+                    );
                 let insertion_point = display_map
-                    .prev_line_boundary(Point::new(start_row - 1, 0))
+                    .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
                     .0;
 
                 // Don't move lines across excerpts
@@ -5754,9 +5771,12 @@ impl Editor {
             );
 
             // Move the text spanned by the row range to be after the last line of the row range
-            if end_row <= buffer.max_point().row {
-                let range_to_move = Point::new(start_row, 0)..Point::new(end_row, 0);
-                let insertion_point = display_map.next_line_boundary(Point::new(end_row, 0)).0;
+            if end_row.0 <= buffer.max_point().row {
+                let range_to_move =
+                    MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
+                let insertion_point = display_map
+                    .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
+                    .0;
 
                 // Don't move lines across excerpt boundaries
                 if buffer
@@ -5906,7 +5926,9 @@ impl Editor {
                 clipboard_selections.push(ClipboardSelection {
                     len,
                     is_entire_line,
-                    first_line_indent: buffer.indent_size_for_line(selection.start.row).len,
+                    first_line_indent: buffer
+                        .indent_size_for_line(MultiBufferRow(selection.start.row))
+                        .len,
                 });
             }
         }
@@ -5950,7 +5972,7 @@ impl Editor {
                 clipboard_selections.push(ClipboardSelection {
                     len,
                     is_entire_line,
-                    first_line_indent: buffer.indent_size_for_line(start.row).len,
+                    first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
                 });
             }
         }
@@ -6847,8 +6869,8 @@ impl Editor {
         let max_point = display_map.buffer_snapshot.max_point();
         for selection in &mut selections {
             let rows = selection.spanned_rows(true, &display_map);
-            selection.start = Point::new(rows.start, 0);
-            selection.end = cmp::min(max_point, Point::new(rows.end, 0));
+            selection.start = Point::new(rows.start.0, 0);
+            selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
             selection.reversed = false;
         }
         self.change_selections(Some(Autoscroll::fit()), cx, |s| {
@@ -6868,7 +6890,7 @@ impl Editor {
             let buffer = self.buffer.read(cx).read(cx);
             for selection in selections {
                 for row in selection.start.row..selection.end.row {
-                    let cursor = Point::new(row, buffer.line_len(row));
+                    let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
                     new_selection_ranges.push(cursor..cursor);
                 }
                 new_selection_ranges.push(selection.end..selection.end);
@@ -6903,10 +6925,10 @@ impl Editor {
 
             selections.clear();
             let mut stack = Vec::new();
-            for row in range.start.row()..=range.end.row() {
+            for row in range.start.row().0..=range.end.row().0 {
                 if let Some(selection) = self.selections.build_columnar_selection(
                     &display_map,
-                    row,
+                    DisplayRow(row),
                     &positions,
                     oldest_selection.reversed,
                     &text_layout_details,
@@ -6927,7 +6949,7 @@ impl Editor {
         let mut new_selections = Vec::new();
         if above == state.above {
             let end_row = if above {
-                0
+                DisplayRow(0)
             } else {
                 display_map.max_point().row()
             };
@@ -6950,9 +6972,9 @@ impl Editor {
 
                     while row != end_row {
                         if above {
-                            row -= 1;
+                            row.0 -= 1;
                         } else {
-                            row += 1;
+                            row.0 += 1;
                         }
 
                         if let Some(new_selection) = self.selections.build_columnar_selection(
@@ -7386,7 +7408,7 @@ impl Editor {
     pub fn toggle_comments(&mut self, action: &ToggleComments, cx: &mut ViewContext<Self>) {
         let text_layout_details = &self.text_layout_details(cx);
         self.transact(cx, |this, cx| {
-            let mut selections = this.selections.all::<Point>(cx);
+            let mut selections = this.selections.all::<MultiBufferPoint>(cx);
             let mut edits = Vec::new();
             let mut selection_edit_ranges = Vec::new();
             let mut last_toggled_row = None;
@@ -7396,11 +7418,11 @@ impl Editor {
 
             fn comment_prefix_range(
                 snapshot: &MultiBufferSnapshot,
-                row: u32,
+                row: MultiBufferRow,
                 comment_prefix: &str,
                 comment_prefix_whitespace: &str,
             ) -> Range<Point> {
-                let start = Point::new(row, snapshot.indent_size_for_line(row).len);
+                let start = Point::new(row.0, snapshot.indent_size_for_line(row).len);
 
                 let mut line_bytes = snapshot
                     .bytes_in_range(start..snapshot.max_point())
@@ -7431,11 +7453,11 @@ impl Editor {
 
             fn comment_suffix_range(
                 snapshot: &MultiBufferSnapshot,
-                row: u32,
+                row: MultiBufferRow,
                 comment_suffix: &str,
                 comment_suffix_has_leading_space: bool,
             ) -> Range<Point> {
-                let end = Point::new(row, snapshot.line_len(row));
+                let end = Point::new(row.0, snapshot.line_len(row));
                 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
 
                 let mut line_end_bytes = snapshot
@@ -7464,7 +7486,9 @@ impl Editor {
 
             // TODO: Handle selections that cross excerpts
             for selection in &mut selections {
-                let start_column = snapshot.indent_size_for_line(selection.start.row).len;
+                let start_column = snapshot
+                    .indent_size_for_line(MultiBufferRow(selection.start.row))
+                    .len;
                 let language = if let Some(language) =
                     snapshot.language_scope_at(Point::new(selection.start.row, start_column))
                 {
@@ -7477,15 +7501,15 @@ impl Editor {
 
                 // If multiple selections contain a given row, avoid processing that
                 // row more than once.
-                let mut start_row = selection.start.row;
+                let mut start_row = MultiBufferRow(selection.start.row);
                 if last_toggled_row == Some(start_row) {
-                    start_row += 1;
+                    start_row = start_row.next_row();
                 }
                 let end_row =
                     if selection.end.row > selection.start.row && selection.end.column == 0 {
-                        selection.end.row - 1
+                        MultiBufferRow(selection.end.row - 1)
                     } else {
-                        selection.end.row
+                        MultiBufferRow(selection.end.row)
                     };
                 last_toggled_row = Some(end_row);
 
@@ -7506,7 +7530,8 @@ impl Editor {
 
                     let mut all_selection_lines_are_comments = true;
 
-                    for row in start_row..=end_row {
+                    for row in start_row.0..=end_row.0 {
+                        let row = MultiBufferRow(row);
                         if start_row < end_row && snapshot.is_line_blank(row) {
                             continue;
                         }
@@ -7596,7 +7621,7 @@ impl Editor {
             let snapshot = this.buffer.read(cx).read(cx);
             for selection in &mut selections {
                 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
-                    match row.cmp(&selection.end.row) {
+                    match row.cmp(&MultiBufferRow(selection.end.row)) {
                         Ordering::Less => {
                             suffixes_inserted.next();
                             continue;
@@ -7782,7 +7807,7 @@ impl Editor {
 
                 let row = snapshot
                     .buffer_snapshot
-                    .buffer_line_for_row(point.row)?
+                    .buffer_line_for_row(MultiBufferRow(point.row))?
                     .1
                     .start
                     .row;
@@ -8060,9 +8085,9 @@ impl Editor {
             &snapshot,
             selection.head(),
             false,
-            snapshot
-                .buffer_snapshot
-                .git_diff_hunks_in_range((selection.head().row + 1)..u32::MAX),
+            snapshot.buffer_snapshot.git_diff_hunks_in_range(
+                MultiBufferRow(selection.head().row + 1)..MultiBufferRow::MAX,
+            ),
             cx,
         ) {
             let wrapped_point = Point::zero();
@@ -8070,9 +8095,9 @@ impl Editor {
                 &snapshot,
                 wrapped_point,
                 true,
-                snapshot
-                    .buffer_snapshot
-                    .git_diff_hunks_in_range((wrapped_point.row + 1)..u32::MAX),
+                snapshot.buffer_snapshot.git_diff_hunks_in_range(
+                    MultiBufferRow(wrapped_point.row + 1)..MultiBufferRow::MAX,
+                ),
                 cx,
             );
         }
@@ -8088,9 +8113,9 @@ impl Editor {
             &snapshot,
             selection.head(),
             false,
-            snapshot
-                .buffer_snapshot
-                .git_diff_hunks_in_range_rev(0..selection.head().row),
+            snapshot.buffer_snapshot.git_diff_hunks_in_range_rev(
+                MultiBufferRow(0)..MultiBufferRow(selection.head().row),
+            ),
             cx,
         ) {
             let wrapped_point = snapshot.buffer_snapshot.max_point();
@@ -8098,9 +8123,9 @@ impl Editor {
                 &snapshot,
                 wrapped_point,
                 true,
-                snapshot
-                    .buffer_snapshot
-                    .git_diff_hunks_in_range_rev(0..wrapped_point.row),
+                snapshot.buffer_snapshot.git_diff_hunks_in_range_rev(
+                    MultiBufferRow(0)..MultiBufferRow(wrapped_point.row),
+                ),
                 cx,
             );
         }
@@ -8111,7 +8136,7 @@ impl Editor {
         snapshot: &DisplaySnapshot,
         initial_point: Point,
         is_wrapped: bool,
-        hunks: impl Iterator<Item = DiffHunk<u32>>,
+        hunks: impl Iterator<Item = DiffHunk<MultiBufferRow>>,
         cx: &mut ViewContext<Editor>,
     ) -> bool {
         let display_point = initial_point.to_display_point(snapshot);
@@ -8972,11 +8997,11 @@ impl Editor {
             let mut primary_message = None;
             let mut group_end = Point::zero();
             let diagnostic_group = buffer
-                .diagnostic_group::<Point>(group_id)
+                .diagnostic_group::<MultiBufferPoint>(group_id)
                 .filter_map(|entry| {
-                    if snapshot.is_line_folded(entry.range.start.row)
+                    if snapshot.is_line_folded(MultiBufferRow(entry.range.start.row))
                         && (entry.range.start.row == entry.range.end.row
-                            || snapshot.is_line_folded(entry.range.end.row))
+                            || snapshot.is_line_folded(MultiBufferRow(entry.range.end.row)))
                     {
                         return None;
                     }
@@ -9117,7 +9142,7 @@ impl Editor {
             let buffer_start_row = range.start.row;
 
             for row in (0..=range.end.row).rev() {
-                let fold_range = display_map.foldable_range(row);
+                let fold_range = display_map.foldable_range(MultiBufferRow(row));
 
                 if let Some(fold_range) = fold_range {
                     if fold_range.end.row >= buffer_start_row {
@@ -9159,7 +9184,7 @@ impl Editor {
                 let mut start = range.start.to_point(&display_map);
                 let mut end = range.end.to_point(&display_map);
                 start.column = 0;
-                end.column = buffer.line_len(end.row);
+                end.column = buffer.line_len(MultiBufferRow(end.row));
                 start..end
             })
             .collect::<Vec<_>>();
@@ -9170,9 +9195,9 @@ impl Editor {
     pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext<Self>) {
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
 
-        let intersection_range = Point::new(unfold_at.buffer_row, 0)
+        let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
             ..Point::new(
-                unfold_at.buffer_row,
+                unfold_at.buffer_row.0,
                 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
             );
 
@@ -9192,7 +9217,12 @@ impl Editor {
         let ranges = selections.into_iter().map(|s| {
             if line_mode {
                 let start = Point::new(s.start.row, 0);
-                let end = Point::new(s.end.row, display_map.buffer_snapshot.line_len(s.end.row));
+                let end = Point::new(
+                    s.end.row,
+                    display_map
+                        .buffer_snapshot
+                        .line_len(MultiBufferRow(s.end.row)),
+                );
                 start..end
             } else {
                 s.start..s.end
@@ -9330,7 +9360,7 @@ impl Editor {
         }
     }
 
-    pub fn longest_row(&self, cx: &mut AppContext) -> u32 {
+    pub fn longest_row(&self, cx: &mut AppContext) -> DisplayRow {
         self.display_map
             .update(cx, |map, cx| map.snapshot(cx))
             .longest_row()
@@ -9595,7 +9625,7 @@ impl Editor {
         let cursor_anchor = self.selections.newest_anchor().head();
 
         let snapshot = self.buffer.read(cx).snapshot(cx);
-        let buffer_row = cursor_anchor.to_point(&snapshot).row;
+        let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
 
         snapshot.line_len(buffer_row) == 0
     }
@@ -9776,7 +9806,7 @@ impl Editor {
         &mut self,
         exclude_highlights: HashSet<TypeId>,
         cx: &mut WindowContext,
-    ) -> BTreeMap<u32, Hsla> {
+    ) -> BTreeMap<DisplayRow, Hsla> {
         let snapshot = self.snapshot(cx);
         let mut used_highlight_orders = HashMap::default();
         self.highlighted_rows
@@ -9784,21 +9814,21 @@ impl Editor {
             .filter(|(type_id, _)| !exclude_highlights.contains(type_id))
             .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
             .fold(
-                BTreeMap::<u32, Hsla>::new(),
+                BTreeMap::<DisplayRow, Hsla>::new(),
                 |mut unique_rows, (highlight_order, anchor_range, hsla)| {
                     let start_row = anchor_range.start().to_display_point(&snapshot).row();
                     let end_row = anchor_range.end().to_display_point(&snapshot).row();
-                    for row in start_row..=end_row {
+                    for row in start_row.0..=end_row.0 {
                         let used_index =
                             used_highlight_orders.entry(row).or_insert(*highlight_order);
                         if highlight_order >= used_index {
                             *used_index = *highlight_order;
                             match hsla {
                                 Some(hsla) => {
-                                    unique_rows.insert(row, *hsla);
+                                    unique_rows.insert(DisplayRow(row), *hsla);
                                 }
                                 None => {
-                                    unique_rows.remove(&row);
+                                    unique_rows.remove(&DisplayRow(row));
                                 }
                             }
                         }
@@ -10640,15 +10670,15 @@ impl Editor {
 fn hunks_for_selections(
     multi_buffer_snapshot: &MultiBufferSnapshot,
     selections: &[Selection<Anchor>],
-) -> Vec<DiffHunk<u32>> {
+) -> Vec<DiffHunk<MultiBufferRow>> {
     let mut hunks = Vec::with_capacity(selections.len());
     let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
         HashMap::default();
-    let display_rows_for_selections = selections.iter().map(|selection| {
+    let buffer_rows_for_selections = selections.iter().map(|selection| {
         let head = selection.head();
         let tail = selection.tail();
-        let start = tail.to_point(&multi_buffer_snapshot).row;
-        let end = head.to_point(&multi_buffer_snapshot).row;
+        let start = MultiBufferRow(tail.to_point(&multi_buffer_snapshot).row);
+        let end = MultiBufferRow(head.to_point(&multi_buffer_snapshot).row);
         if start > end {
             end..start
         } else {
@@ -10656,12 +10686,13 @@ fn hunks_for_selections(
         }
     });
 
-    for selected_multi_buffer_rows in display_rows_for_selections {
-        let query_rows = selected_multi_buffer_rows.start..selected_multi_buffer_rows.end + 1;
+    for selected_multi_buffer_rows in buffer_rows_for_selections {
+        let query_rows =
+            selected_multi_buffer_rows.start..selected_multi_buffer_rows.end.next_row();
         for hunk in multi_buffer_snapshot.git_diff_hunks_in_range(query_rows.clone()) {
             // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
             // when the caret is just above or just below the deleted hunk.
-            let allow_adjacent = hunk.status() == DiffHunkStatus::Removed;
+            let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed;
             let related_to_selection = if allow_adjacent {
                 hunk.associated_range.overlaps(&query_rows)
                     || hunk.associated_range.start == query_rows.end
@@ -10798,13 +10829,13 @@ fn consume_contiguous_rows(
     selection: &Selection<Point>,
     display_map: &DisplaySnapshot,
     selections: &mut std::iter::Peekable<std::slice::Iter<Selection<Point>>>,
-) -> (u32, u32) {
+) -> (MultiBufferRow, MultiBufferRow) {
     contiguous_row_selections.push(selection.clone());
-    let start_row = selection.start.row;
+    let start_row = MultiBufferRow(selection.start.row);
     let mut end_row = ending_row(selection, display_map);
 
     while let Some(next_selection) = selections.peek() {
-        if next_selection.start.row <= end_row {
+        if next_selection.start.row <= end_row.0 {
             end_row = ending_row(next_selection, display_map);
             contiguous_row_selections.push(selections.next().unwrap().clone());
         } else {
@@ -10814,11 +10845,11 @@ fn consume_contiguous_rows(
     (start_row, end_row)
 }
 
-fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> u32 {
+fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
     if next_selection.end.column > 0 || next_selection.is_empty() {
-        display_map.next_line_boundary(next_selection.end).0.row + 1
+        MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
     } else {
-        next_selection.end.row
+        MultiBufferRow(next_selection.end.row)
     }
 }
 
@@ -11300,7 +11331,7 @@ impl ViewInputHandler for Editor {
         let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
         let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
             + self.gutter_dimensions.width;
-        let y = line_height * (start.row() as f32 - scroll_position.y);
+        let y = line_height * (start.row().as_f32() - scroll_position.y);
 
         Some(Bounds {
             origin: element_bounds.origin + point(x, y),
@@ -11311,8 +11342,11 @@ impl ViewInputHandler for Editor {
 
 trait SelectionExt {
     fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
-    fn spanned_rows(&self, include_end_if_at_line_start: bool, map: &DisplaySnapshot)
-        -> Range<u32>;
+    fn spanned_rows(
+        &self,
+        include_end_if_at_line_start: bool,
+        map: &DisplaySnapshot,
+    ) -> Range<MultiBufferRow>;
 }
 
 impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
@@ -11336,7 +11370,7 @@ impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
         &self,
         include_end_if_at_line_start: bool,
         map: &DisplaySnapshot,
-    ) -> Range<u32> {
+    ) -> Range<MultiBufferRow> {
         let start = self.start.to_point(&map.buffer_snapshot);
         let mut end = self.end.to_point(&map.buffer_snapshot);
         if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
@@ -11345,7 +11379,7 @@ impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
 
         let buffer_start = map.prev_line_boundary(start).0;
         let buffer_end = map.next_line_boundary(end).0;
-        buffer_start.row..buffer_end.row + 1
+        MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
     }
 }
 
@@ -11609,3 +11643,91 @@ impl<T: ToOffset> RangeToAnchorExt for Range<T> {
         }
     }
 }
+
+pub trait RowExt {
+    fn as_f32(&self) -> f32;
+
+    fn next_row(&self) -> Self;
+
+    fn previous_row(&self) -> Self;
+
+    fn minus(&self, other: Self) -> u32;
+}
+
+impl RowExt for DisplayRow {
+    fn as_f32(&self) -> f32 {
+        self.0 as f32
+    }
+
+    fn next_row(&self) -> Self {
+        Self(self.0 + 1)
+    }
+
+    fn previous_row(&self) -> Self {
+        Self(self.0.saturating_sub(1))
+    }
+
+    fn minus(&self, other: Self) -> u32 {
+        self.0 - other.0
+    }
+}
+
+impl RowExt for MultiBufferRow {
+    fn as_f32(&self) -> f32 {
+        self.0 as f32
+    }
+
+    fn next_row(&self) -> Self {
+        Self(self.0 + 1)
+    }
+
+    fn previous_row(&self) -> Self {
+        Self(self.0.saturating_sub(1))
+    }
+
+    fn minus(&self, other: Self) -> u32 {
+        self.0 - other.0
+    }
+}
+
+trait RowRangeExt {
+    type Row;
+
+    fn len(&self) -> usize;
+
+    fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
+}
+
+impl RowRangeExt for Range<MultiBufferRow> {
+    type Row = MultiBufferRow;
+
+    fn len(&self) -> usize {
+        (self.end.0 - self.start.0) as usize
+    }
+
+    fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
+        (self.start.0..self.end.0).map(MultiBufferRow)
+    }
+}
+
+impl RowRangeExt for Range<DisplayRow> {
+    type Row = DisplayRow;
+
+    fn len(&self) -> usize {
+        (self.end.0 - self.start.0) as usize
+    }
+
+    fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
+        (self.start.0..self.end.0).map(DisplayRow)
+    }
+}
+
+fn hunk_status(hunk: &DiffHunk<MultiBufferRow>) -> DiffHunkStatus {
+    if hunk.diff_base_byte_range.is_empty() {
+        DiffHunkStatus::Added
+    } else if hunk.associated_range.is_empty() {
+        DiffHunkStatus::Removed
+    } else {
+        DiffHunkStatus::Modified
+    }
+}

crates/editor/src/editor_tests.rs šŸ”—

@@ -333,18 +333,18 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
     });
 
     _ = editor.update(cx, |view, cx| {
-        view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
+        view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
     });
     assert_eq!(
         editor
             .update(cx, |view, cx| view.selections.display_ranges(cx))
             .unwrap(),
-        [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
+        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
     );
 
     _ = editor.update(cx, |view, cx| {
         view.update_selection(
-            DisplayPoint::new(3, 3),
+            DisplayPoint::new(DisplayRow(3), 3),
             0,
             gpui::Point::<f32>::default(),
             cx,
@@ -355,12 +355,12 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
         editor
             .update(cx, |view, cx| view.selections.display_ranges(cx))
             .unwrap(),
-        [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
     );
 
     _ = editor.update(cx, |view, cx| {
         view.update_selection(
-            DisplayPoint::new(1, 1),
+            DisplayPoint::new(DisplayRow(1), 1),
             0,
             gpui::Point::<f32>::default(),
             cx,
@@ -371,13 +371,13 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
         editor
             .update(cx, |view, cx| view.selections.display_ranges(cx))
             .unwrap(),
-        [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
+        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
     );
 
     _ = editor.update(cx, |view, cx| {
         view.end_selection(cx);
         view.update_selection(
-            DisplayPoint::new(3, 3),
+            DisplayPoint::new(DisplayRow(3), 3),
             0,
             gpui::Point::<f32>::default(),
             cx,
@@ -388,13 +388,13 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
         editor
             .update(cx, |view, cx| view.selections.display_ranges(cx))
             .unwrap(),
-        [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
+        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
     );
 
     _ = editor.update(cx, |view, cx| {
-        view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
+        view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
         view.update_selection(
-            DisplayPoint::new(0, 0),
+            DisplayPoint::new(DisplayRow(0), 0),
             0,
             gpui::Point::<f32>::default(),
             cx,
@@ -406,8 +406,8 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
             .update(cx, |view, cx| view.selections.display_ranges(cx))
             .unwrap(),
         [
-            DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
-            DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
+            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
+            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
         ]
     );
 
@@ -419,7 +419,7 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
         editor
             .update(cx, |view, cx| view.selections.display_ranges(cx))
             .unwrap(),
-        [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
+        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
     );
 }
 
@@ -433,37 +433,37 @@ fn test_canceling_pending_selection(cx: &mut TestAppContext) {
     });
 
     _ = view.update(cx, |view, cx| {
-        view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
+        view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
+            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
         );
     });
 
     _ = view.update(cx, |view, cx| {
         view.update_selection(
-            DisplayPoint::new(3, 3),
+            DisplayPoint::new(DisplayRow(3), 3),
             0,
             gpui::Point::<f32>::default(),
             cx,
         );
         assert_eq!(
             view.selections.display_ranges(cx),
-            [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
         );
     });
 
     _ = view.update(cx, |view, cx| {
         view.cancel(&Cancel, cx);
         view.update_selection(
-            DisplayPoint::new(1, 1),
+            DisplayPoint::new(DisplayRow(1), 1),
             0,
             gpui::Point::<f32>::default(),
             cx,
         );
         assert_eq!(
             view.selections.display_ranges(cx),
-            [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
         );
     });
 }
@@ -566,51 +566,57 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
             // Move the cursor a small distance.
             // Nothing is added to the navigation history.
             editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
+                s.select_display_ranges([
+                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
+                ])
             });
             editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
+                s.select_display_ranges([
+                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
+                ])
             });
             assert!(pop_history(&mut editor, cx).is_none());
 
             // Move the cursor a large distance.
             // The history can jump back to the previous position.
             editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
+                s.select_display_ranges([
+                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
+                ])
             });
             let nav_entry = pop_history(&mut editor, cx).unwrap();
             editor.navigate(nav_entry.data.unwrap(), cx);
             assert_eq!(nav_entry.item.id(), cx.entity_id());
             assert_eq!(
                 editor.selections.display_ranges(cx),
-                &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
+                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
             );
             assert!(pop_history(&mut editor, cx).is_none());
 
             // Move the cursor a small distance via the mouse.
             // Nothing is added to the navigation history.
-            editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
+            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
             editor.end_selection(cx);
             assert_eq!(
                 editor.selections.display_ranges(cx),
-                &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
+                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
             );
             assert!(pop_history(&mut editor, cx).is_none());
 
             // Move the cursor a large distance via the mouse.
             // The history can jump back to the previous position.
-            editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
+            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
             editor.end_selection(cx);
             assert_eq!(
                 editor.selections.display_ranges(cx),
-                &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
+                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
             );
             let nav_entry = pop_history(&mut editor, cx).unwrap();
             editor.navigate(nav_entry.data.unwrap(), cx);
             assert_eq!(nav_entry.item.id(), cx.entity_id());
             assert_eq!(
                 editor.selections.display_ranges(cx),
-                &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
+                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
             );
             assert!(pop_history(&mut editor, cx).is_none());
 
@@ -649,7 +655,7 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
             );
             assert_eq!(
                 editor.scroll_position(cx),
-                gpui::Point::new(0., editor.max_point(cx).row() as f32)
+                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
             );
 
             editor
@@ -667,18 +673,18 @@ fn test_cancel(cx: &mut TestAppContext) {
     });
 
     _ = view.update(cx, |view, cx| {
-        view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
+        view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
         view.update_selection(
-            DisplayPoint::new(1, 1),
+            DisplayPoint::new(DisplayRow(1), 1),
             0,
             gpui::Point::<f32>::default(),
             cx,
         );
         view.end_selection(cx);
 
-        view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
+        view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
         view.update_selection(
-            DisplayPoint::new(0, 3),
+            DisplayPoint::new(DisplayRow(0), 3),
             0,
             gpui::Point::<f32>::default(),
             cx,
@@ -687,8 +693,8 @@ fn test_cancel(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             [
-                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
-                DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
+                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
+                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
             ]
         );
     });
@@ -697,7 +703,7 @@ fn test_cancel(cx: &mut TestAppContext) {
         view.cancel(&Cancel, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
+            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
         );
     });
 
@@ -705,7 +711,7 @@ fn test_cancel(cx: &mut TestAppContext) {
         view.cancel(&Cancel, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
+            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
         );
     });
 }
@@ -741,7 +747,9 @@ fn test_fold_action(cx: &mut TestAppContext) {
 
     _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
-            s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]);
+            s.select_display_ranges([
+                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(12), 0)
+            ]);
         });
         view.fold(&Fold, cx);
         assert_eq!(
@@ -820,58 +828,60 @@ fn test_move_cursor(cx: &mut TestAppContext) {
     _ = view.update(cx, |view, cx| {
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
+            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
         );
 
         view.move_down(&MoveDown, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
+            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
         );
 
         view.move_right(&MoveRight, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
+            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
         );
 
         view.move_left(&MoveLeft, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
+            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
         );
 
         view.move_up(&MoveUp, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
+            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
         );
 
         view.move_to_end(&MoveToEnd, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
+            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
         );
 
         view.move_to_beginning(&MoveToBeginning, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
+            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
         );
 
         view.change_selections(None, cx, |s| {
-            s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
+            s.select_display_ranges([
+                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
+            ]);
         });
         view.select_to_beginning(&SelectToBeginning, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
+            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
         );
 
         view.select_to_end(&SelectToEnd, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
+            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
         );
     });
 }
@@ -1060,8 +1070,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
     _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
-                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
-                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
+                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
+                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
             ]);
         });
     });
@@ -1071,8 +1081,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             &[
-                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
-                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
+                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
+                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
             ]
         );
     });
@@ -1082,8 +1092,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             &[
-                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
-                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
+                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
             ]
         );
     });
@@ -1093,8 +1103,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             &[
-                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
-                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
+                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
+                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
             ]
         );
     });
@@ -1104,8 +1114,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             &[
-                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
-                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
+                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
+                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
             ]
         );
     });
@@ -1116,8 +1126,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             &[
-                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
-                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
+                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
+                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
             ]
         );
     });
@@ -1133,8 +1143,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             &[
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
-                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
+                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
+                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
             ]
         );
     });
@@ -1149,8 +1159,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             &[
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
-                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
+                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
+                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
             ]
         );
     });
@@ -1165,8 +1175,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             &[
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
-                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
+                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
+                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
             ]
         );
     });
@@ -1181,8 +1191,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             &[
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
-                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
+                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
+                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
             ]
         );
     });
@@ -1193,8 +1203,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             &[
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
+                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
+                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
             ]
         );
     });
@@ -1205,8 +1215,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             &[
-                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
-                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
+                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
             ]
         );
     });
@@ -1246,41 +1256,45 @@ fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
         // First, let's assert behavior on the first line, that was not soft-wrapped.
         // Start the cursor at the `k` on the first line
         view.change_selections(None, cx, |s| {
-            s.select_display_ranges([DisplayPoint::new(0, 7)..DisplayPoint::new(0, 7)]);
+            s.select_display_ranges([
+                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
+            ]);
         });
 
         // Moving to the beginning of the line should put us at the beginning of the line.
         view.move_to_beginning_of_line(&move_to_beg, cx);
         assert_eq!(
-            vec![DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),],
+            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
             view.selections.display_ranges(cx)
         );
 
         // Moving to the end of the line should put us at the end of the line.
         view.move_to_end_of_line(&move_to_end, cx);
         assert_eq!(
-            vec![DisplayPoint::new(0, 16)..DisplayPoint::new(0, 16),],
+            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
             view.selections.display_ranges(cx)
         );
 
         // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
         // Start the cursor at the last line (`y` that was wrapped to a new line)
         view.change_selections(None, cx, |s| {
-            s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0)]);
+            s.select_display_ranges([
+                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
+            ]);
         });
 
         // Moving to the beginning of the line should put us at the start of the second line of
         // display text, i.e., the `j`.
         view.move_to_beginning_of_line(&move_to_beg, cx);
         assert_eq!(
-            vec![DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),],
+            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
             view.selections.display_ranges(cx)
         );
 
         // Moving to the beginning of the line again should be a no-op.
         view.move_to_beginning_of_line(&move_to_beg, cx);
         assert_eq!(
-            vec![DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),],
+            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
             view.selections.display_ranges(cx)
         );
 
@@ -1288,14 +1302,14 @@ fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
         // next display line.
         view.move_to_end_of_line(&move_to_end, cx);
         assert_eq!(
-            vec![DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),],
+            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
             view.selections.display_ranges(cx)
         );
 
         // Moving to the end of the line again should be a no-op.
         view.move_to_end_of_line(&move_to_end, cx);
         assert_eq!(
-            vec![DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),],
+            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
             view.selections.display_ranges(cx)
         );
     });
@@ -1312,8 +1326,8 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
     _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
-                DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
-                DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
+                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
+                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
             ])
         });
 
@@ -1370,43 +1384,45 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
         );
 
         view.change_selections(None, cx, |s| {
-            s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
+            s.select_display_ranges([
+                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
+            ]);
         });
 
         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
+            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
         );
 
         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
+            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
         );
 
         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
+            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
         );
 
         view.move_to_next_word_end(&MoveToNextWordEnd, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
+            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
         );
 
         view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
+            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
         );
 
         view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
+            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
         );
     });
 }
@@ -1806,9 +1822,9 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
                 // an empty selection - the preceding word fragment is deleted
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
                 // characters selected - they are deleted
-                DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12),
+                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
             ])
         });
         view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
@@ -1819,9 +1835,9 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
                 // an empty selection - the following word fragment is deleted
-                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
                 // characters selected - they are deleted
-                DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10),
+                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
             ])
         });
         view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
@@ -1841,9 +1857,9 @@ fn test_newline(cx: &mut TestAppContext) {
     _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
-                DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6),
+                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
+                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
+                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
             ])
         });
 
@@ -2603,9 +2619,9 @@ fn test_delete_line(cx: &mut TestAppContext) {
     _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
-                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
-                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
-                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
+                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
+                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
+                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
             ])
         });
         view.delete_line(&DeleteLine, cx);
@@ -2613,8 +2629,8 @@ fn test_delete_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             vec![
-                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
-                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
+                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
+                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
             ]
         );
     });
@@ -2625,13 +2641,15 @@ fn test_delete_line(cx: &mut TestAppContext) {
     });
     _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
-            s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
+            s.select_display_ranges([
+                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
+            ])
         });
         view.delete_line(&DeleteLine, cx);
         assert_eq!(view.display_text(cx), "ghi\n");
         assert_eq!(
             view.selections.display_ranges(cx),
-            vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
+            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
         );
     });
 }
@@ -2843,7 +2861,7 @@ async fn test_custom_newlines_cause_no_false_positive_diffs(
                 .buffer()
                 .read(cx)
                 .snapshot(cx)
-                .git_diff_hunks_in_range(0..u32::MAX)
+                .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
                 .collect::<Vec<_>>(),
             Vec::new(),
             "Should not have any diffs for files with custom newlines"
@@ -3284,10 +3302,10 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
     _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
-                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
-                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
+                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
+                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
+                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
+                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
             ])
         });
         view.duplicate_line_down(&DuplicateLineDown, cx);
@@ -3295,10 +3313,10 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             vec![
-                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
-                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
-                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
-                DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
+                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
+                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
+                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
+                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
             ]
         );
     });
@@ -3310,8 +3328,8 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
     _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
-                DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
-                DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
+                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
+                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
             ])
         });
         view.duplicate_line_down(&DuplicateLineDown, cx);
@@ -3319,8 +3337,8 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             vec![
-                DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
-                DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
+                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
+                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
             ]
         );
     });
@@ -3334,10 +3352,10 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
     _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
-                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
-                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
+                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
+                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
+                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
+                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
             ])
         });
         view.duplicate_line_up(&DuplicateLineUp, cx);
@@ -3345,10 +3363,10 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             vec![
-                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
-                DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
+                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
+                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
+                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
+                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
             ]
         );
     });
@@ -3360,8 +3378,8 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
     _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
-                DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
-                DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
+                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
+                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
             ])
         });
         view.duplicate_line_up(&DuplicateLineUp, cx);
@@ -3369,8 +3387,8 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             vec![
-                DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
-                DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
+                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
+                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
             ]
         );
     });
@@ -3396,10 +3414,10 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
         );
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
-                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
-                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
-                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
-                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
+                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
+                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
+                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
+                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
             ])
         });
         assert_eq!(
@@ -3415,10 +3433,10 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             vec![
-                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
-                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
-                DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
-                DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
+                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
+                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
+                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
+                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
             ]
         );
     });
@@ -3432,10 +3450,10 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             vec![
-                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
-                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
-                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
-                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
+                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
+                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
+                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
+                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
             ]
         );
     });
@@ -3449,10 +3467,10 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             vec![
-                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
-                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
-                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
-                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
+                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
+                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
+                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
+                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
             ]
         );
     });
@@ -3466,10 +3484,10 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             vec![
-                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
-                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
-                DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
-                DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
+                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
+                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
+                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
+                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
             ]
         );
     });
@@ -3808,7 +3826,7 @@ fn test_select_all(cx: &mut TestAppContext) {
         view.select_all(&SelectAll, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
+            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
         );
     });
 }
@@ -3824,18 +3842,18 @@ fn test_select_line(cx: &mut TestAppContext) {
     _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
-                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
-                DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
+                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
+                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
+                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
+                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
             ])
         });
         view.select_line(&SelectLine, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
             vec![
-                DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
-                DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
+                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
+                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
             ]
         );
     });
@@ -3845,8 +3863,8 @@ fn test_select_line(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             vec![
-                DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
-                DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
+                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
+                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
             ]
         );
     });
@@ -3855,7 +3873,7 @@ fn test_select_line(cx: &mut TestAppContext) {
         view.select_line(&SelectLine, cx);
         assert_eq!(
             view.selections.display_ranges(cx),
-            vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
+            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
         );
     });
 }
@@ -3880,10 +3898,10 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
         );
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
-                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
-                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
+                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
+                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
+                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
+                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
             ])
         });
         assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
@@ -3898,17 +3916,19 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             [
-                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
-                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
-                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
-                DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
+                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
+                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
+                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
+                DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
             ]
         );
     });
 
     _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
-            s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
+            s.select_display_ranges([
+                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
+            ])
         });
         view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
         assert_eq!(
@@ -3918,14 +3938,14 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
         assert_eq!(
             view.selections.display_ranges(cx),
             [
-                DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
-                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
-                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
-                DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
-                DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
-                DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
-                DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
-                DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
+                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
+                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
+                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
+                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
+                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
+                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
+                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
+                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
             ]
         );
     });
@@ -4429,9 +4449,9 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
     _ = view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
-                DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
-                DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
-                DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
+                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
+                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
+                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
             ]);
         });
         view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);

crates/editor/src/element.rs šŸ”—

@@ -9,14 +9,15 @@ use crate::{
     hover_popover::{
         self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
     },
+    hunk_status,
     items::BufferSearchHighlights,
     mouse_context_menu::{self, MouseContextMenu},
     scroll::scroll_amount::ScrollAmount,
-    CodeActionsMenu, CursorShape, DisplayPoint, DocumentHighlightRead, DocumentHighlightWrite,
-    Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts,
-    GutterDimensions, HalfPageDown, HalfPageUp, HoveredCursor, HunkToExpand, LineDown, LineUp,
-    OpenExcerpts, PageDown, PageUp, Point, SelectPhase, Selection, SoftWrap, ToPoint,
-    CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
+    CodeActionsMenu, CursorShape, DisplayPoint, DisplayRow, DocumentHighlightRead,
+    DocumentHighlightWrite, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
+    ExpandExcerpts, GutterDimensions, HalfPageDown, HalfPageUp, HoveredCursor, HunkToExpand,
+    LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase,
+    Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
 };
 use anyhow::Result;
 use client::ParticipantIndex;
@@ -35,7 +36,7 @@ use gpui::{
 use itertools::Itertools;
 use language::language_settings::ShowWhitespaceSetting;
 use lsp::DiagnosticSeverity;
-use multi_buffer::Anchor;
+use multi_buffer::{Anchor, MultiBufferPoint, MultiBufferRow};
 use project::{
     project_settings::{GitGutterSetting, ProjectSettings},
     ProjectPath,
@@ -64,7 +65,7 @@ struct SelectionLayout {
     is_newest: bool,
     is_local: bool,
     range: Range<DisplayPoint>,
-    active_rows: Range<u32>,
+    active_rows: Range<DisplayRow>,
     user_name: Option<SharedString>,
 }
 
@@ -98,16 +99,19 @@ impl SelectionLayout {
         {
             if head.column() > 0 {
                 head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left)
-            } else if head.row() > 0 && head != map.max_point() {
+            } else if head.row().0 > 0 && head != map.max_point() {
                 head = map.clip_point(
-                    DisplayPoint::new(head.row() - 1, map.line_len(head.row() - 1)),
+                    DisplayPoint::new(
+                        head.row().previous_row(),
+                        map.line_len(head.row().previous_row()),
+                    ),
                     Bias::Left,
                 );
                 // updating range.end is a no-op unless you're cursor is
                 // on the newline containing a multi-buffer divider
                 // in which case the clip_point may have moved the head up
                 // an additional row.
-                range.end = DisplayPoint::new(head.row() + 1, 0);
+                range.end = DisplayPoint::new(head.row().next_row(), 0);
                 active_rows.end = head.row();
             }
         }
@@ -129,6 +133,8 @@ pub struct EditorElement {
     style: EditorStyle,
 }
 
+type DisplayRowDelta = u32;
+
 impl EditorElement {
     pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {
         Self {
@@ -706,12 +712,12 @@ impl EditorElement {
         start_anchor: Anchor,
         end_anchor: Anchor,
         snapshot: &EditorSnapshot,
-        start_row: u32,
-        end_row: u32,
+        start_row: DisplayRow,
+        end_row: DisplayRow,
         cx: &mut WindowContext,
     ) -> (
         Vec<(PlayerColor, Vec<SelectionLayout>)>,
-        BTreeMap<u32, bool>,
+        BTreeMap<DisplayRow, bool>,
         Option<DisplayPoint>,
     ) {
         let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
@@ -743,10 +749,11 @@ impl EditorElement {
                     newest_selection_head = Some(layout.head);
                 }
 
-                for row in cmp::max(layout.active_rows.start, start_row)
-                    ..=cmp::min(layout.active_rows.end, end_row)
+                for row in cmp::max(layout.active_rows.start.0, start_row.0)
+                    ..=cmp::min(layout.active_rows.end.0, end_row.0)
                 {
-                    let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
+                    let contains_non_empty_selection =
+                        active_rows.entry(DisplayRow(row)).or_insert(!is_empty);
                     *contains_non_empty_selection |= !is_empty;
                 }
                 layouts.push(layout);
@@ -826,7 +833,7 @@ impl EditorElement {
         snapshot: &EditorSnapshot,
         content_origin: gpui::Point<Pixels>,
         visible_anchor_range: Range<Anchor>,
-        visible_display_row_range: Range<u32>,
+        visible_display_row_range: Range<DisplayRow>,
         scroll_pixel_position: gpui::Point<Pixels>,
         line_height: Pixels,
         line_layouts: &[LineWithInvisibles],
@@ -842,13 +849,14 @@ impl EditorElement {
                 let row = display_range.start.row();
                 debug_assert!(row < visible_display_row_range.end);
                 let line_layout = line_layouts
-                    .get((row - visible_display_row_range.start) as usize)
+                    .get(row.minus(visible_display_row_range.start) as usize)
                     .map(|l| &l.line)?;
 
                 let start_x = content_origin.x
                     + line_layout.x_for_index(display_range.start.column() as usize)
                     - scroll_pixel_position.x;
-                let start_y = content_origin.y + row as f32 * line_height - scroll_pixel_position.y;
+                let start_y =
+                    content_origin.y + row.as_f32() * line_height - scroll_pixel_position.y;
                 let end_x = content_origin.x
                     + line_layout.x_for_index(display_range.end.column() as usize)
                     - scroll_pixel_position.x;
@@ -927,7 +935,7 @@ impl EditorElement {
         &self,
         snapshot: &EditorSnapshot,
         selections: &[(PlayerColor, Vec<SelectionLayout>)],
-        visible_display_row_range: Range<u32>,
+        visible_display_row_range: Range<DisplayRow>,
         line_layouts: &[LineWithInvisibles],
         text_hitbox: &Hitbox,
         content_origin: gpui::Point<Pixels>,
@@ -951,7 +959,7 @@ impl EditorElement {
                     }
 
                     let cursor_row_layout = &line_layouts
-                        [(cursor_position.row() - visible_display_row_range.start) as usize]
+                        [cursor_position.row().minus(visible_display_row_range.start) as usize]
                         .line;
                     let cursor_column = cursor_position.column() as usize;
 
@@ -999,7 +1007,8 @@ impl EditorElement {
                     };
 
                     let x = cursor_character_x - scroll_pixel_position.x;
-                    let y = (cursor_position.row() as f32 - scroll_pixel_position.y / line_height)
+                    let y = (cursor_position.row().as_f32()
+                        - scroll_pixel_position.y / line_height)
                         * line_height;
                     if selection.is_newest {
                         editor.pixel_position_of_newest_cursor = Some(point(
@@ -1009,7 +1018,7 @@ impl EditorElement {
 
                         if autoscroll_containing_element {
                             let top = text_hitbox.origin.y
-                                + (cursor_position.row() as f32 - scroll_position.y - 3.).max(0.)
+                                + (cursor_position.row().as_f32() - scroll_position.y - 3.).max(0.)
                                     * line_height;
                             let left = text_hitbox.origin.x
                                 + (cursor_position.column() as f32 - scroll_position.x - 3.)
@@ -1017,7 +1026,7 @@ impl EditorElement {
                                     * em_width;
 
                             let bottom = text_hitbox.origin.y
-                                + (cursor_position.row() as f32 - scroll_position.y + 4.)
+                                + (cursor_position.row().as_f32() - scroll_position.y + 4.)
                                     * line_height;
                             let right = text_hitbox.origin.x
                                 + (cursor_position.column() as f32 - scroll_position.x + 4.)
@@ -1040,7 +1049,7 @@ impl EditorElement {
                     let cursor_name = selection.user_name.clone().map(|name| CursorName {
                         string: name,
                         color: self.style.background,
-                        is_top_row: cursor_position.row() == 0,
+                        is_top_row: cursor_position.row().0 == 0,
                     });
                     cursor.layout(content_origin, cursor_name, cx);
                     cursors.push(cursor);
@@ -1112,10 +1121,10 @@ impl EditorElement {
         );
 
         let height = bounds.size.height;
-        let total_rows = snapshot.max_point().row() as f32 + rows_per_page;
+        let total_rows = snapshot.max_point().row().as_f32() + rows_per_page;
         let px_per_row = height / total_rows;
         let thumb_height = (rows_per_page * px_per_row).max(ScrollbarLayout::MIN_THUMB_HEIGHT);
-        let row_height = (height - thumb_height) / snapshot.max_point().row() as f32;
+        let row_height = (height - thumb_height) / snapshot.max_point().row().as_f32();
 
         Some(ScrollbarLayout {
             hitbox: cx.insert_hitbox(track_bounds, false),
@@ -1129,7 +1138,7 @@ impl EditorElement {
     #[allow(clippy::too_many_arguments)]
     fn layout_gutter_fold_indicators(
         &self,
-        fold_statuses: Vec<Option<(FoldStatus, u32, bool)>>,
+        fold_statuses: Vec<Option<(FoldStatus, MultiBufferRow, bool)>>,
         line_height: Pixels,
         gutter_dimensions: &GutterDimensions,
         gutter_settings: crate::editor_settings::Gutter,
@@ -1181,18 +1190,22 @@ impl EditorElement {
         &self,
         line_height: Pixels,
         gutter_hitbox: &Hitbox,
-        display_rows: Range<u32>,
+        display_rows: Range<DisplayRow>,
         snapshot: &EditorSnapshot,
         cx: &mut WindowContext,
     ) -> Vec<(DisplayDiffHunk, Option<Hitbox>)> {
         let buffer_snapshot = &snapshot.buffer_snapshot;
 
-        let buffer_start_row = DisplayPoint::new(display_rows.start, 0)
-            .to_point(snapshot)
-            .row;
-        let buffer_end_row = DisplayPoint::new(display_rows.end, 0)
-            .to_point(snapshot)
-            .row;
+        let buffer_start_row = MultiBufferRow(
+            DisplayPoint::new(display_rows.start, 0)
+                .to_point(snapshot)
+                .row,
+        );
+        let buffer_end_row = MultiBufferRow(
+            DisplayPoint::new(display_rows.end, 0)
+                .to_point(snapshot)
+                .row,
+        );
 
         let expanded_hunk_display_rows = self.editor.update(cx, |editor, _| {
             editor
@@ -1249,7 +1262,7 @@ impl EditorElement {
     #[allow(clippy::too_many_arguments)]
     fn layout_inline_blame(
         &self,
-        display_row: u32,
+        display_row: DisplayRow,
         display_snapshot: &DisplaySnapshot,
         line_layout: &LineWithInvisibles,
         em_width: Pixels,
@@ -1273,7 +1286,7 @@ impl EditorElement {
             .map(|(w, _)| w.clone());
 
         let display_point = DisplayPoint::new(display_row, 0);
-        let buffer_row = display_point.to_point(display_snapshot).row;
+        let buffer_row = MultiBufferRow(display_point.to_point(display_snapshot).row);
 
         let blame = self.editor.read(cx).blame.clone()?;
         let blame_entry = blame
@@ -1286,7 +1299,7 @@ impl EditorElement {
             render_inline_blame_entry(&blame, blame_entry, &self.style, workspace, cx);
 
         let start_y = content_origin.y
-            + line_height * (display_row as f32 - scroll_pixel_position.y / line_height);
+            + line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height);
 
         let start_x = {
             const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
@@ -1315,7 +1328,7 @@ impl EditorElement {
     #[allow(clippy::too_many_arguments)]
     fn layout_blame_entries(
         &self,
-        buffer_rows: impl Iterator<Item = Option<u32>>,
+        buffer_rows: impl Iterator<Item = Option<MultiBufferRow>>,
         em_width: Pixels,
         scroll_position: gpui::Point<f32>,
         line_height: Pixels,
@@ -1399,7 +1412,7 @@ impl EditorElement {
                     actions
                         .tasks
                         .as_ref()
-                        .map(|tasks| tasks.position.row)
+                        .map(|tasks| tasks.position.to_display_point(snapshot).row())
                         .or_else(|| *deployed_from_indicator)
                 } else {
                     None
@@ -1407,19 +1420,16 @@ impl EditorElement {
             editor
                 .tasks
                 .iter()
-                .filter_map(|((_, row), (multibuffer_offset, _))| {
-                    if snapshot.is_line_folded(*row) {
+                .filter_map(|(_, (multibuffer_offset, _))| {
+                    let multibuffer_point = multibuffer_offset.to_point(&snapshot.buffer_snapshot);
+                    let multibuffer_row = MultiBufferRow(multibuffer_point.row);
+                    if snapshot.is_line_folded(multibuffer_row) {
                         return None;
                     }
-                    let display_row = snapshot
-                        .buffer_snapshot
-                        .offset_to_point(*multibuffer_offset)
-                        .to_display_point(&snapshot.display_snapshot)
-                        .row();
-
+                    let display_row = multibuffer_point.to_display_point(snapshot).row();
                     let button = editor.render_run_indicator(
                         &self.style,
-                        Some(*row) == active_task_indicator_row,
+                        Some(display_row) == active_task_indicator_row,
                         display_row,
                         cx,
                     );
@@ -1489,10 +1499,10 @@ impl EditorElement {
     fn calculate_relative_line_numbers(
         &self,
         snapshot: &EditorSnapshot,
-        rows: &Range<u32>,
-        relative_to: Option<u32>,
-    ) -> HashMap<u32, u32> {
-        let mut relative_rows: HashMap<u32, u32> = Default::default();
+        rows: &Range<DisplayRow>,
+        relative_to: Option<DisplayRow>,
+    ) -> HashMap<DisplayRow, DisplayRowDelta> {
+        let mut relative_rows: HashMap<DisplayRow, DisplayRowDelta> = Default::default();
         let Some(relative_to) = relative_to else {
             return relative_rows;
         };
@@ -1501,17 +1511,17 @@ impl EditorElement {
         let end = rows.end.max(relative_to);
 
         let buffer_rows = snapshot
-            .buffer_rows(start)
-            .take(1 + (end - start) as usize)
+            .display_rows(start)
+            .take(1 + end.minus(start) as usize)
             .collect::<Vec<_>>();
 
-        let head_idx = relative_to - start;
+        let head_idx = relative_to.minus(start);
         let mut delta = 1;
         let mut i = head_idx + 1;
         while i < buffer_rows.len() as u32 {
             if buffer_rows[i as usize].is_some() {
-                if rows.contains(&(i + start)) {
-                    relative_rows.insert(i + start, delta);
+                if rows.contains(&DisplayRow(i + start.0)) {
+                    relative_rows.insert(DisplayRow(i + start.0), delta);
                 }
                 delta += 1;
             }
@@ -1526,8 +1536,8 @@ impl EditorElement {
         while i > 0 {
             i -= 1;
             if buffer_rows[i as usize].is_some() {
-                if rows.contains(&(i + start)) {
-                    relative_rows.insert(i + start, delta);
+                if rows.contains(&DisplayRow(i + start.0)) {
+                    relative_rows.insert(DisplayRow(i + start.0), delta);
                 }
                 delta += 1;
             }
@@ -1538,15 +1548,15 @@ impl EditorElement {
 
     fn layout_line_numbers(
         &self,
-        rows: Range<u32>,
-        buffer_rows: impl Iterator<Item = Option<u32>>,
-        active_rows: &BTreeMap<u32, bool>,
+        rows: Range<DisplayRow>,
+        buffer_rows: impl Iterator<Item = Option<DisplayRow>>,
+        active_rows: &BTreeMap<DisplayRow, bool>,
         newest_selection_head: Option<DisplayPoint>,
         snapshot: &EditorSnapshot,
         cx: &WindowContext,
     ) -> (
         Vec<Option<ShapedLine>>,
-        Vec<Option<(FoldStatus, BufferRow, bool)>>,
+        Vec<Option<(FoldStatus, MultiBufferRow, bool)>>,
     ) {
         let editor = self.editor.read(cx);
         let is_singleton = editor.is_singleton(cx);
@@ -1581,20 +1591,20 @@ impl EditorElement {
         let relative_rows = self.calculate_relative_line_numbers(snapshot, &rows, relative_to);
 
         for (ix, row) in buffer_rows.into_iter().enumerate() {
-            let display_row = rows.start + ix as u32;
+            let display_row = DisplayRow(rows.start.0 + ix as u32);
             let (active, color) = if active_rows.contains_key(&display_row) {
                 (true, cx.theme().colors().editor_active_line_number)
             } else {
                 (false, cx.theme().colors().editor_line_number)
             };
-            if let Some(buffer_row) = row {
+            if let Some(display_row) = row {
                 if include_line_numbers {
                     line_number.clear();
-                    let default_number = buffer_row + 1;
+                    let default_number = display_row.0 + 1;
                     let number = relative_rows
-                        .get(&(ix as u32 + rows.start))
+                        .get(&DisplayRow(ix as u32 + rows.start.0))
                         .unwrap_or(&default_number);
-                    write!(&mut line_number, "{}", number).unwrap();
+                    write!(&mut line_number, "{number}").unwrap();
                     let run = TextRun {
                         len: line_number.len(),
                         font: self.style.text.font(),
@@ -1613,9 +1623,12 @@ impl EditorElement {
                     fold_statuses.push(
                         is_singleton
                             .then(|| {
+                                let multibuffer_point =
+                                    DisplayPoint::new(display_row, 0).to_point(snapshot);
+                                let multibuffer_row = MultiBufferRow(multibuffer_point.row);
                                 snapshot
-                                    .fold_for_line(buffer_row)
-                                    .map(|fold_status| (fold_status, buffer_row, active))
+                                    .fold_for_line(multibuffer_row)
+                                    .map(|fold_status| (fold_status, multibuffer_row, active))
                             })
                             .flatten(),
                     )
@@ -1631,7 +1644,7 @@ impl EditorElement {
 
     fn layout_lines(
         &self,
-        rows: Range<u32>,
+        rows: Range<DisplayRow>,
         line_number_layouts: &[Option<ShapedLine>],
         snapshot: &EditorSnapshot,
         cx: &WindowContext,
@@ -1650,7 +1663,7 @@ impl EditorElement {
                 .as_ref()
                 .map_or("", AsRef::as_ref)
                 .split('\n')
-                .skip(rows.start as usize)
+                .skip(rows.start.0 as usize)
                 .chain(iter::repeat(""))
                 .take(rows.len());
             placeholder_lines
@@ -1689,7 +1702,7 @@ impl EditorElement {
     #[allow(clippy::too_many_arguments)]
     fn build_blocks(
         &self,
-        rows: Range<u32>,
+        rows: Range<DisplayRow>,
         snapshot: &EditorSnapshot,
         hitbox: &Hitbox,
         text_hitbox: &Hitbox,
@@ -1712,7 +1725,7 @@ impl EditorElement {
         let render_block = |block: &TransformBlock,
                             available_space: Size<AvailableSpace>,
                             block_id: usize,
-                            block_row_start: u32,
+                            block_row_start: DisplayRow,
                             cx: &mut WindowContext| {
             let mut element = match block {
                 TransformBlock::Custom(block) => {
@@ -1722,7 +1735,7 @@ impl EditorElement {
                         .to_display_point(snapshot);
                     let anchor_x = text_x
                         + if rows.contains(&align_to.row()) {
-                            line_layouts[(align_to.row() - rows.start) as usize]
+                            line_layouts[align_to.row().minus(rows.start) as usize]
                                 .line
                                 .x_for_index(align_to.column() as usize)
                         } else {
@@ -1788,7 +1801,7 @@ impl EditorElement {
                         };
 
                         let line_offset_from_top =
-                            block_row_start + *height as u32 + offset_from_excerpt_start
+                            block_row_start.0 + *height as u32 + offset_from_excerpt_start
                                 - snapshot
                                     .scroll_anchor
                                     .scroll_position(&snapshot.display_snapshot)
@@ -2040,7 +2053,7 @@ impl EditorElement {
             let mut origin = hitbox.origin
                 + point(
                     Pixels::ZERO,
-                    block.row as f32 * line_height - scroll_pixel_position.y,
+                    block.row.as_f32() * line_height - scroll_pixel_position.y,
                 );
             if !matches!(block.style, BlockStyle::Sticky) {
                 origin += point(-scroll_pixel_position.x, Pixels::ZERO);
@@ -2058,7 +2071,7 @@ impl EditorElement {
         hitbox: &Hitbox,
         text_hitbox: &Hitbox,
         content_origin: gpui::Point<Pixels>,
-        start_row: u32,
+        start_row: DisplayRow,
         scroll_pixel_position: gpui::Point<Pixels>,
         line_layouts: &[LineWithInvisibles],
         newest_selection_head: DisplayPoint,
@@ -2084,21 +2097,17 @@ impl EditorElement {
 
         let (x, y) = match position {
             crate::ContextMenuOrigin::EditorPoint(point) => {
-                let cursor_row_layout = &line_layouts[(point.row() - start_row) as usize].line;
+                let cursor_row_layout = &line_layouts[point.row().minus(start_row) as usize].line;
                 let x = cursor_row_layout.x_for_index(point.column() as usize)
                     - scroll_pixel_position.x;
-                let y = (point.row() + 1) as f32 * line_height - scroll_pixel_position.y;
+                let y = point.row().next_row().as_f32() * line_height - scroll_pixel_position.y;
                 (x, y)
             }
             crate::ContextMenuOrigin::GutterIndicator(row) => {
                 // Context menu was spawned via a click on a gutter. Ensure it's a bit closer to the indicator than just a plain first column of the
                 // text field.
-                let snapshot = self.editor.update(cx, |this, cx| this.snapshot(cx));
-                let row = Point::new(row, 0)
-                    .to_display_point(&snapshot.display_snapshot)
-                    .row();
                 let x = -gutter_overshoot;
-                let y = (row + 1) as f32 * line_height - scroll_pixel_position.y;
+                let y = row.next_row().as_f32() * line_height - scroll_pixel_position.y;
                 (x, y)
             }
         };
@@ -2143,7 +2152,7 @@ impl EditorElement {
         snapshot: &EditorSnapshot,
         hitbox: &Hitbox,
         text_hitbox: &Hitbox,
-        visible_display_row_range: Range<u32>,
+        visible_display_row_range: Range<DisplayRow>,
         content_origin: gpui::Point<Pixels>,
         scroll_pixel_position: gpui::Point<Pixels>,
         line_layouts: &[LineWithInvisibles],
@@ -2184,12 +2193,12 @@ impl EditorElement {
 
         // This is safe because we check on layout whether the required row is available
         let hovered_row_layout =
-            &line_layouts[(position.row() - visible_display_row_range.start) as usize].line;
+            &line_layouts[position.row().minus(visible_display_row_range.start) as usize].line;
 
         // Compute Hovered Point
         let x =
             hovered_row_layout.x_for_index(position.column() as usize) - scroll_pixel_position.x;
-        let y = position.row() as f32 * line_height - scroll_pixel_position.y;
+        let y = position.row().as_f32() * line_height - scroll_pixel_position.y;
         let hovered_point = content_origin + point(x, y);
 
         let mut overall_height = Pixels::ZERO;
@@ -2265,10 +2274,14 @@ impl EditorElement {
             if let EditorMode::Full = layout.mode {
                 let mut active_rows = layout.active_rows.iter().peekable();
                 while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
-                    let mut end_row = *start_row;
-                    while active_rows.peek().map_or(false, |r| {
-                        *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
-                    }) {
+                    let mut end_row = start_row.0;
+                    while active_rows
+                        .peek()
+                        .map_or(false, |(active_row, has_selection)| {
+                            active_row.0 == end_row + 1
+                                && *has_selection == contains_non_empty_selection
+                        })
+                    {
                         active_rows.next().unwrap();
                         end_row += 1;
                     }
@@ -2277,12 +2290,12 @@ impl EditorElement {
                         let origin = point(
                             layout.hitbox.origin.x,
                             layout.hitbox.origin.y
-                                + (*start_row as f32 - scroll_top)
+                                + (start_row.as_f32() - scroll_top)
                                     * layout.position_map.line_height,
                         );
                         let size = size(
                             layout.hitbox.size.width,
-                            layout.position_map.line_height * (end_row - start_row + 1) as f32,
+                            layout.position_map.line_height * (end_row - start_row.0 + 1) as f32,
                         );
                         let active_line_bg = cx.theme().colors().editor_active_line_background;
                         cx.paint_quad(fill(Bounds { origin, size }, active_line_bg));
@@ -2290,28 +2303,28 @@ impl EditorElement {
                 }
 
                 let mut paint_highlight =
-                    |highlight_row_start: u32, highlight_row_end: u32, color| {
+                    |highlight_row_start: DisplayRow, highlight_row_end: DisplayRow, color| {
                         let origin = point(
                             layout.hitbox.origin.x,
                             layout.hitbox.origin.y
-                                + (highlight_row_start as f32 - scroll_top)
+                                + (highlight_row_start.as_f32() - scroll_top)
                                     * layout.position_map.line_height,
                         );
                         let size = size(
                             layout.hitbox.size.width,
                             layout.position_map.line_height
-                                * (highlight_row_end + 1 - highlight_row_start) as f32,
+                                * highlight_row_end.next_row().minus(highlight_row_start) as f32,
                         );
                         cx.paint_quad(fill(Bounds { origin, size }, color));
                     };
 
-                let mut current_paint: Option<(Hsla, Range<u32>)> = None;
+                let mut current_paint: Option<(Hsla, Range<DisplayRow>)> = None;
                 for (&new_row, &new_color) in &layout.highlighted_rows {
                     match &mut current_paint {
                         Some((current_color, current_range)) => {
                             let current_color = *current_color;
-                            let new_range_started =
-                                current_color != new_color || current_range.end + 1 != new_row;
+                            let new_range_started = current_color != new_color
+                                || current_range.end.next_row() != new_row;
                             if new_range_started {
                                 paint_highlight(
                                     current_range.start,
@@ -2321,7 +2334,7 @@ impl EditorElement {
                                 current_paint = Some((new_color, new_row..new_row));
                                 continue;
                             } else {
-                                current_range.end += 1;
+                                current_range.end = current_range.end.next_row();
                             }
                         }
                         None => current_paint = Some((new_color, new_row..new_row)),
@@ -2494,7 +2507,7 @@ impl EditorElement {
 
         match hunk {
             DisplayDiffHunk::Folded { display_row, .. } => {
-                let start_y = *display_row as f32 * line_height - scroll_top;
+                let start_y = display_row.as_f32() * line_height - scroll_top;
                 let end_y = start_y + line_height;
 
                 let width = 0.275 * line_height;
@@ -2527,8 +2540,8 @@ impl EditorElement {
                         })
                         .unwrap_or(end_row);
 
-                    let start_y = start_row as f32 * line_height - scroll_top;
-                    let end_y = end_row_in_current_excerpt as f32 * line_height - scroll_top;
+                    let start_y = start_row.as_f32() * line_height - scroll_top;
+                    let end_y = end_row_in_current_excerpt.as_f32() * line_height - scroll_top;
 
                     let width = 0.275 * line_height;
                     let highlight_origin = bounds.origin + point(-width, start_y);
@@ -2539,7 +2552,7 @@ impl EditorElement {
                     let row = display_row_range.start;
 
                     let offset = line_height / 2.;
-                    let start_y = row as f32 * line_height - offset - scroll_top;
+                    let start_y = row.as_f32() * line_height - offset - scroll_top;
                     let end_y = start_y + line_height;
 
                     let width = 0.35 * line_height;
@@ -2648,7 +2661,7 @@ impl EditorElement {
             .show_whitespaces;
 
         for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
-            let row = layout.visible_display_row_range.start + ix as u32;
+            let row = DisplayRow(layout.visible_display_row_range.start.0 + ix as u32);
             line_with_invisibles.draw(
                 layout,
                 row,
@@ -2879,20 +2892,22 @@ impl EditorElement {
                             if scrollbar_settings.git_diff {
                                 let marker_row_ranges = snapshot
                                     .buffer_snapshot
-                                    .git_diff_hunks_in_range(0..max_point.row)
+                                    .git_diff_hunks_in_range(
+                                        MultiBufferRow::MIN..MultiBufferRow::MAX,
+                                    )
                                     .map(|hunk| {
                                         let start_display_row =
-                                            Point::new(hunk.associated_range.start, 0)
+                                            MultiBufferPoint::new(hunk.associated_range.start.0, 0)
                                                 .to_display_point(&snapshot.display_snapshot)
                                                 .row();
                                         let mut end_display_row =
-                                            Point::new(hunk.associated_range.end, 0)
+                                            MultiBufferPoint::new(hunk.associated_range.end.0, 0)
                                                 .to_display_point(&snapshot.display_snapshot)
                                                 .row();
                                         if end_display_row != start_display_row {
-                                            end_display_row -= 1;
+                                            end_display_row.0 -= 1;
                                         }
-                                        let color = match hunk.status() {
+                                        let color = match hunk_status(&hunk) {
                                             DiffHunkStatus::Added => theme.status().created,
                                             DiffHunkStatus::Modified => theme.status().modified,
                                             DiffHunkStatus::Removed => theme.status().deleted,
@@ -3018,7 +3033,8 @@ impl EditorElement {
             let row_range = if range.end.column() == 0 {
                 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
             } else {
-                cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
+                cmp::max(range.start.row(), start_row)
+                    ..cmp::min(range.end.row().next_row(), end_row)
             };
 
             let highlighted_range = HighlightedRange {
@@ -3026,13 +3042,13 @@ impl EditorElement {
                 line_height: layout.position_map.line_height,
                 corner_radius,
                 start_y: layout.content_origin.y
-                    + row_range.start as f32 * layout.position_map.line_height
+                    + row_range.start.as_f32() * layout.position_map.line_height
                     - layout.position_map.scroll_pixel_position.y,
                 lines: row_range
-                    .into_iter()
+                    .iter_rows()
                     .map(|row| {
                         let line_layout =
-                            &layout.position_map.line_layouts[(row - start_row) as usize].line;
+                            &layout.position_map.line_layouts[row.minus(start_row) as usize].line;
                         HighlightedRangeLine {
                             start_x: if row == range.start.row() {
                                 layout.content_origin.x
@@ -3281,14 +3297,20 @@ impl EditorElement {
     }
 
     fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &WindowContext) -> Pixels {
-        let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
+        let digit_count = snapshot
+            .max_buffer_row()
+            .next_row()
+            .as_f32()
+            .log10()
+            .floor() as usize
+            + 1;
         self.column_pixels(digit_count, cx)
     }
 }
 
 fn prepaint_gutter_button(
     button: IconButton,
-    row: u32,
+    row: DisplayRow,
     line_height: Pixels,
     gutter_dimensions: &GutterDimensions,
     scroll_pixel_position: gpui::Point<Pixels>,
@@ -3312,7 +3334,7 @@ fn prepaint_gutter_button(
         - blame_width;
     x += available_width / 2.;
 
-    let mut y = row as f32 * line_height - scroll_pixel_position.y;
+    let mut y = row.as_f32() * line_height - scroll_pixel_position.y;
     y += (line_height - indicator_size.height) / 2.;
 
     button.prepaint_as_root(gutter_hitbox.origin + point(x, y), available_space, cx);
@@ -3563,15 +3585,15 @@ impl LineWithInvisibles {
     fn draw(
         &self,
         layout: &EditorLayout,
-        row: u32,
+        row: DisplayRow,
         content_origin: gpui::Point<Pixels>,
         whitespace_setting: ShowWhitespaceSetting,
         selection_ranges: &[Range<DisplayPoint>],
         cx: &mut WindowContext,
     ) {
         let line_height = layout.position_map.line_height;
-        let line_y =
-            line_height * (row as f32 - layout.position_map.scroll_pixel_position.y / line_height);
+        let line_y = line_height
+            * (row.as_f32() - layout.position_map.scroll_pixel_position.y / line_height);
 
         let line_origin =
             content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y);
@@ -3596,7 +3618,7 @@ impl LineWithInvisibles {
         layout: &EditorLayout,
         content_origin: gpui::Point<Pixels>,
         line_y: Pixels,
-        row: u32,
+        row: DisplayRow,
         line_height: Pixels,
         whitespace_setting: ShowWhitespaceSetting,
         cx: &mut WindowContext,
@@ -3806,19 +3828,20 @@ impl Element for EditorElement {
                 let mut scroll_position = snapshot.scroll_position();
                 // The scroll position is a fractional point, the whole number of which represents
                 // the top of the window in terms of display rows.
-                let start_row = scroll_position.y as u32;
+                let start_row = DisplayRow(scroll_position.y as u32);
                 let height_in_lines = bounds.size.height / line_height;
                 let max_row = snapshot.max_point().row();
                 let end_row = cmp::min(
                     (scroll_position.y + height_in_lines).ceil() as u32,
-                    max_row + 1,
+                    max_row.next_row().0,
                 );
+                let end_row = DisplayRow(end_row);
 
                 let buffer_rows = snapshot
-                    .buffer_rows(start_row)
+                    .display_rows(start_row)
                     .take((start_row..end_row).len());
 
-                let start_anchor = if start_row == 0 {
+                let start_anchor = if start_row == Default::default() {
                     Anchor::min()
                 } else {
                     snapshot.buffer_snapshot.anchor_before(
@@ -3914,7 +3937,7 @@ impl Element for EditorElement {
                 if let Some(newest_selection_head) = newest_selection_head {
                     let display_row = newest_selection_head.row();
                     if (start_row..end_row).contains(&display_row) {
-                        let line_layout = &line_layouts[(display_row - start_row) as usize];
+                        let line_layout = &line_layouts[display_row.minus(start_row) as usize];
                         inline_blame = self.layout_inline_blame(
                             display_row,
                             &snapshot.display_snapshot,
@@ -3929,7 +3952,11 @@ impl Element for EditorElement {
                 }
 
                 let blamed_display_rows = self.layout_blame_entries(
-                    buffer_rows,
+                    buffer_rows.map(|display_row| {
+                        display_row.map(|row| {
+                            MultiBufferRow(DisplayPoint::new(row, 0).to_point(&snapshot).row)
+                        })
+                    }),
                     em_width,
                     scroll_position,
                     line_height,
@@ -3940,7 +3967,7 @@ impl Element for EditorElement {
 
                 let scroll_max = point(
                     ((scroll_width - text_hitbox.size.width) / em_width).max(0.0),
-                    max_row as f32,
+                    max_row.as_f32(),
                 );
 
                 self.editor.update(cx, |editor, cx| {
@@ -4041,7 +4068,7 @@ impl Element for EditorElement {
                                 newest_selection_head.to_point(&snapshot.display_snapshot);
                             let buffer = snapshot
                                 .buffer_snapshot
-                                .buffer_line_for_row(newest_selection_point.row);
+                                .buffer_line_for_row(MultiBufferRow(newest_selection_point.row));
                             if let Some((buffer, range)) = buffer {
                                 let buffer_id = buffer.remote_id();
                                 let row = range.start.row;
@@ -4263,8 +4290,6 @@ impl IntoElement for EditorElement {
     }
 }
 
-type BufferRow = u32;
-
 pub struct EditorLayout {
     position_map: Arc<PositionMap>,
     hitbox: Hitbox,
@@ -4275,9 +4300,9 @@ pub struct EditorLayout {
     scrollbar_layout: Option<ScrollbarLayout>,
     mode: EditorMode,
     wrap_guides: SmallVec<[(Pixels, bool); 2]>,
-    visible_display_row_range: Range<u32>,
-    active_rows: BTreeMap<u32, bool>,
-    highlighted_rows: BTreeMap<u32, Hsla>,
+    visible_display_row_range: Range<DisplayRow>,
+    active_rows: BTreeMap<DisplayRow, bool>,
+    highlighted_rows: BTreeMap<DisplayRow, Hsla>,
     line_numbers: Vec<Option<ShapedLine>>,
     display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
     blamed_display_rows: Option<Vec<AnyElement>>,
@@ -4339,7 +4364,7 @@ impl ScrollbarLayout {
 
     fn marker_quads_for_ranges(
         &self,
-        row_ranges: impl IntoIterator<Item = ColoredRange<u32>>,
+        row_ranges: impl IntoIterator<Item = ColoredRange<DisplayRow>>,
         column: Option<usize>,
     ) -> Vec<PaintQuad> {
         struct MinMax {
@@ -4370,7 +4395,7 @@ impl ScrollbarLayout {
             )
         };
 
-        let row_to_y = |row: u32| row as f32 * self.row_height;
+        let row_to_y = |row: DisplayRow| row.as_f32() * self.row_height;
         let mut pixel_ranges = row_ranges
             .into_iter()
             .map(|range| {
@@ -4475,7 +4500,7 @@ impl PositionMap {
             (0, x)
         };
 
-        let mut exact_unclipped = DisplayPoint::new(row, column);
+        let mut exact_unclipped = DisplayPoint::new(DisplayRow(row), column);
         let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
         let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
 
@@ -4491,14 +4516,14 @@ impl PositionMap {
 }
 
 struct BlockLayout {
-    row: u32,
+    row: DisplayRow,
     element: AnyElement,
     available_space: Size<AvailableSpace>,
     style: BlockStyle,
 }
 
 fn layout_line(
-    row: u32,
+    row: DisplayRow,
     snapshot: &EditorSnapshot,
     style: &EditorStyle,
     cx: &WindowContext,
@@ -4835,10 +4860,10 @@ mod tests {
             .update_window(*window, |_, cx| {
                 element
                     .layout_line_numbers(
-                        0..6,
-                        (0..6).map(Some),
+                        DisplayRow(0)..DisplayRow(6),
+                        (0..6).map(DisplayRow).map(Some),
                         &Default::default(),
-                        Some(DisplayPoint::new(0, 0)),
+                        Some(DisplayPoint::new(DisplayRow(0), 0)),
                         &snapshot,
                         cx,
                     )
@@ -4850,39 +4875,51 @@ mod tests {
         let relative_rows = window
             .update(cx, |editor, cx| {
                 let snapshot = editor.snapshot(cx);
-                element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3))
+                element.calculate_relative_line_numbers(
+                    &snapshot,
+                    &(DisplayRow(0)..DisplayRow(6)),
+                    Some(DisplayRow(3)),
+                )
             })
             .unwrap();
-        assert_eq!(relative_rows[&0], 3);
-        assert_eq!(relative_rows[&1], 2);
-        assert_eq!(relative_rows[&2], 1);
+        assert_eq!(relative_rows[&DisplayRow(0)], 3);
+        assert_eq!(relative_rows[&DisplayRow(1)], 2);
+        assert_eq!(relative_rows[&DisplayRow(2)], 1);
         // current line has no relative number
-        assert_eq!(relative_rows[&4], 1);
-        assert_eq!(relative_rows[&5], 2);
+        assert_eq!(relative_rows[&DisplayRow(4)], 1);
+        assert_eq!(relative_rows[&DisplayRow(5)], 2);
 
         // works if cursor is before screen
         let relative_rows = window
             .update(cx, |editor, cx| {
                 let snapshot = editor.snapshot(cx);
-                element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1))
+                element.calculate_relative_line_numbers(
+                    &snapshot,
+                    &(DisplayRow(3)..DisplayRow(6)),
+                    Some(DisplayRow(1)),
+                )
             })
             .unwrap();
         assert_eq!(relative_rows.len(), 3);
-        assert_eq!(relative_rows[&3], 2);
-        assert_eq!(relative_rows[&4], 3);
-        assert_eq!(relative_rows[&5], 4);
+        assert_eq!(relative_rows[&DisplayRow(3)], 2);
+        assert_eq!(relative_rows[&DisplayRow(4)], 3);
+        assert_eq!(relative_rows[&DisplayRow(5)], 4);
 
         // works if cursor is after screen
         let relative_rows = window
             .update(cx, |editor, cx| {
                 let snapshot = editor.snapshot(cx);
-                element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6))
+                element.calculate_relative_line_numbers(
+                    &snapshot,
+                    &(DisplayRow(0)..DisplayRow(3)),
+                    Some(DisplayRow(6)),
+                )
             })
             .unwrap();
         assert_eq!(relative_rows.len(), 3);
-        assert_eq!(relative_rows[&0], 5);
-        assert_eq!(relative_rows[&1], 4);
-        assert_eq!(relative_rows[&2], 3);
+        assert_eq!(relative_rows[&DisplayRow(0)], 5);
+        assert_eq!(relative_rows[&DisplayRow(1)], 4);
+        assert_eq!(relative_rows[&DisplayRow(2)], 3);
     }
 
     #[gpui::test]
@@ -4918,30 +4955,39 @@ mod tests {
         let local_selections = &state.selections[0].1;
         assert_eq!(local_selections.len(), 3);
         // moves cursor back one line
-        assert_eq!(local_selections[0].head, DisplayPoint::new(0, 6));
+        assert_eq!(
+            local_selections[0].head,
+            DisplayPoint::new(DisplayRow(0), 6)
+        );
         assert_eq!(
             local_selections[0].range,
-            DisplayPoint::new(0, 0)..DisplayPoint::new(1, 0)
+            DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0)
         );
 
         // moves cursor back one column
         assert_eq!(
             local_selections[1].range,
-            DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3)
+            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 3)
+        );
+        assert_eq!(
+            local_selections[1].head,
+            DisplayPoint::new(DisplayRow(3), 2)
         );
-        assert_eq!(local_selections[1].head, DisplayPoint::new(3, 2));
 
         // leaves cursor on the max point
         assert_eq!(
             local_selections[2].range,
-            DisplayPoint::new(5, 6)..DisplayPoint::new(6, 0)
+            DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(6), 0)
+        );
+        assert_eq!(
+            local_selections[2].head,
+            DisplayPoint::new(DisplayRow(6), 0)
         );
-        assert_eq!(local_selections[2].head, DisplayPoint::new(6, 0));
 
         // active lines does not include 1 (even though the range of the selection does)
         assert_eq!(
-            state.active_rows.keys().cloned().collect::<Vec<u32>>(),
-            vec![0, 3, 5, 6]
+            state.active_rows.keys().cloned().collect::<Vec<_>>(),
+            vec![DisplayRow(0), DisplayRow(3), DisplayRow(5), DisplayRow(6)]
         );
 
         // multi-buffer support

crates/editor/src/git.rs šŸ”—

@@ -4,29 +4,29 @@ use std::ops::Range;
 
 use git::diff::{DiffHunk, DiffHunkStatus};
 use language::Point;
-use multi_buffer::Anchor;
+use multi_buffer::{Anchor, MultiBufferRow};
 
 use crate::{
     display_map::{DisplaySnapshot, ToDisplayPoint},
-    AnchorRangeExt,
+    hunk_status, AnchorRangeExt, DisplayRow,
 };
 
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum DisplayDiffHunk {
     Folded {
-        display_row: u32,
+        display_row: DisplayRow,
     },
 
     Unfolded {
         diff_base_byte_range: Range<usize>,
-        display_row_range: Range<u32>,
+        display_row_range: Range<DisplayRow>,
         multi_buffer_range: Range<Anchor>,
         status: DiffHunkStatus,
     },
 }
 
 impl DisplayDiffHunk {
-    pub fn start_display_row(&self) -> u32 {
+    pub fn start_display_row(&self) -> DisplayRow {
         match self {
             &DisplayDiffHunk::Folded { display_row } => display_row,
             DisplayDiffHunk::Unfolded {
@@ -35,7 +35,7 @@ impl DisplayDiffHunk {
         }
     }
 
-    pub fn contains_display_row(&self, display_row: u32) -> bool {
+    pub fn contains_display_row(&self, display_row: DisplayRow) -> bool {
         let range = match self {
             &DisplayDiffHunk::Folded { display_row } => display_row..=display_row,
 
@@ -48,21 +48,26 @@ impl DisplayDiffHunk {
     }
 }
 
-pub fn diff_hunk_to_display(hunk: &DiffHunk<u32>, snapshot: &DisplaySnapshot) -> DisplayDiffHunk {
-    let hunk_start_point = Point::new(hunk.associated_range.start, 0);
-    let hunk_start_point_sub = Point::new(hunk.associated_range.start.saturating_sub(1), 0);
+pub fn diff_hunk_to_display(
+    hunk: &DiffHunk<MultiBufferRow>,
+    snapshot: &DisplaySnapshot,
+) -> DisplayDiffHunk {
+    let hunk_start_point = Point::new(hunk.associated_range.start.0, 0);
+    let hunk_start_point_sub = Point::new(hunk.associated_range.start.0.saturating_sub(1), 0);
     let hunk_end_point_sub = Point::new(
         hunk.associated_range
             .end
+            .0
             .saturating_sub(1)
-            .max(hunk.associated_range.start),
+            .max(hunk.associated_range.start.0),
         0,
     );
 
-    let is_removal = hunk.status() == DiffHunkStatus::Removed;
+    let status = hunk_status(hunk);
+    let is_removal = status == DiffHunkStatus::Removed;
 
-    let folds_start = Point::new(hunk.associated_range.start.saturating_sub(2), 0);
-    let folds_end = Point::new(hunk.associated_range.end + 2, 0);
+    let folds_start = Point::new(hunk.associated_range.start.0.saturating_sub(2), 0);
+    let folds_end = Point::new(hunk.associated_range.end.0 + 2, 0);
     let folds_range = folds_start..folds_end;
 
     let containing_fold = snapshot.folds_in_range(folds_range).find(|fold| {
@@ -83,7 +88,7 @@ pub fn diff_hunk_to_display(hunk: &DiffHunk<u32>, snapshot: &DisplaySnapshot) ->
         let start = hunk_start_point.to_display_point(snapshot).row();
 
         let hunk_end_row = hunk.associated_range.end.max(hunk.associated_range.start);
-        let hunk_end_point = Point::new(hunk_end_row, 0);
+        let hunk_end_point = Point::new(hunk_end_row.0, 0);
 
         let multi_buffer_start = snapshot.buffer_snapshot.anchor_after(hunk_start_point);
         let multi_buffer_end = snapshot.buffer_snapshot.anchor_before(hunk_end_point);
@@ -92,7 +97,7 @@ pub fn diff_hunk_to_display(hunk: &DiffHunk<u32>, snapshot: &DisplaySnapshot) ->
         DisplayDiffHunk::Unfolded {
             display_row_range: start..end,
             multi_buffer_range: multi_buffer_start..multi_buffer_end,
-            status: hunk.status(),
+            status,
             diff_base_byte_range: hunk.diff_base_byte_range.clone(),
         }
     }
@@ -100,11 +105,11 @@ pub fn diff_hunk_to_display(hunk: &DiffHunk<u32>, snapshot: &DisplaySnapshot) ->
 
 #[cfg(test)]
 mod tests {
-    use crate::editor_tests::init_test;
     use crate::Point;
+    use crate::{editor_tests::init_test, hunk_status};
     use gpui::{Context, TestAppContext};
     use language::Capability::ReadWrite;
-    use multi_buffer::{ExcerptRange, MultiBuffer};
+    use multi_buffer::{ExcerptRange, MultiBuffer, MultiBufferRow};
     use project::{FakeFs, Project};
     use unindent::Unindent;
     #[gpui::test]
@@ -257,26 +262,41 @@ mod tests {
         );
 
         let expected = [
-            (DiffHunkStatus::Modified, 1..2),
-            (DiffHunkStatus::Modified, 2..3),
+            (
+                DiffHunkStatus::Modified,
+                MultiBufferRow(1)..MultiBufferRow(2),
+            ),
+            (
+                DiffHunkStatus::Modified,
+                MultiBufferRow(2)..MultiBufferRow(3),
+            ),
             //TODO: Define better when and where removed hunks show up at range extremities
-            (DiffHunkStatus::Removed, 6..6),
-            (DiffHunkStatus::Removed, 8..8),
-            (DiffHunkStatus::Added, 10..11),
+            (
+                DiffHunkStatus::Removed,
+                MultiBufferRow(6)..MultiBufferRow(6),
+            ),
+            (
+                DiffHunkStatus::Removed,
+                MultiBufferRow(8)..MultiBufferRow(8),
+            ),
+            (
+                DiffHunkStatus::Added,
+                MultiBufferRow(10)..MultiBufferRow(11),
+            ),
         ];
 
         assert_eq!(
             snapshot
-                .git_diff_hunks_in_range(0..12)
-                .map(|hunk| (hunk.status(), hunk.associated_range))
+                .git_diff_hunks_in_range(MultiBufferRow(0)..MultiBufferRow(12))
+                .map(|hunk| (hunk_status(&hunk), hunk.associated_range))
                 .collect::<Vec<_>>(),
             &expected,
         );
 
         assert_eq!(
             snapshot
-                .git_diff_hunks_in_range_rev(0..12)
-                .map(|hunk| (hunk.status(), hunk.associated_range))
+                .git_diff_hunks_in_range_rev(MultiBufferRow(0)..MultiBufferRow(12))
+                .map(|hunk| (hunk_status(&hunk), hunk.associated_range))
                 .collect::<Vec<_>>(),
             expected
                 .iter()

crates/editor/src/git/blame.rs šŸ”—

@@ -8,6 +8,7 @@ use git::{
 };
 use gpui::{Model, ModelContext, Subscription, Task};
 use language::{markdown, Bias, Buffer, BufferSnapshot, Edit, LanguageRegistry, ParsedMarkdown};
+use multi_buffer::MultiBufferRow;
 use project::{Item, Project};
 use smallvec::SmallVec;
 use sum_tree::SumTree;
@@ -185,7 +186,7 @@ impl GitBlame {
 
     pub fn blame_for_rows<'a>(
         &'a mut self,
-        rows: impl 'a + IntoIterator<Item = Option<u32>>,
+        rows: impl 'a + IntoIterator<Item = Option<MultiBufferRow>>,
         cx: &mut ModelContext<Self>,
     ) -> impl 'a + Iterator<Item = Option<BlameEntry>> {
         self.sync(cx);
@@ -193,7 +194,7 @@ impl GitBlame {
         let mut cursor = self.entries.cursor::<u32>();
         rows.into_iter().map(move |row| {
             let row = row?;
-            cursor.seek_forward(&row, Bias::Right, &());
+            cursor.seek_forward(&row.0, Bias::Right, &());
             cursor.item()?.blame.clone()
         })
     }
@@ -532,7 +533,7 @@ mod tests {
         ($blame:expr, $rows:expr, $expected:expr, $cx:expr) => {
             assert_eq!(
                 $blame
-                    .blame_for_rows($rows.map(Some), $cx)
+                    .blame_for_rows($rows.map(MultiBufferRow).map(Some), $cx)
                     .collect::<Vec<_>>(),
                 $expected
             );
@@ -597,7 +598,7 @@ mod tests {
         blame.update(cx, |blame, cx| {
             assert_eq!(
                 blame
-                    .blame_for_rows((0..1).map(Some), cx)
+                    .blame_for_rows((0..1).map(MultiBufferRow).map(Some), cx)
                     .collect::<Vec<_>>(),
                 vec![None]
             );
@@ -661,7 +662,7 @@ mod tests {
             // All lines
             assert_eq!(
                 blame
-                    .blame_for_rows((0..8).map(Some), cx)
+                    .blame_for_rows((0..8).map(MultiBufferRow).map(Some), cx)
                     .collect::<Vec<_>>(),
                 vec![
                     Some(blame_entry("1b1b1b", 0..1)),
@@ -677,7 +678,7 @@ mod tests {
             // Subset of lines
             assert_eq!(
                 blame
-                    .blame_for_rows((1..4).map(Some), cx)
+                    .blame_for_rows((1..4).map(MultiBufferRow).map(Some), cx)
                     .collect::<Vec<_>>(),
                 vec![
                     Some(blame_entry("0d0d0d", 1..2)),
@@ -688,7 +689,7 @@ mod tests {
             // Subset of lines, with some not displayed
             assert_eq!(
                 blame
-                    .blame_for_rows(vec![Some(1), None, None], cx)
+                    .blame_for_rows(vec![Some(MultiBufferRow(1)), None, None], cx)
                     .collect::<Vec<_>>(),
                 vec![Some(blame_entry("0d0d0d", 1..2)), None, None]
             );

crates/editor/src/hover_popover.rs šŸ”—

@@ -1,8 +1,8 @@
 use crate::{
     display_map::{InlayOffset, ToDisplayPoint},
     hover_links::{InlayHighlight, RangeInEditor},
-    Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,
-    ExcerptId, Hover, RangeToAnchorExt,
+    Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,
+    EditorStyle, ExcerptId, Hover, RangeToAnchorExt,
 };
 use futures::{stream::FuturesUnordered, FutureExt};
 use gpui::{
@@ -440,7 +440,7 @@ impl HoverState {
         &mut self,
         snapshot: &EditorSnapshot,
         style: &EditorStyle,
-        visible_rows: Range<u32>,
+        visible_rows: Range<DisplayRow>,
         max_size: Size<Pixels>,
         workspace: Option<WeakView<Workspace>>,
         cx: &mut ViewContext<Editor>,

crates/editor/src/hunk_diff.rs šŸ”—

@@ -7,7 +7,9 @@ use collections::{hash_map, HashMap, HashSet};
 use git::diff::{DiffHunk, DiffHunkStatus};
 use gpui::{AppContext, Hsla, Model, Task, View};
 use language::Buffer;
-use multi_buffer::{Anchor, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToPoint};
+use multi_buffer::{
+    Anchor, ExcerptRange, MultiBuffer, MultiBufferRow, MultiBufferSnapshot, ToPoint,
+};
 use text::{BufferId, Point};
 use ui::{
     div, ActiveTheme, Context as _, IntoElement, ParentElement, Styled, ViewContext, VisualContext,
@@ -16,9 +18,9 @@ use util::{debug_panic, RangeExt};
 
 use crate::{
     git::{diff_hunk_to_display, DisplayDiffHunk},
-    hunks_for_selections, BlockDisposition, BlockId, BlockProperties, BlockStyle, DiffRowHighlight,
-    Editor, EditorSnapshot, ExpandAllHunkDiffs, RangeToAnchorExt, RevertSelectedHunks,
-    ToDisplayPoint, ToggleHunkDiff,
+    hunk_status, hunks_for_selections, BlockDisposition, BlockId, BlockProperties, BlockStyle,
+    DiffRowHighlight, Editor, EditorSnapshot, ExpandAllHunkDiffs, RangeToAnchorExt,
+    RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
 };
 
 #[derive(Debug, Clone)]
@@ -90,11 +92,11 @@ impl Editor {
         let hunks = snapshot
             .display_snapshot
             .buffer_snapshot
-            .git_diff_hunks_in_range(0..u32::MAX)
+            .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
             .filter(|hunk| {
-                let hunk_display_row_range = Point::new(hunk.associated_range.start, 0)
+                let hunk_display_row_range = Point::new(hunk.associated_range.start.0, 0)
                     .to_display_point(&snapshot.display_snapshot)
-                    ..Point::new(hunk.associated_range.end, 0)
+                    ..Point::new(hunk.associated_range.end.0, 0)
                         .to_display_point(&snapshot.display_snapshot);
                 let row_range_end =
                     display_rows_with_expanded_hunks.get(&hunk_display_row_range.start.row());
@@ -105,7 +107,7 @@ impl Editor {
 
     fn toggle_hunks_expanded(
         &mut self,
-        hunks_to_toggle: Vec<DiffHunk<u32>>,
+        hunks_to_toggle: Vec<DiffHunk<MultiBufferRow>>,
         cx: &mut ViewContext<Self>,
     ) {
         let previous_toggle_task = self.expanded_hunks.hunk_update_tasks.remove(&None);
@@ -176,10 +178,10 @@ impl Editor {
                     });
                     for remaining_hunk in hunks_to_toggle {
                         let remaining_hunk_point_range =
-                            Point::new(remaining_hunk.associated_range.start, 0)
-                                ..Point::new(remaining_hunk.associated_range.end, 0);
+                            Point::new(remaining_hunk.associated_range.start.0, 0)
+                                ..Point::new(remaining_hunk.associated_range.end.0, 0);
                         hunks_to_expand.push(HunkToExpand {
-                            status: remaining_hunk.status(),
+                            status: hunk_status(&remaining_hunk),
                             multi_buffer_range: remaining_hunk_point_range
                                 .to_anchors(&snapshot.buffer_snapshot),
                             diff_base_byte_range: remaining_hunk.diff_base_byte_range.clone(),
@@ -374,9 +376,10 @@ impl Editor {
                     }
 
                     let snapshot = editor.snapshot(cx);
-                    let buffer_snapshot = buffer.read(cx).snapshot();
-                    let mut recalculated_hunks = buffer_snapshot
-                        .git_diff_hunks_in_row_range(0..u32::MAX)
+                    let mut recalculated_hunks = snapshot
+                        .buffer_snapshot
+                        .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
+                        .filter(|hunk| hunk.buffer_id == buffer_id)
                         .fuse()
                         .peekable();
                     let mut highlights_to_remove =
@@ -402,7 +405,7 @@ impl Editor {
                                     .to_display_point(&snapshot)
                                     .row();
                             while let Some(buffer_hunk) = recalculated_hunks.peek() {
-                                match diff_hunk_to_display(buffer_hunk, &snapshot) {
+                                match diff_hunk_to_display(&buffer_hunk, &snapshot) {
                                     DisplayDiffHunk::Folded { display_row } => {
                                         recalculated_hunks.next();
                                         if !expanded_hunk.folded
@@ -441,7 +444,7 @@ impl Editor {
                                         } else {
                                             if !expanded_hunk.folded
                                                 && expanded_hunk_display_range == hunk_display_range
-                                                && expanded_hunk.status == buffer_hunk.status()
+                                                && expanded_hunk.status == hunk_status(buffer_hunk)
                                                 && expanded_hunk.diff_base_byte_range
                                                     == buffer_hunk.diff_base_byte_range
                                             {
@@ -614,15 +617,17 @@ fn editor_with_deleted_text(
         editor
     });
 
-    let editor_height = editor.update(cx, |editor, cx| editor.max_point(cx).row() as u8);
+    let editor_height = editor.update(cx, |editor, cx| editor.max_point(cx).row().0 as u8);
     (editor_height, editor)
 }
 
 fn buffer_diff_hunk(
     buffer_snapshot: &MultiBufferSnapshot,
     row_range: Range<Point>,
-) -> Option<DiffHunk<u32>> {
-    let mut hunks = buffer_snapshot.git_diff_hunks_in_range(row_range.start.row..row_range.end.row);
+) -> Option<DiffHunk<MultiBufferRow>> {
+    let mut hunks = buffer_snapshot.git_diff_hunks_in_range(
+        MultiBufferRow(row_range.start.row)..MultiBufferRow(row_range.end.row),
+    );
     let hunk = hunks.next()?;
     let second_hunk = hunks.next();
     if second_hunk.is_none() {

crates/editor/src/movement.rs šŸ”—

@@ -2,10 +2,12 @@
 //! in editor given a given motion (e.g. it handles converting a "move left" command into coordinates in editor). It is exposed mostly for use by vim crate.
 
 use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
-use crate::{char_kind, scroll::ScrollAnchor, CharKind, EditorStyle, ToOffset, ToPoint};
+use crate::{
+    char_kind, scroll::ScrollAnchor, CharKind, DisplayRow, EditorStyle, RowExt, ToOffset, ToPoint,
+};
 use gpui::{px, Pixels, WindowTextSystem};
 use language::Point;
-use multi_buffer::MultiBufferSnapshot;
+use multi_buffer::{MultiBufferRow, MultiBufferSnapshot};
 use serde::Deserialize;
 
 use std::{ops::Range, sync::Arc};
@@ -35,7 +37,7 @@ pub struct TextLayoutDetails {
 pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
     if point.column() > 0 {
         *point.column_mut() -= 1;
-    } else if point.row() > 0 {
+    } else if point.row().0 > 0 {
         *point.row_mut() -= 1;
         *point.column_mut() = map.line_len(point.row());
     }
@@ -127,7 +129,7 @@ pub(crate) fn up_by_rows(
         _ => map.x_for_display_point(start, text_layout_details),
     };
 
-    let prev_row = start.row().saturating_sub(row_count);
+    let prev_row = DisplayRow(start.row().0.saturating_sub(row_count));
     let mut point = map.clip_point(
         DisplayPoint::new(prev_row, map.line_len(prev_row)),
         Bias::Left,
@@ -137,7 +139,7 @@ pub(crate) fn up_by_rows(
     } else if preserve_column_at_start {
         return (start, goal);
     } else {
-        point = DisplayPoint::new(0, 0);
+        point = DisplayPoint::new(DisplayRow(0), 0);
         goal_x = px(0.);
     }
 
@@ -166,7 +168,7 @@ pub(crate) fn down_by_rows(
         _ => map.x_for_display_point(start, text_layout_details),
     };
 
-    let new_row = start.row() + row_count;
+    let new_row = DisplayRow(start.row().0 + row_count);
     let mut point = map.clip_point(DisplayPoint::new(new_row, 0), Bias::Right);
     if point.row() > start.row() {
         *point.column_mut() = map.display_column_for_x(point.row(), goal_x, text_layout_details)
@@ -220,7 +222,9 @@ pub fn indented_line_beginning(
     let soft_line_start = map.clip_point(DisplayPoint::new(display_point.row(), 0), Bias::Right);
     let indent_start = Point::new(
         point.row,
-        map.buffer_snapshot.indent_size_for_line(point.row).len,
+        map.buffer_snapshot
+            .indent_size_for_line(MultiBufferRow(point.row))
+            .len,
     )
     .to_display_point(map);
     let line_start = map.prev_line_boundary(point).1;
@@ -326,7 +330,7 @@ pub fn start_of_paragraph(
 
     let mut found_non_blank_line = false;
     for row in (0..point.row + 1).rev() {
-        let blank = map.buffer_snapshot.is_line_blank(row);
+        let blank = map.buffer_snapshot.is_line_blank(MultiBufferRow(row));
         if found_non_blank_line && blank {
             if count <= 1 {
                 return Point::new(row, 0).to_display_point(map);
@@ -349,13 +353,13 @@ pub fn end_of_paragraph(
     mut count: usize,
 ) -> DisplayPoint {
     let point = display_point.to_point(map);
-    if point.row == map.max_buffer_row() {
+    if point.row == map.max_buffer_row().0 {
         return map.max_point();
     }
 
     let mut found_non_blank_line = false;
-    for row in point.row..map.max_buffer_row() + 1 {
-        let blank = map.buffer_snapshot.is_line_blank(row);
+    for row in point.row..map.max_buffer_row().next_row().0 {
+        let blank = map.buffer_snapshot.is_line_blank(MultiBufferRow(row));
         if found_non_blank_line && blank {
             if count <= 1 {
                 return Point::new(row, 0).to_display_point(map);
@@ -549,13 +553,16 @@ pub fn split_display_range_by_lines(
 
     let mut start = range.start;
     // Loop over all the covered rows until the one containing the range end
-    for row in range.start.row()..range.end.row() {
-        let row_end_column = map.line_len(row);
-        let end = map.clip_point(DisplayPoint::new(row, row_end_column), Bias::Left);
+    for row in range.start.row().0..range.end.row().0 {
+        let row_end_column = map.line_len(DisplayRow(row));
+        let end = map.clip_point(
+            DisplayPoint::new(DisplayRow(row), row_end_column),
+            Bias::Left,
+        );
         if start != end {
             result.push(start..end);
         }
-        start = map.clip_point(DisplayPoint::new(row + 1, 0), Bias::Left);
+        start = map.clip_point(DisplayPoint::new(DisplayRow(row + 1), 0), Bias::Left);
     }
 
     // Add the final range from the start of the last end to the original range end.
@@ -570,7 +577,7 @@ mod tests {
     use crate::{
         display_map::Inlay,
         test::{editor_test_context::EditorTestContext, marked_display_snapshot},
-        Buffer, DisplayMap, ExcerptRange, InlayId, MultiBuffer,
+        Buffer, DisplayMap, DisplayRow, ExcerptRange, InlayId, MultiBuffer,
     };
     use gpui::{font, Context as _};
     use language::Capability;
@@ -900,126 +907,126 @@ mod tests {
 
             assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn");
 
-            let col_2_x =
-                snapshot.x_for_display_point(DisplayPoint::new(2, 2), &text_layout_details);
+            let col_2_x = snapshot
+                .x_for_display_point(DisplayPoint::new(DisplayRow(2), 2), &text_layout_details);
 
             // Can't move up into the first excerpt's header
             assert_eq!(
                 up(
                     &snapshot,
-                    DisplayPoint::new(2, 2),
+                    DisplayPoint::new(DisplayRow(2), 2),
                     SelectionGoal::HorizontalPosition(col_2_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(2, 0),
+                    DisplayPoint::new(DisplayRow(2), 0),
                     SelectionGoal::HorizontalPosition(0.0)
                 ),
             );
             assert_eq!(
                 up(
                     &snapshot,
-                    DisplayPoint::new(2, 0),
+                    DisplayPoint::new(DisplayRow(2), 0),
                     SelectionGoal::None,
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(2, 0),
+                    DisplayPoint::new(DisplayRow(2), 0),
                     SelectionGoal::HorizontalPosition(0.0)
                 ),
             );
 
-            let col_4_x =
-                snapshot.x_for_display_point(DisplayPoint::new(3, 4), &text_layout_details);
+            let col_4_x = snapshot
+                .x_for_display_point(DisplayPoint::new(DisplayRow(3), 4), &text_layout_details);
 
             // Move up and down within first excerpt
             assert_eq!(
                 up(
                     &snapshot,
-                    DisplayPoint::new(3, 4),
+                    DisplayPoint::new(DisplayRow(3), 4),
                     SelectionGoal::HorizontalPosition(col_4_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(2, 3),
+                    DisplayPoint::new(DisplayRow(2), 3),
                     SelectionGoal::HorizontalPosition(col_4_x.0)
                 ),
             );
             assert_eq!(
                 down(
                     &snapshot,
-                    DisplayPoint::new(2, 3),
+                    DisplayPoint::new(DisplayRow(2), 3),
                     SelectionGoal::HorizontalPosition(col_4_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(3, 4),
+                    DisplayPoint::new(DisplayRow(3), 4),
                     SelectionGoal::HorizontalPosition(col_4_x.0)
                 ),
             );
 
-            let col_5_x =
-                snapshot.x_for_display_point(DisplayPoint::new(6, 5), &text_layout_details);
+            let col_5_x = snapshot
+                .x_for_display_point(DisplayPoint::new(DisplayRow(6), 5), &text_layout_details);
 
             // Move up and down across second excerpt's header
             assert_eq!(
                 up(
                     &snapshot,
-                    DisplayPoint::new(6, 5),
+                    DisplayPoint::new(DisplayRow(6), 5),
                     SelectionGoal::HorizontalPosition(col_5_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(3, 4),
+                    DisplayPoint::new(DisplayRow(3), 4),
                     SelectionGoal::HorizontalPosition(col_5_x.0)
                 ),
             );
             assert_eq!(
                 down(
                     &snapshot,
-                    DisplayPoint::new(3, 4),
+                    DisplayPoint::new(DisplayRow(3), 4),
                     SelectionGoal::HorizontalPosition(col_5_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(6, 5),
+                    DisplayPoint::new(DisplayRow(6), 5),
                     SelectionGoal::HorizontalPosition(col_5_x.0)
                 ),
             );
 
-            let max_point_x =
-                snapshot.x_for_display_point(DisplayPoint::new(7, 2), &text_layout_details);
+            let max_point_x = snapshot
+                .x_for_display_point(DisplayPoint::new(DisplayRow(7), 2), &text_layout_details);
 
             // Can't move down off the end
             assert_eq!(
                 down(
                     &snapshot,
-                    DisplayPoint::new(7, 0),
+                    DisplayPoint::new(DisplayRow(7), 0),
                     SelectionGoal::HorizontalPosition(0.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(7, 2),
+                    DisplayPoint::new(DisplayRow(7), 2),
                     SelectionGoal::HorizontalPosition(max_point_x.0)
                 ),
             );
             assert_eq!(
                 down(
                     &snapshot,
-                    DisplayPoint::new(7, 2),
+                    DisplayPoint::new(DisplayRow(7), 2),
                     SelectionGoal::HorizontalPosition(max_point_x.0),
                     false,
                     &text_layout_details
                 ),
                 (
-                    DisplayPoint::new(7, 2),
+                    DisplayPoint::new(DisplayRow(7), 2),
                     SelectionGoal::HorizontalPosition(max_point_x.0)
                 ),
             );

crates/editor/src/scroll.rs šŸ”—

@@ -6,8 +6,8 @@ use crate::{
     display_map::{DisplaySnapshot, ToDisplayPoint},
     hover_popover::hide_hover,
     persistence::DB,
-    Anchor, DisplayPoint, Editor, EditorEvent, EditorMode, EditorSettings, InlayHintRefreshReason,
-    MultiBufferSnapshot, ToPoint,
+    Anchor, DisplayPoint, DisplayRow, Editor, EditorEvent, EditorMode, EditorSettings,
+    InlayHintRefreshReason, MultiBufferSnapshot, RowExt, ToPoint,
 };
 pub use autoscroll::{Autoscroll, AutoscrollStrategy};
 use gpui::{point, px, AppContext, Entity, Global, Pixels, Task, ViewContext, WindowContext};
@@ -48,7 +48,7 @@ impl ScrollAnchor {
         if self.anchor == Anchor::min() {
             scroll_position.y = 0.;
         } else {
-            let scroll_top = self.anchor.to_display_point(snapshot).row() as f32;
+            let scroll_top = self.anchor.to_display_point(snapshot).row().as_f32();
             scroll_position.y = scroll_top + scroll_position.y;
         }
         scroll_position
@@ -200,7 +200,7 @@ impl ScrollManager {
             )
         } else {
             let scroll_top_buffer_point =
-                DisplayPoint::new(scroll_position.y as u32, 0).to_point(&map);
+                DisplayPoint::new(DisplayRow(scroll_position.y as u32), 0).to_point(&map);
             let top_anchor = map
                 .buffer_snapshot
                 .anchor_at(scroll_top_buffer_point, Bias::Right);
@@ -210,7 +210,7 @@ impl ScrollManager {
                     anchor: top_anchor,
                     offset: point(
                         scroll_position.x.max(0.),
-                        scroll_position.y - top_anchor.to_display_point(&map).row() as f32,
+                        scroll_position.y - top_anchor.to_display_point(&map).row().as_f32(),
                     ),
                 },
                 scroll_top_buffer_point.row,
@@ -472,7 +472,7 @@ impl Editor {
         }
 
         if let Some(visible_lines) = self.visible_line_count() {
-            if newest_head.row() < screen_top.row() + visible_lines as u32 {
+            if newest_head.row() < DisplayRow(screen_top.row().0 + visible_lines as u32) {
                 return Ordering::Equal;
             }
         }

crates/editor/src/scroll/actions.rs šŸ”—

@@ -37,7 +37,7 @@ impl Editor {
         let scroll_margin_rows = self.vertical_scroll_margin() as u32;
 
         let mut new_screen_top = self.selections.newest_display(cx).head();
-        *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(scroll_margin_rows);
+        *new_screen_top.row_mut() = new_screen_top.row().0.saturating_sub(scroll_margin_rows);
         *new_screen_top.column_mut() = 0;
         let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
         let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
@@ -60,7 +60,7 @@ impl Editor {
         };
 
         let mut new_screen_top = self.selections.newest_display(cx).head();
-        *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(visible_rows / 2);
+        *new_screen_top.row_mut() = new_screen_top.row().0.saturating_sub(visible_rows / 2);
         *new_screen_top.column_mut() = 0;
         let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
         let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
@@ -86,6 +86,7 @@ impl Editor {
         let mut new_screen_top = self.selections.newest_display(cx).head();
         *new_screen_top.row_mut() = new_screen_top
             .row()
+            .0
             .saturating_sub(visible_rows.saturating_sub(scroll_margin_rows));
         *new_screen_top.column_mut() = 0;
         let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);

crates/editor/src/scroll/autoscroll.rs šŸ”—

@@ -5,7 +5,8 @@ use gpui::{px, Bounds, Pixels, ViewContext};
 use language::Point;
 
 use crate::{
-    display_map::ToDisplayPoint, DiffRowHighlight, Editor, EditorMode, LineWithInvisibles,
+    display_map::ToDisplayPoint, DiffRowHighlight, DisplayRow, Editor, EditorMode,
+    LineWithInvisibles, RowExt,
 };
 
 #[derive(PartialEq, Eq, Clone, Copy)]
@@ -88,9 +89,9 @@ impl Editor {
             }
         }
         let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
-            (display_map.max_point().row() as f32 - visible_lines + 1.).max(0.)
+            (display_map.max_point().row().as_f32() - visible_lines + 1.).max(0.)
         } else {
-            display_map.max_point().row() as f32
+            display_map.max_point().row().as_f32()
         };
         if scroll_position.y > max_scroll_top {
             scroll_position.y = max_scroll_top;
@@ -113,7 +114,7 @@ impl Editor {
             )
             .first_entry()
         {
-            target_top = *first_highlighted_row.key() as f32;
+            target_top = first_highlighted_row.key().as_f32();
             target_bottom = target_top + 1.;
         } else {
             let selections = self.selections.all::<Point>(cx);
@@ -122,14 +123,16 @@ impl Editor {
                 .unwrap()
                 .head()
                 .to_display_point(&display_map)
-                .row() as f32;
+                .row()
+                .as_f32();
             target_bottom = selections
                 .last()
                 .unwrap()
                 .head()
                 .to_display_point(&display_map)
-                .row() as f32
-                + 1.0;
+                .row()
+                .next_row()
+                .as_f32();
 
             // If the selections can't all fit on screen, scroll to the newest.
             if autoscroll == Autoscroll::newest()
@@ -141,7 +144,8 @@ impl Editor {
                     .unwrap()
                     .head()
                     .to_display_point(&display_map)
-                    .row() as f32;
+                    .row()
+                    .as_f32();
                 target_top = newest_selection_top;
                 target_bottom = newest_selection_top + 1.;
             }
@@ -227,7 +231,7 @@ impl Editor {
 
     pub(crate) fn autoscroll_horizontally(
         &mut self,
-        start_row: u32,
+        start_row: DisplayRow,
         viewport_width: Pixels,
         scroll_width: Pixels,
         max_glyph_width: Pixels,
@@ -245,16 +249,18 @@ impl Editor {
             target_right = px(0.);
             for selection in selections {
                 let head = selection.head().to_display_point(&display_map);
-                if head.row() >= start_row && head.row() < start_row + layouts.len() as u32 {
+                if head.row() >= start_row
+                    && head.row() < DisplayRow(start_row.0 + layouts.len() as u32)
+                {
                     let start_column = head.column().saturating_sub(3);
                     let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3);
                     target_left = target_left.min(
-                        layouts[(head.row() - start_row) as usize]
+                        layouts[head.row().minus(start_row) as usize]
                             .line
                             .x_for_index(start_column as usize),
                     );
                     target_right = target_right.max(
-                        layouts[(head.row() - start_row) as usize]
+                        layouts[head.row().minus(start_row) as usize]
                             .line
                             .x_for_index(end_column as usize)
                             + max_glyph_width,

crates/editor/src/selections_collection.rs šŸ”—

@@ -14,7 +14,8 @@ use util::post_inc;
 use crate::{
     display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
     movement::TextLayoutDetails,
-    Anchor, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, ToOffset,
+    Anchor, DisplayPoint, DisplayRow, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode,
+    ToOffset,
 };
 
 #[derive(Debug, Clone)]
@@ -308,7 +309,7 @@ impl SelectionsCollection {
     pub fn build_columnar_selection(
         &mut self,
         display_map: &DisplaySnapshot,
-        row: u32,
+        row: DisplayRow,
         positions: &Range<Pixels>,
         reversed: bool,
         text_layout_details: &TextLayoutDetails,

crates/editor/src/test.rs šŸ”—

@@ -81,23 +81,30 @@ pub fn editor_hunks(
     editor: &Editor,
     snapshot: &DisplaySnapshot,
     cx: &mut ViewContext<'_, Editor>,
-) -> Vec<(String, git::diff::DiffHunkStatus, core::ops::Range<u32>)> {
+) -> Vec<(
+    String,
+    git::diff::DiffHunkStatus,
+    std::ops::Range<crate::DisplayRow>,
+)> {
+    use multi_buffer::MultiBufferRow;
     use text::Point;
 
+    use crate::hunk_status;
+
     snapshot
         .buffer_snapshot
-        .git_diff_hunks_in_range(0..u32::MAX)
+        .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
         .map(|hunk| {
-            let display_range = Point::new(hunk.associated_range.start, 0)
+            let display_range = Point::new(hunk.associated_range.start.0, 0)
                 .to_display_point(snapshot)
                 .row()
-                ..Point::new(hunk.associated_range.end, 0)
+                ..Point::new(hunk.associated_range.end.0, 0)
                     .to_display_point(snapshot)
                     .row();
             let (_, buffer, _) = editor
                 .buffer()
                 .read(cx)
-                .excerpt_containing(Point::new(hunk.associated_range.start, 0), cx)
+                .excerpt_containing(Point::new(hunk.associated_range.start.0, 0), cx)
                 .expect("no excerpt for expanded buffer's hunk start");
             let diff_base = buffer
                 .read(cx)
@@ -105,7 +112,7 @@ pub fn editor_hunks(
                 .expect("should have a diff base for expanded hunk")
                 .slice(hunk.diff_base_byte_range.clone())
                 .to_string();
-            (diff_base, hunk.status(), display_range)
+            (diff_base, hunk_status(&hunk), display_range)
         })
         .collect()
 }
@@ -115,7 +122,11 @@ pub fn expanded_hunks(
     editor: &Editor,
     snapshot: &DisplaySnapshot,
     cx: &mut ViewContext<'_, Editor>,
-) -> Vec<(String, git::diff::DiffHunkStatus, core::ops::Range<u32>)> {
+) -> Vec<(
+    String,
+    git::diff::DiffHunkStatus,
+    std::ops::Range<crate::DisplayRow>,
+)> {
     editor
         .expanded_hunks
         .hunks(false)
@@ -150,7 +161,9 @@ pub fn expanded_hunks(
 pub fn expanded_hunks_background_highlights(
     editor: &mut Editor,
     cx: &mut gpui::WindowContext,
-) -> Vec<std::ops::RangeInclusive<u32>> {
+) -> Vec<std::ops::RangeInclusive<crate::DisplayRow>> {
+    use crate::DisplayRow;
+
     let mut highlights = Vec::new();
 
     let mut range_start = 0;
@@ -159,19 +172,19 @@ pub fn expanded_hunks_background_highlights(
     {
         match previous_highlighted_row {
             Some(previous_row) => {
-                if previous_row + 1 != highlighted_row {
-                    highlights.push(range_start..=previous_row);
-                    range_start = highlighted_row;
+                if previous_row + 1 != highlighted_row.0 {
+                    highlights.push(DisplayRow(range_start)..=DisplayRow(previous_row));
+                    range_start = highlighted_row.0;
                 }
             }
             None => {
-                range_start = highlighted_row;
+                range_start = highlighted_row.0;
             }
         }
-        previous_highlighted_row = Some(highlighted_row);
+        previous_highlighted_row = Some(highlighted_row.0);
     }
     if let Some(previous_row) = previous_highlighted_row {
-        highlights.push(range_start..=previous_row);
+        highlights.push(DisplayRow(range_start)..=DisplayRow(previous_row));
     }
 
     highlights

crates/editor/src/test/editor_test_context.rs šŸ”—

@@ -1,5 +1,6 @@
 use crate::{
     display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
+    RowExt,
 };
 use collections::BTreeMap;
 use futures::Future;
@@ -256,7 +257,7 @@ impl EditorTestContext {
             let details = editor.text_layout_details(cx);
 
             let y = pixel_position.y
-                + line_height * (display_point.row() as f32 - newest_point.row() as f32);
+                + line_height * (display_point.row().as_f32() - newest_point.row().as_f32());
             let x = pixel_position.x + snapshot.x_for_display_point(display_point, &details)
                 - snapshot.x_for_display_point(newest_point, &details);
             Point::new(x, y)

crates/git/src/diff.rs šŸ”—

@@ -30,18 +30,6 @@ pub struct DiffHunk<T> {
     pub diff_base_byte_range: Range<usize>,
 }
 
-impl DiffHunk<u32> {
-    pub fn status(&self) -> DiffHunkStatus {
-        if self.diff_base_byte_range.is_empty() {
-            DiffHunkStatus::Added
-        } else if self.associated_range.is_empty() {
-            DiffHunkStatus::Removed
-        } else {
-            DiffHunkStatus::Modified
-        }
-    }
-}
-
 impl sum_tree::Item for DiffHunk<Anchor> {
     type Summary = DiffHunkSummary;
 

crates/language/src/buffer.rs šŸ”—

@@ -75,6 +75,8 @@ pub enum Capability {
     ReadOnly,
 }
 
+pub type BufferRow = u32;
+
 /// An in-memory representation of a source code file, including its text,
 /// syntax trees, git status, and diagnostics.
 pub struct Buffer {
@@ -3104,7 +3106,7 @@ impl BufferSnapshot {
     /// row range.
     pub fn git_diff_hunks_in_row_range(
         &self,
-        range: Range<u32>,
+        range: Range<BufferRow>,
     ) -> impl '_ + Iterator<Item = git::diff::DiffHunk<u32>> {
         self.git_diff.hunks_in_row_range(range, self)
     }

crates/multi_buffer/Cargo.toml šŸ”—

@@ -35,6 +35,7 @@ log.workspace = true
 parking_lot.workspace = true
 rand.workspace = true
 settings.workspace = true
+serde.workspace = true
 smallvec.workspace = true
 sum_tree.workspace = true
 text.workspace = true

crates/multi_buffer/src/multi_buffer.rs šŸ”—

@@ -11,9 +11,9 @@ use itertools::Itertools;
 use language::{
     char_kind,
     language_settings::{language_settings, LanguageSettings},
-    AutoindentMode, Buffer, BufferChunks, BufferSnapshot, Capability, CharKind, Chunk, CursorShape,
-    DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16,
-    Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
+    AutoindentMode, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability, CharKind, Chunk,
+    CursorShape, DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt,
+    OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
     ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
 };
 use smallvec::SmallVec;
@@ -100,6 +100,16 @@ pub enum Event {
     DiagnosticsUpdated,
 }
 
+pub type MultiBufferPoint = Point;
+
+#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq, serde::Deserialize)]
+#[serde(transparent)]
+pub struct MultiBufferRow(pub u32);
+
+impl MultiBufferRow {
+    pub const MIN: Self = Self(0);
+    pub const MAX: Self = Self(u32::MAX);
+}
 #[derive(Clone)]
 struct History {
     next_transaction_id: TransactionId,
@@ -165,8 +175,7 @@ pub struct MultiBufferSnapshot {
 /// A boundary between [`Excerpt`]s in a [`MultiBuffer`]
 pub struct ExcerptBoundary {
     pub id: ExcerptId,
-    /// The row in the `MultiBuffer` where the boundary is located
-    pub row: u32,
+    pub row: MultiBufferRow,
     pub buffer: BufferSnapshot,
     pub range: ExcerptRange<text::Anchor>,
     /// It's possible to have multiple excerpts in the same buffer,
@@ -190,7 +199,7 @@ struct Excerpt {
     /// The range of the buffer to be shown in the excerpt
     range: ExcerptRange<text::Anchor>,
     /// The last row in the excerpted slice of the buffer
-    max_buffer_row: u32,
+    max_buffer_row: BufferRow,
     /// A summary of the text in the excerpt
     text_summary: TextSummary,
     has_trailing_newline: bool,
@@ -229,7 +238,7 @@ struct ExcerptSummary {
     /// The location of the last [`Excerpt`] being summarized
     excerpt_locator: Locator,
     /// The maximum row of the [`Excerpt`]s being summarized
-    max_buffer_row: u32,
+    max_buffer_row: MultiBufferRow,
     text: TextSummary,
 }
 
@@ -2111,8 +2120,8 @@ impl MultiBufferSnapshot {
         self.chunks(range, false).map(|chunk| chunk.text)
     }
 
-    pub fn is_line_blank(&self, row: u32) -> bool {
-        self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row)))
+    pub fn is_line_blank(&self, row: MultiBufferRow) -> bool {
+        self.text_for_range(Point::new(row.0, 0)..Point::new(row.0, self.line_len(row)))
             .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
     }
 
@@ -2181,7 +2190,7 @@ impl MultiBufferSnapshot {
         self.excerpts.summary().text.len == 0
     }
 
-    pub fn max_buffer_row(&self) -> u32 {
+    pub fn max_buffer_row(&self) -> MultiBufferRow {
         self.excerpts.summary().max_buffer_row
     }
 
@@ -2312,7 +2321,7 @@ impl MultiBufferSnapshot {
         }
     }
 
-    pub fn buffer_rows(&self, start_row: u32) -> MultiBufferRows {
+    pub fn buffer_rows(&self, start_row: MultiBufferRow) -> MultiBufferRows {
         let mut result = MultiBufferRows {
             buffer_row_range: 0..0,
             excerpts: self.excerpts.cursor(),
@@ -2509,7 +2518,7 @@ impl MultiBufferSnapshot {
         &self,
         rows: impl IntoIterator<Item = u32>,
         cx: &AppContext,
-    ) -> BTreeMap<u32, IndentSize> {
+    ) -> BTreeMap<MultiBufferRow, IndentSize> {
         let mut result = BTreeMap::new();
 
         let mut rows_for_excerpt = Vec::new();
@@ -2555,16 +2564,19 @@ impl MultiBufferSnapshot {
             let buffer_indents = excerpt
                 .buffer
                 .suggested_indents(buffer_rows, single_indent_size);
-            let multibuffer_indents = buffer_indents
-                .into_iter()
-                .map(|(row, indent)| (start_multibuffer_row + row - start_buffer_row, indent));
+            let multibuffer_indents = buffer_indents.into_iter().map(|(row, indent)| {
+                (
+                    MultiBufferRow(start_multibuffer_row + row - start_buffer_row),
+                    indent,
+                )
+            });
             result.extend(multibuffer_indents);
         }
 
         result
     }
 
-    pub fn indent_size_for_line(&self, row: u32) -> IndentSize {
+    pub fn indent_size_for_line(&self, row: MultiBufferRow) -> IndentSize {
         if let Some((buffer, range)) = self.buffer_line_for_row(row) {
             let mut size = buffer.indent_size_for_line(range.start.row);
             size.len = size
@@ -2577,9 +2589,9 @@ impl MultiBufferSnapshot {
         }
     }
 
-    pub fn prev_non_blank_row(&self, mut row: u32) -> Option<u32> {
-        while row > 0 {
-            row -= 1;
+    pub fn prev_non_blank_row(&self, mut row: MultiBufferRow) -> Option<MultiBufferRow> {
+        while row.0 > 0 {
+            row.0 -= 1;
             if !self.is_line_blank(row) {
                 return Some(row);
             }
@@ -2587,7 +2599,7 @@ impl MultiBufferSnapshot {
         None
     }
 
-    pub fn line_len(&self, row: u32) -> u32 {
+    pub fn line_len(&self, row: MultiBufferRow) -> u32 {
         if let Some((_, range)) = self.buffer_line_for_row(row) {
             range.end.column - range.start.column
         } else {
@@ -2595,15 +2607,18 @@ impl MultiBufferSnapshot {
         }
     }
 
-    pub fn buffer_line_for_row(&self, row: u32) -> Option<(&BufferSnapshot, Range<Point>)> {
+    pub fn buffer_line_for_row(
+        &self,
+        row: MultiBufferRow,
+    ) -> Option<(&BufferSnapshot, Range<Point>)> {
         let mut cursor = self.excerpts.cursor::<Point>();
-        let point = Point::new(row, 0);
+        let point = Point::new(row.0, 0);
         cursor.seek(&point, Bias::Right, &());
         if cursor.item().is_none() && *cursor.start() == point {
             cursor.prev(&());
         }
         if let Some(excerpt) = cursor.item() {
-            let overshoot = row - cursor.start().row;
+            let overshoot = row.0 - cursor.start().row;
             let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer);
             let excerpt_end = excerpt.range.context.end.to_point(&excerpt.buffer);
             let buffer_row = excerpt_start.row + overshoot;
@@ -3028,7 +3043,7 @@ impl MultiBufferSnapshot {
                 let starts_new_buffer = Some(excerpt.buffer_id) != prev_buffer_id;
                 let boundary = ExcerptBoundary {
                     id: excerpt.id,
-                    row: cursor.start().1.row,
+                    row: MultiBufferRow(cursor.start().1.row),
                     buffer: excerpt.buffer.clone(),
                     range: excerpt.range.clone(),
                     starts_new_buffer,
@@ -3312,11 +3327,11 @@ impl MultiBufferSnapshot {
 
     pub fn git_diff_hunks_in_range_rev(
         &self,
-        row_range: Range<u32>,
-    ) -> impl Iterator<Item = DiffHunk<u32>> + '_ {
+        row_range: Range<MultiBufferRow>,
+    ) -> impl Iterator<Item = DiffHunk<MultiBufferRow>> + '_ {
         let mut cursor = self.excerpts.cursor::<Point>();
 
-        cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &());
+        cursor.seek(&Point::new(row_range.end.0, 0), Bias::Left, &());
         if cursor.item().is_none() {
             cursor.prev(&());
         }
@@ -3325,7 +3340,7 @@ impl MultiBufferSnapshot {
             let excerpt = cursor.item()?;
             let multibuffer_start = *cursor.start();
             let multibuffer_end = multibuffer_start + excerpt.text_summary.lines;
-            if multibuffer_start.row >= row_range.end {
+            if multibuffer_start.row >= row_range.end.0 {
                 return None;
             }
 
@@ -3334,15 +3349,15 @@ impl MultiBufferSnapshot {
             let excerpt_start_point = buffer_start.to_point(&excerpt.buffer);
             let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines;
 
-            if row_range.start > multibuffer_start.row {
+            if row_range.start.0 > multibuffer_start.row {
                 let buffer_start_point =
-                    excerpt_start_point + Point::new(row_range.start - multibuffer_start.row, 0);
+                    excerpt_start_point + Point::new(row_range.start.0 - multibuffer_start.row, 0);
                 buffer_start = excerpt.buffer.anchor_before(buffer_start_point);
             }
 
-            if row_range.end < multibuffer_end.row {
+            if row_range.end.0 < multibuffer_end.row {
                 let buffer_end_point =
-                    excerpt_start_point + Point::new(row_range.end - multibuffer_start.row, 0);
+                    excerpt_start_point + Point::new(row_range.end.0 - multibuffer_start.row, 0);
                 buffer_end = excerpt.buffer.anchor_before(buffer_end_point);
             }
 
@@ -3363,7 +3378,7 @@ impl MultiBufferSnapshot {
                             .saturating_sub(excerpt_start_point.row);
 
                     DiffHunk {
-                        associated_range: start..end,
+                        associated_range: MultiBufferRow(start)..MultiBufferRow(end),
                         diff_base_byte_range: hunk.diff_base_byte_range.clone(),
                         buffer_range: hunk.buffer_range.clone(),
                         buffer_id: hunk.buffer_id,
@@ -3379,11 +3394,11 @@ impl MultiBufferSnapshot {
 
     pub fn git_diff_hunks_in_range(
         &self,
-        row_range: Range<u32>,
-    ) -> impl Iterator<Item = DiffHunk<u32>> + '_ {
+        row_range: Range<MultiBufferRow>,
+    ) -> impl Iterator<Item = DiffHunk<MultiBufferRow>> + '_ {
         let mut cursor = self.excerpts.cursor::<Point>();
 
-        cursor.seek(&Point::new(row_range.start, 0), Bias::Left, &());
+        cursor.seek(&Point::new(row_range.start.0, 0), Bias::Left, &());
 
         std::iter::from_fn(move || {
             let excerpt = cursor.item()?;
@@ -3392,25 +3407,25 @@ impl MultiBufferSnapshot {
             let mut buffer_start = excerpt.range.context.start;
             let mut buffer_end = excerpt.range.context.end;
 
-            let excerpt_rows = match multibuffer_start.row.cmp(&row_range.end) {
+            let excerpt_rows = match multibuffer_start.row.cmp(&row_range.end.0) {
                 cmp::Ordering::Less => {
                     let excerpt_start_point = buffer_start.to_point(&excerpt.buffer);
                     let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines;
 
-                    if row_range.start > multibuffer_start.row {
+                    if row_range.start.0 > multibuffer_start.row {
                         let buffer_start_point = excerpt_start_point
-                            + Point::new(row_range.start - multibuffer_start.row, 0);
+                            + Point::new(row_range.start.0 - multibuffer_start.row, 0);
                         buffer_start = excerpt.buffer.anchor_before(buffer_start_point);
                     }
 
-                    if row_range.end < multibuffer_end.row {
+                    if row_range.end.0 < multibuffer_end.row {
                         let buffer_end_point = excerpt_start_point
-                            + Point::new(row_range.end - multibuffer_start.row, 0);
+                            + Point::new(row_range.end.0 - multibuffer_start.row, 0);
                         buffer_end = excerpt.buffer.anchor_before(buffer_end_point);
                     }
                     excerpt_start_point.row..excerpt_end_point.row
                 }
-                cmp::Ordering::Equal if row_range.end == 0 => {
+                cmp::Ordering::Equal if row_range.end.0 == 0 => {
                     buffer_end = buffer_start;
                     0..0
                 }
@@ -3422,7 +3437,7 @@ impl MultiBufferSnapshot {
                 .git_diff_hunks_intersecting_range(buffer_start..buffer_end)
                 .map(move |hunk| {
                     let buffer_range = if excerpt_rows.start == 0 && excerpt_rows.end == 0 {
-                        0..1
+                        MultiBufferRow(0)..MultiBufferRow(1)
                     } else {
                         let start = multibuffer_start.row
                             + hunk
@@ -3435,7 +3450,7 @@ impl MultiBufferSnapshot {
                                 .end
                                 .min(excerpt_rows.end + 1)
                                 .saturating_sub(excerpt_rows.start);
-                        start..end
+                        MultiBufferRow(start)..MultiBufferRow(end)
                     };
                     DiffHunk {
                         associated_range: buffer_range,
@@ -4096,7 +4111,7 @@ impl sum_tree::Item for Excerpt {
         ExcerptSummary {
             excerpt_id: self.id,
             excerpt_locator: self.locator.clone(),
-            max_buffer_row: self.max_buffer_row,
+            max_buffer_row: MultiBufferRow(self.max_buffer_row),
             text,
         }
     }
@@ -4198,22 +4213,22 @@ impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<ExcerptId> {
 }
 
 impl<'a> MultiBufferRows<'a> {
-    pub fn seek(&mut self, row: u32) {
+    pub fn seek(&mut self, row: MultiBufferRow) {
         self.buffer_row_range = 0..0;
 
         self.excerpts
-            .seek_forward(&Point::new(row, 0), Bias::Right, &());
+            .seek_forward(&Point::new(row.0, 0), Bias::Right, &());
         if self.excerpts.item().is_none() {
             self.excerpts.prev(&());
 
-            if self.excerpts.item().is_none() && row == 0 {
+            if self.excerpts.item().is_none() && row.0 == 0 {
                 self.buffer_row_range = 0..1;
                 return;
             }
         }
 
         if let Some(excerpt) = self.excerpts.item() {
-            let overshoot = row - self.excerpts.start().row;
+            let overshoot = row.0 - self.excerpts.start().row;
             let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer).row;
             self.buffer_row_range.start = excerpt_start + overshoot;
             self.buffer_row_range.end = excerpt_start + excerpt.text_summary.lines.row + 1;
@@ -4546,7 +4561,7 @@ mod tests {
         assert_eq!(snapshot.text(), buffer.read(cx).text());
 
         assert_eq!(
-            snapshot.buffer_rows(0).collect::<Vec<_>>(),
+            snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
             (0..buffer.read(cx).row_count())
                 .map(Some)
                 .collect::<Vec<_>>()
@@ -4557,7 +4572,7 @@ mod tests {
 
         assert_eq!(snapshot.text(), buffer.read(cx).text());
         assert_eq!(
-            snapshot.buffer_rows(0).collect::<Vec<_>>(),
+            snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
             (0..buffer.read(cx).row_count())
                 .map(Some)
                 .collect::<Vec<_>>()
@@ -4685,27 +4700,33 @@ mod tests {
             )
         );
         assert_eq!(
-            snapshot.buffer_rows(0).collect::<Vec<_>>(),
+            snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
             [Some(1), Some(2), Some(3), Some(4), Some(3)]
         );
         assert_eq!(
-            snapshot.buffer_rows(2).collect::<Vec<_>>(),
+            snapshot.buffer_rows(MultiBufferRow(2)).collect::<Vec<_>>(),
             [Some(3), Some(4), Some(3)]
         );
-        assert_eq!(snapshot.buffer_rows(4).collect::<Vec<_>>(), [Some(3)]);
-        assert_eq!(snapshot.buffer_rows(5).collect::<Vec<_>>(), []);
+        assert_eq!(
+            snapshot.buffer_rows(MultiBufferRow(4)).collect::<Vec<_>>(),
+            [Some(3)]
+        );
+        assert_eq!(
+            snapshot.buffer_rows(MultiBufferRow(5)).collect::<Vec<_>>(),
+            []
+        );
 
         assert_eq!(
             boundaries_in_range(Point::new(0, 0)..Point::new(4, 2), &snapshot),
             &[
-                (0, "bbbb\nccccc".to_string(), true),
-                (2, "ddd\neeee".to_string(), false),
-                (4, "jj".to_string(), true),
+                (MultiBufferRow(0), "bbbb\nccccc".to_string(), true),
+                (MultiBufferRow(2), "ddd\neeee".to_string(), false),
+                (MultiBufferRow(4), "jj".to_string(), true),
             ]
         );
         assert_eq!(
             boundaries_in_range(Point::new(0, 0)..Point::new(2, 0), &snapshot),
-            &[(0, "bbbb\nccccc".to_string(), true)]
+            &[(MultiBufferRow(0), "bbbb\nccccc".to_string(), true)]
         );
         assert_eq!(
             boundaries_in_range(Point::new(1, 0)..Point::new(1, 5), &snapshot),
@@ -4717,19 +4738,19 @@ mod tests {
         );
         assert_eq!(
             boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
-            &[(2, "ddd\neeee".to_string(), false)]
+            &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
         );
         assert_eq!(
             boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot),
-            &[(2, "ddd\neeee".to_string(), false)]
+            &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
         );
         assert_eq!(
             boundaries_in_range(Point::new(2, 0)..Point::new(3, 0), &snapshot),
-            &[(2, "ddd\neeee".to_string(), false)]
+            &[(MultiBufferRow(2), "ddd\neeee".to_string(), false)]
         );
         assert_eq!(
             boundaries_in_range(Point::new(4, 0)..Point::new(4, 2), &snapshot),
-            &[(4, "jj".to_string(), true)]
+            &[(MultiBufferRow(4), "jj".to_string(), true)]
         );
         assert_eq!(
             boundaries_in_range(Point::new(4, 2)..Point::new(4, 2), &snapshot),
@@ -4812,7 +4833,7 @@ mod tests {
         fn boundaries_in_range(
             range: Range<Point>,
             snapshot: &MultiBufferSnapshot,
-        ) -> Vec<(u32, String, bool)> {
+        ) -> Vec<(MultiBufferRow, String, bool)> {
             snapshot
                 .excerpt_boundaries_in_range(range)
                 .map(|boundary| {
@@ -5100,8 +5121,14 @@ mod tests {
 
         let snapshot = multibuffer.read(cx).snapshot(cx);
         assert_eq!(snapshot.text(), "");
-        assert_eq!(snapshot.buffer_rows(0).collect::<Vec<_>>(), &[Some(0)]);
-        assert_eq!(snapshot.buffer_rows(1).collect::<Vec<_>>(), &[]);
+        assert_eq!(
+            snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
+            &[Some(0)]
+        );
+        assert_eq!(
+            snapshot.buffer_rows(MultiBufferRow(1)).collect::<Vec<_>>(),
+            &[]
+        );
     }
 
     #[gpui::test]
@@ -5518,14 +5545,16 @@ mod tests {
             log::info!("MultiBuffer text: {:?}", expected_text);
 
             assert_eq!(
-                snapshot.buffer_rows(0).collect::<Vec<_>>(),
+                snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
                 expected_buffer_rows,
             );
 
             for _ in 0..5 {
                 let start_row = rng.gen_range(0..=expected_buffer_rows.len());
                 assert_eq!(
-                    snapshot.buffer_rows(start_row as u32).collect::<Vec<_>>(),
+                    snapshot
+                        .buffer_rows(MultiBufferRow(start_row as u32))
+                        .collect::<Vec<_>>(),
                     &expected_buffer_rows[start_row..],
                     "buffer_rows({})",
                     start_row
@@ -5533,7 +5562,7 @@ mod tests {
             }
 
             assert_eq!(
-                snapshot.max_buffer_row(),
+                snapshot.max_buffer_row().0,
                 expected_buffer_rows.into_iter().flatten().max().unwrap()
             );
 
@@ -5666,7 +5695,7 @@ mod tests {
 
             for (row, line) in expected_text.split('\n').enumerate() {
                 assert_eq!(
-                    snapshot.line_len(row as u32),
+                    snapshot.line_len(MultiBufferRow(row as u32)),
                     line.len() as u32,
                     "line_len({}).",
                     row

crates/search/src/buffer_search.rs šŸ”—

@@ -1090,7 +1090,7 @@ mod tests {
     use std::ops::Range;
 
     use super::*;
-    use editor::{DisplayPoint, Editor};
+    use editor::{display_map::DisplayRow, DisplayPoint, Editor};
     use gpui::{Context, Hsla, TestAppContext, VisualTestContext};
     use language::Buffer;
     use project::Project;
@@ -1157,8 +1157,8 @@ mod tests {
             assert_eq!(
                 display_points_of(editor.all_text_background_highlights(cx)),
                 &[
-                    DisplayPoint::new(2, 17)..DisplayPoint::new(2, 19),
-                    DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45),
+                    DisplayPoint::new(DisplayRow(2), 17)..DisplayPoint::new(DisplayRow(2), 19),
+                    DisplayPoint::new(DisplayRow(2), 43)..DisplayPoint::new(DisplayRow(2), 45),
                 ]
             );
         });
@@ -1172,7 +1172,7 @@ mod tests {
         editor.update(cx, |editor, cx| {
             assert_eq!(
                 display_points_of(editor.all_text_background_highlights(cx)),
-                &[DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45),]
+                &[DisplayPoint::new(DisplayRow(2), 43)..DisplayPoint::new(DisplayRow(2), 45),]
             );
         });
 
@@ -1186,13 +1186,13 @@ mod tests {
             assert_eq!(
                 display_points_of(editor.all_text_background_highlights(cx)),
                 &[
-                    DisplayPoint::new(0, 24)..DisplayPoint::new(0, 26),
-                    DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43),
-                    DisplayPoint::new(2, 71)..DisplayPoint::new(2, 73),
-                    DisplayPoint::new(3, 1)..DisplayPoint::new(3, 3),
-                    DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13),
-                    DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58),
-                    DisplayPoint::new(3, 60)..DisplayPoint::new(3, 62),
+                    DisplayPoint::new(DisplayRow(0), 24)..DisplayPoint::new(DisplayRow(0), 26),
+                    DisplayPoint::new(DisplayRow(0), 41)..DisplayPoint::new(DisplayRow(0), 43),
+                    DisplayPoint::new(DisplayRow(2), 71)..DisplayPoint::new(DisplayRow(2), 73),
+                    DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 3),
+                    DisplayPoint::new(DisplayRow(3), 11)..DisplayPoint::new(DisplayRow(3), 13),
+                    DisplayPoint::new(DisplayRow(3), 56)..DisplayPoint::new(DisplayRow(3), 58),
+                    DisplayPoint::new(DisplayRow(3), 60)..DisplayPoint::new(DisplayRow(3), 62),
                 ]
             );
         });
@@ -1207,16 +1207,18 @@ mod tests {
             assert_eq!(
                 display_points_of(editor.all_text_background_highlights(cx)),
                 &[
-                    DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43),
-                    DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13),
-                    DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58),
+                    DisplayPoint::new(DisplayRow(0), 41)..DisplayPoint::new(DisplayRow(0), 43),
+                    DisplayPoint::new(DisplayRow(3), 11)..DisplayPoint::new(DisplayRow(3), 13),
+                    DisplayPoint::new(DisplayRow(3), 56)..DisplayPoint::new(DisplayRow(3), 58),
                 ]
             );
         });
 
         editor.update(cx, |editor, cx| {
             editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)])
+                s.select_display_ranges([
+                    DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
+                ])
             });
         });
         search_bar.update(cx, |search_bar, cx| {
@@ -1224,7 +1226,7 @@ mod tests {
             search_bar.select_next_match(&SelectNextMatch, cx);
             assert_eq!(
                 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
+                [DisplayPoint::new(DisplayRow(0), 41)..DisplayPoint::new(DisplayRow(0), 43)]
             );
         });
         search_bar.update(cx, |search_bar, _| {
@@ -1235,7 +1237,7 @@ mod tests {
             search_bar.select_next_match(&SelectNextMatch, cx);
             assert_eq!(
                 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)]
+                [DisplayPoint::new(DisplayRow(3), 11)..DisplayPoint::new(DisplayRow(3), 13)]
             );
         });
         search_bar.update(cx, |search_bar, _| {
@@ -1246,7 +1248,7 @@ mod tests {
             search_bar.select_next_match(&SelectNextMatch, cx);
             assert_eq!(
                 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
+                [DisplayPoint::new(DisplayRow(3), 56)..DisplayPoint::new(DisplayRow(3), 58)]
             );
         });
         search_bar.update(cx, |search_bar, _| {
@@ -1257,7 +1259,7 @@ mod tests {
             search_bar.select_next_match(&SelectNextMatch, cx);
             assert_eq!(
                 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
+                [DisplayPoint::new(DisplayRow(0), 41)..DisplayPoint::new(DisplayRow(0), 43)]
             );
         });
         search_bar.update(cx, |search_bar, _| {
@@ -1268,7 +1270,7 @@ mod tests {
             search_bar.select_prev_match(&SelectPrevMatch, cx);
             assert_eq!(
                 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
+                [DisplayPoint::new(DisplayRow(3), 56)..DisplayPoint::new(DisplayRow(3), 58)]
             );
         });
         search_bar.update(cx, |search_bar, _| {
@@ -1279,7 +1281,7 @@ mod tests {
             search_bar.select_prev_match(&SelectPrevMatch, cx);
             assert_eq!(
                 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)]
+                [DisplayPoint::new(DisplayRow(3), 11)..DisplayPoint::new(DisplayRow(3), 13)]
             );
         });
         search_bar.update(cx, |search_bar, _| {
@@ -1290,7 +1292,7 @@ mod tests {
             search_bar.select_prev_match(&SelectPrevMatch, cx);
             assert_eq!(
                 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
+                [DisplayPoint::new(DisplayRow(0), 41)..DisplayPoint::new(DisplayRow(0), 43)]
             );
         });
         search_bar.update(cx, |search_bar, _| {
@@ -1301,7 +1303,9 @@ mod tests {
         // the closest match to the left.
         editor.update(cx, |editor, cx| {
             editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
+                s.select_display_ranges([
+                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
+                ])
             });
         });
         search_bar.update(cx, |search_bar, cx| {
@@ -1309,7 +1313,7 @@ mod tests {
             search_bar.select_prev_match(&SelectPrevMatch, cx);
             assert_eq!(
                 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
+                [DisplayPoint::new(DisplayRow(0), 41)..DisplayPoint::new(DisplayRow(0), 43)]
             );
         });
         search_bar.update(cx, |search_bar, _| {
@@ -1320,7 +1324,9 @@ mod tests {
         // closest match to the right.
         editor.update(cx, |editor, cx| {
             editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
+                s.select_display_ranges([
+                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
+                ])
             });
         });
         search_bar.update(cx, |search_bar, cx| {
@@ -1328,7 +1334,7 @@ mod tests {
             search_bar.select_next_match(&SelectNextMatch, cx);
             assert_eq!(
                 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)]
+                [DisplayPoint::new(DisplayRow(3), 11)..DisplayPoint::new(DisplayRow(3), 13)]
             );
         });
         search_bar.update(cx, |search_bar, _| {
@@ -1339,7 +1345,9 @@ mod tests {
         // the last match.
         editor.update(cx, |editor, cx| {
             editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(3, 60)..DisplayPoint::new(3, 60)])
+                s.select_display_ranges([
+                    DisplayPoint::new(DisplayRow(3), 60)..DisplayPoint::new(DisplayRow(3), 60)
+                ])
             });
         });
         search_bar.update(cx, |search_bar, cx| {
@@ -1347,7 +1355,7 @@ mod tests {
             search_bar.select_prev_match(&SelectPrevMatch, cx);
             assert_eq!(
                 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
+                [DisplayPoint::new(DisplayRow(3), 56)..DisplayPoint::new(DisplayRow(3), 58)]
             );
         });
         search_bar.update(cx, |search_bar, _| {
@@ -1358,7 +1366,9 @@ mod tests {
         // first match.
         editor.update(cx, |editor, cx| {
             editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(3, 60)..DisplayPoint::new(3, 60)])
+                s.select_display_ranges([
+                    DisplayPoint::new(DisplayRow(3), 60)..DisplayPoint::new(DisplayRow(3), 60)
+                ])
             });
         });
         search_bar.update(cx, |search_bar, cx| {
@@ -1366,7 +1376,7 @@ mod tests {
             search_bar.select_next_match(&SelectNextMatch, cx);
             assert_eq!(
                 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)]
+                [DisplayPoint::new(DisplayRow(0), 41)..DisplayPoint::new(DisplayRow(0), 43)]
             );
         });
         search_bar.update(cx, |search_bar, _| {
@@ -1377,7 +1387,9 @@ mod tests {
         // selects the last match.
         editor.update(cx, |editor, cx| {
             editor.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)])
+                s.select_display_ranges([
+                    DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
+                ])
             });
         });
         search_bar.update(cx, |search_bar, cx| {
@@ -1385,7 +1397,7 @@ mod tests {
             search_bar.select_prev_match(&SelectPrevMatch, cx);
             assert_eq!(
                 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)]
+                [DisplayPoint::new(DisplayRow(3), 56)..DisplayPoint::new(DisplayRow(3), 58)]
             );
         });
         search_bar.update(cx, |search_bar, _| {
@@ -1414,7 +1426,7 @@ mod tests {
         editor.update(cx, |editor, cx| {
             assert_eq!(
                 display_points_of(editor.all_text_background_highlights(cx)),
-                &[DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45),]
+                &[DisplayPoint::new(DisplayRow(2), 43)..DisplayPoint::new(DisplayRow(2), 45),]
             );
         });
 
@@ -1439,7 +1451,7 @@ mod tests {
         editor.update(cx, |editor, cx| {
             assert_eq!(
                 display_points_of(editor.all_text_background_highlights(cx)),
-                &[DisplayPoint::new(0, 35)..DisplayPoint::new(0, 40),]
+                &[DisplayPoint::new(DisplayRow(0), 35)..DisplayPoint::new(DisplayRow(0), 40),]
             );
         });
 
@@ -2041,8 +2053,8 @@ mod tests {
             assert_eq!(
                 display_points_of(editor.all_text_background_highlights(cx)),
                 &[
-                    DisplayPoint::new(0, 10)..DisplayPoint::new(0, 20),
-                    DisplayPoint::new(1, 9)..DisplayPoint::new(1, 19),
+                    DisplayPoint::new(DisplayRow(0), 10)..DisplayPoint::new(DisplayRow(0), 20),
+                    DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 19),
                 ],
             );
         });

crates/search/src/project_search.rs šŸ”—

@@ -1688,7 +1688,7 @@ fn register_workspace_action_for_present_search<A: Action>(
 #[cfg(test)]
 pub mod tests {
     use super::*;
-    use editor::DisplayPoint;
+    use editor::{display_map::DisplayRow, DisplayPoint};
     use gpui::{Action, TestAppContext, WindowHandle};
     use project::FakeFs;
     use serde_json::json;
@@ -1730,15 +1730,15 @@ pub mod tests {
                     .update(cx, |editor, cx| editor.all_text_background_highlights(cx)),
                 &[
                     (
-                        DisplayPoint::new(2, 32)..DisplayPoint::new(2, 35),
+                        DisplayPoint::new(DisplayRow(2), 32)..DisplayPoint::new(DisplayRow(2), 35),
                         match_background_color
                     ),
                     (
-                        DisplayPoint::new(2, 37)..DisplayPoint::new(2, 40),
+                        DisplayPoint::new(DisplayRow(2), 37)..DisplayPoint::new(DisplayRow(2), 40),
                         match_background_color
                     ),
                     (
-                        DisplayPoint::new(5, 6)..DisplayPoint::new(5, 9),
+                        DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 9),
                         match_background_color
                     )
                 ]
@@ -1748,7 +1748,7 @@ pub mod tests {
                 search_view
                     .results_editor
                     .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                [DisplayPoint::new(2, 32)..DisplayPoint::new(2, 35)]
+                [DisplayPoint::new(DisplayRow(2), 32)..DisplayPoint::new(DisplayRow(2), 35)]
             );
 
             search_view.select_match(Direction::Next, cx);
@@ -1761,7 +1761,7 @@ pub mod tests {
                     search_view
                         .results_editor
                         .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(2, 37)..DisplayPoint::new(2, 40)]
+                    [DisplayPoint::new(DisplayRow(2), 37)..DisplayPoint::new(DisplayRow(2), 40)]
                 );
                 search_view.select_match(Direction::Next, cx);
             })
@@ -1774,7 +1774,7 @@ pub mod tests {
                     search_view
                         .results_editor
                         .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(5, 6)..DisplayPoint::new(5, 9)]
+                    [DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 9)]
                 );
                 search_view.select_match(Direction::Next, cx);
             })
@@ -1787,7 +1787,7 @@ pub mod tests {
                     search_view
                         .results_editor
                         .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(2, 32)..DisplayPoint::new(2, 35)]
+                    [DisplayPoint::new(DisplayRow(2), 32)..DisplayPoint::new(DisplayRow(2), 35)]
                 );
                 search_view.select_match(Direction::Prev, cx);
             })
@@ -1800,7 +1800,7 @@ pub mod tests {
                     search_view
                         .results_editor
                         .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(5, 6)..DisplayPoint::new(5, 9)]
+                    [DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 9)]
                 );
                 search_view.select_match(Direction::Prev, cx);
             })
@@ -1813,7 +1813,7 @@ pub mod tests {
                     search_view
                         .results_editor
                         .update(cx, |editor, cx| editor.selections.display_ranges(cx)),
-                    [DisplayPoint::new(2, 37)..DisplayPoint::new(2, 40)]
+                    [DisplayPoint::new(DisplayRow(2), 37)..DisplayPoint::new(DisplayRow(2), 40)]
                 );
             })
             .unwrap();

crates/vim/Cargo.toml šŸ”—

@@ -26,6 +26,7 @@ gpui.workspace = true
 itertools.workspace = true
 language.workspace = true
 log.workspace = true
+multi_buffer.workspace = true
 nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "master", features = [
     "use_tokio",
 ], optional = true }

crates/vim/src/motion.rs šŸ”—

@@ -1,13 +1,14 @@
 use editor::{
-    display_map::{DisplaySnapshot, FoldPoint, ToDisplayPoint},
+    display_map::{DisplayRow, DisplaySnapshot, FoldPoint, ToDisplayPoint},
     movement::{
         self, find_boundary, find_preceding_boundary_display_point, FindRange, TextLayoutDetails,
     },
     scroll::Autoscroll,
-    Anchor, Bias, DisplayPoint, ToOffset,
+    Anchor, Bias, DisplayPoint, RowExt, ToOffset,
 };
 use gpui::{actions, impl_actions, px, ViewContext, WindowContext};
 use language::{char_kind, CharKind, Point, Selection, SelectionGoal};
+use multi_buffer::MultiBufferRow;
 use serde::Deserialize;
 use std::ops::Range;
 use workspace::Workspace;
@@ -843,7 +844,7 @@ impl Motion {
                         selection.end = map.clip_point(selection.end, Bias::Right);
                         // Don't reset the end here
                         return Some(selection.start..selection.end);
-                    } else if selection.start.row() > 0 {
+                    } else if selection.start.row().0 > 0 {
                         *selection.start.row_mut() -= 1;
                         *selection.start.column_mut() = map.line_len(selection.start.row());
                         selection.start = map.clip_point(selection.start, Bias::Left);
@@ -860,10 +861,10 @@ impl Motion {
                     ignore_punctuation: _,
                 } = self
                 {
-                    let start_row = selection.start.to_point(&map).row;
-                    if selection.end.to_point(&map).row > start_row {
+                    let start_row = MultiBufferRow(selection.start.to_point(&map).row);
+                    if selection.end.to_point(&map).row > start_row.0 {
                         selection.end =
-                            Point::new(start_row, map.buffer_snapshot.line_len(start_row))
+                            Point::new(start_row.0, map.buffer_snapshot.line_len(start_row))
                                 .to_display_point(&map)
                     }
                 }
@@ -997,7 +998,7 @@ fn up_down_buffer_rows(
         map.fold_snapshot
             .clip_point(FoldPoint::new(start.row(), 0), Bias::Left),
     );
-    let select_nth_wrapped_row = point.row() - begin_folded_line.row();
+    let select_nth_wrapped_row = point.row().0 - begin_folded_line.row().0;
 
     let (goal_wrap, goal_x) = match goal {
         SelectionGoal::WrappedHorizontalPosition((row, x)) => (row, x),
@@ -1020,7 +1021,7 @@ fn up_down_buffer_rows(
 
     let mut i = 0;
     while i < goal_wrap && begin_folded_line.row() < map.max_point().row() {
-        let next_folded_line = DisplayPoint::new(begin_folded_line.row() + 1, 0);
+        let next_folded_line = DisplayPoint::new(begin_folded_line.row().next_row(), 0);
         if map
             .display_point_to_fold_point(next_folded_line, Bias::Right)
             .row()
@@ -1215,7 +1216,7 @@ fn previous_word_end(
     let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
     let mut point = point.to_point(map);
 
-    if point.column < map.buffer_snapshot.line_len(point.row) {
+    if point.column < map.buffer_snapshot.line_len(MultiBufferRow(point.row)) {
         point.column += 1;
     }
     for _ in 0..times {
@@ -1375,7 +1376,7 @@ fn previous_subword_end(
     let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
     let mut point = point.to_point(map);
 
-    if point.column < map.buffer_snapshot.line_len(point.row) {
+    if point.column < map.buffer_snapshot.line_len(MultiBufferRow(point.row)) {
         point.column += 1;
     }
     for _ in 0..times {
@@ -1497,7 +1498,7 @@ fn end_of_document(
     let new_row = if let Some(line) = line {
         (line - 1) as u32
     } else {
-        map.max_buffer_row()
+        map.max_buffer_row().0
     };
 
     let new_point = Point::new(new_row, point.column());
@@ -1672,22 +1673,24 @@ fn window_top(
         .anchor
         .to_display_point(map);
 
-    if first_visible_line.row() != 0 && text_layout_details.vertical_scroll_margin as usize > times
+    if first_visible_line.row() != DisplayRow(0)
+        && text_layout_details.vertical_scroll_margin as usize > times
     {
         times = text_layout_details.vertical_scroll_margin.ceil() as usize;
     }
 
     if let Some(visible_rows) = text_layout_details.visible_rows {
-        let bottom_row = first_visible_line.row() + visible_rows as u32;
-        let new_row = (first_visible_line.row() + (times as u32))
+        let bottom_row = first_visible_line.row().0 + visible_rows as u32;
+        let new_row = (first_visible_line.row().0 + (times as u32))
             .min(bottom_row)
-            .min(map.max_point().row());
+            .min(map.max_point().row().0);
         let new_col = point.column().min(map.line_len(first_visible_line.row()));
 
-        let new_point = DisplayPoint::new(new_row, new_col);
+        let new_point = DisplayPoint::new(DisplayRow(new_row), new_col);
         (map.clip_point(new_point, Bias::Left), SelectionGoal::None)
     } else {
-        let new_row = (first_visible_line.row() + (times as u32)).min(map.max_point().row());
+        let new_row =
+            DisplayRow((first_visible_line.row().0 + (times as u32)).min(map.max_point().row().0));
         let new_col = point.column().min(map.line_len(first_visible_line.row()));
 
         let new_point = DisplayPoint::new(new_row, new_col);
@@ -1707,10 +1710,11 @@ fn window_middle(
             .to_display_point(map);
 
         let max_visible_rows =
-            (visible_rows as u32).min(map.max_point().row() - first_visible_line.row());
+            (visible_rows as u32).min(map.max_point().row().0 - first_visible_line.row().0);
 
         let new_row =
-            (first_visible_line.row() + (max_visible_rows / 2)).min(map.max_point().row());
+            (first_visible_line.row().0 + (max_visible_rows / 2)).min(map.max_point().row().0);
+        let new_row = DisplayRow(new_row);
         let new_col = point.column().min(map.line_len(new_row));
         let new_point = DisplayPoint::new(new_row, new_col);
         (map.clip_point(new_point, Bias::Left), SelectionGoal::None)
@@ -1730,18 +1734,19 @@ fn window_bottom(
             .scroll_anchor
             .anchor
             .to_display_point(map);
-        let bottom_row = first_visible_line.row()
+        let bottom_row = first_visible_line.row().0
             + (visible_rows + text_layout_details.scroll_anchor.offset.y - 1.).floor() as u32;
-        if bottom_row < map.max_point().row()
+        if bottom_row < map.max_point().row().0
             && text_layout_details.vertical_scroll_margin as usize > times
         {
             times = text_layout_details.vertical_scroll_margin.ceil() as usize;
         }
-        let bottom_row_capped = bottom_row.min(map.max_point().row());
-        let new_row = if bottom_row_capped.saturating_sub(times as u32) < first_visible_line.row() {
+        let bottom_row_capped = bottom_row.min(map.max_point().row().0);
+        let new_row = if bottom_row_capped.saturating_sub(times as u32) < first_visible_line.row().0
+        {
             first_visible_line.row()
         } else {
-            bottom_row_capped.saturating_sub(times as u32)
+            DisplayRow(bottom_row_capped.saturating_sub(times as u32))
         };
         let new_col = point.column().min(map.line_len(new_row));
         let new_point = DisplayPoint::new(new_row, new_col);

crates/vim/src/normal.rs šŸ”—

@@ -25,6 +25,7 @@ use editor::Bias;
 use gpui::{actions, ViewContext, WindowContext};
 use language::{Point, SelectionGoal};
 use log::error;
+use multi_buffer::MultiBufferRow;
 use workspace::Workspace;
 
 use self::{
@@ -314,7 +315,7 @@ fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContex
                     .collect();
                 let edits = selection_start_rows.into_iter().map(|row| {
                     let indent = snapshot
-                        .indent_size_for_line(row)
+                        .indent_size_for_line(MultiBufferRow(row))
                         .chars()
                         .collect::<String>();
                     let start_of_line = Point::new(row, 0);
@@ -349,10 +350,10 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex
                     .collect();
                 let edits = selection_end_rows.into_iter().map(|row| {
                     let indent = snapshot
-                        .indent_size_for_line(row)
+                        .indent_size_for_line(MultiBufferRow(row))
                         .chars()
                         .collect::<String>();
-                    let end_of_line = Point::new(row, snapshot.line_len(row));
+                    let end_of_line = Point::new(row, snapshot.line_len(MultiBufferRow(row)));
                     (end_of_line..end_of_line, "\n".to_string() + &indent)
                 });
                 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {

crates/vim/src/normal/case.rs šŸ”—

@@ -1,6 +1,7 @@
 use editor::scroll::Autoscroll;
 use gpui::ViewContext;
 use language::{Bias, Point};
+use multi_buffer::MultiBufferRow;
 use workspace::Workspace;
 
 use crate::{
@@ -48,8 +49,10 @@ where
                 match vim.state().mode {
                     Mode::VisualLine => {
                         let start = Point::new(selection.start.row, 0);
-                        let end =
-                            Point::new(selection.end.row, snapshot.line_len(selection.end.row));
+                        let end = Point::new(
+                            selection.end.row,
+                            snapshot.line_len(MultiBufferRow(selection.end.row)),
+                        );
                         ranges.push(start..end);
                         cursor_positions.push(start..start);
                     }
@@ -71,7 +74,7 @@ where
                         }
                         ranges.push(start..end);
 
-                        if end.column == snapshot.line_len(end.row) {
+                        if end.column == snapshot.line_len(MultiBufferRow(end.row)) {
                             end = snapshot.clip_point(end - Point::new(0, 1), Bias::Left);
                         }
                         cursor_positions.push(end..end)

crates/vim/src/normal/delete.rs šŸ”—

@@ -7,6 +7,7 @@ use editor::{
 };
 use gpui::WindowContext;
 use language::{Point, Selection};
+use multi_buffer::MultiBufferRow;
 
 pub fn delete_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
     vim.stop_recording();
@@ -29,7 +30,7 @@ pub fn delete_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &m
                         if selection.is_empty()
                             && map
                                 .buffer_snapshot
-                                .line_len(selection.start.to_point(&map).row)
+                                .line_len(MultiBufferRow(selection.start.to_point(&map).row))
                                 == 0
                         {
                             selection.end = map
@@ -79,7 +80,7 @@ pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut Windo
                     let mut move_selection_start_to_previous_line =
                         |map: &DisplaySnapshot, selection: &mut Selection<DisplayPoint>| {
                             let start = selection.start.to_offset(map, Bias::Left);
-                            if selection.start.row() > 0 {
+                            if selection.start.row().0 > 0 {
                                 should_move_to_start.insert(selection.id);
                                 selection.start = (start - '\n'.len_utf8()).to_display_point(map);
                             }

crates/vim/src/normal/paste.rs šŸ”—

@@ -2,6 +2,7 @@ use std::cmp;
 
 use editor::{
     display_map::ToDisplayPoint, movement, scroll::Autoscroll, ClipboardSelection, DisplayPoint,
+    RowExt,
 };
 use gpui::{impl_actions, AppContext, ViewContext};
 use language::{Bias, SelectionGoal};
@@ -99,13 +100,13 @@ fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
                         .map(|selection| cmp::min(selection.start.column(), selection.end.column()))
                         .min()
                         .unwrap();
-                    let mut row = current_selections.last().unwrap().end.row() + 1;
+                    let mut row = current_selections.last().unwrap().end.row().next_row();
                     while i < clipboard_selections.len() {
                         let cursor =
                             display_map.clip_point(DisplayPoint::new(row, left), Bias::Left);
                         selections_to_process.push((cursor..cursor, false));
                         i += 1;
-                        row += 1;
+                        row.0 += 1;
                     }
                 }
 

crates/vim/src/normal/scroll.rs šŸ”—

@@ -1,6 +1,8 @@
 use crate::Vim;
 use editor::{
-    display_map::ToDisplayPoint, scroll::ScrollAmount, DisplayPoint, Editor, EditorSettings,
+    display_map::{DisplayRow, ToDisplayPoint},
+    scroll::ScrollAmount,
+    DisplayPoint, Editor, EditorSettings,
 };
 use gpui::{actions, ViewContext};
 use language::Bias;
@@ -85,11 +87,13 @@ fn scroll_editor(
 
                 if preserve_cursor_position {
                     let old_top = old_top_anchor.to_display_point(map);
-                    let new_row = top.row() + selection.head().row() - old_top.row();
+                    let new_row =
+                        DisplayRow(top.row().0 + selection.head().row().0 - old_top.row().0);
                     head = map.clip_point(DisplayPoint::new(new_row, head.column()), Bias::Left)
                 }
-                let min_row = top.row() + vertical_scroll_margin as u32;
-                let max_row = top.row() + visible_rows - vertical_scroll_margin as u32 - 1;
+                let min_row = DisplayRow(top.row().0 + vertical_scroll_margin as u32);
+                let max_row =
+                    DisplayRow(top.row().0 + visible_rows - vertical_scroll_margin as u32 - 1);
 
                 let new_head = if head.row() < min_row {
                     map.clip_point(DisplayPoint::new(min_row, head.column()), Bias::Left)

crates/vim/src/normal/search.rs šŸ”—

@@ -509,7 +509,7 @@ fn parse_replace_all(query: &str) -> Replacement {
 
 #[cfg(test)]
 mod test {
-    use editor::DisplayPoint;
+    use editor::{display_map::DisplayRow, DisplayPoint};
     use indoc::indoc;
     use search::BufferSearchBar;
 
@@ -582,7 +582,7 @@ mod test {
             let highlights = editor.all_text_background_highlights(cx);
             assert_eq!(3, highlights.len());
             assert_eq!(
-                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
+                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 2),
                 highlights[0].0
             )
         });

crates/vim/src/object.rs šŸ”—

@@ -14,6 +14,7 @@ use itertools::Itertools;
 
 use gpui::{actions, impl_actions, ViewContext, WindowContext};
 use language::{char_kind, BufferSnapshot, CharKind, Point, Selection};
+use multi_buffer::MultiBufferRow;
 use serde::Deserialize;
 use workspace::Workspace;
 
@@ -724,7 +725,7 @@ fn paragraph(
     let paragraph_end_row = paragraph_end.row();
     let paragraph_ends_with_eof = paragraph_end_row == map.max_point().row();
     let point = relative_to.to_point(map);
-    let current_line_is_empty = map.buffer_snapshot.is_line_blank(point.row);
+    let current_line_is_empty = map.buffer_snapshot.is_line_blank(MultiBufferRow(point.row));
 
     if around {
         if paragraph_ends_with_eof {
@@ -733,13 +734,13 @@ fn paragraph(
             }
 
             let paragraph_start_row = paragraph_start.row();
-            if paragraph_start_row != 0 {
+            if paragraph_start_row.0 != 0 {
                 let previous_paragraph_last_line_start =
-                    Point::new(paragraph_start_row - 1, 0).to_display_point(map);
+                    Point::new(paragraph_start_row.0 - 1, 0).to_display_point(map);
                 paragraph_start = start_of_paragraph(map, previous_paragraph_last_line_start);
             }
         } else {
-            let next_paragraph_start = Point::new(paragraph_end_row + 1, 0).to_display_point(map);
+            let next_paragraph_start = Point::new(paragraph_end_row.0 + 1, 0).to_display_point(map);
             paragraph_end = end_of_paragraph(map, next_paragraph_start);
         }
     }
@@ -756,10 +757,10 @@ pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) ->
         return DisplayPoint::zero();
     }
 
-    let is_current_line_blank = map.buffer_snapshot.is_line_blank(point.row);
+    let is_current_line_blank = map.buffer_snapshot.is_line_blank(MultiBufferRow(point.row));
 
     for row in (0..point.row).rev() {
-        let blank = map.buffer_snapshot.is_line_blank(row);
+        let blank = map.buffer_snapshot.is_line_blank(MultiBufferRow(row));
         if blank != is_current_line_blank {
             return Point::new(row + 1, 0).to_display_point(map);
         }
@@ -773,18 +774,21 @@ pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) ->
 /// The trailing newline is excluded from the paragraph.
 pub fn end_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
     let point = display_point.to_point(map);
-    if point.row == map.max_buffer_row() {
+    if point.row == map.max_buffer_row().0 {
         return map.max_point();
     }
 
-    let is_current_line_blank = map.buffer_snapshot.is_line_blank(point.row);
+    let is_current_line_blank = map.buffer_snapshot.is_line_blank(MultiBufferRow(point.row));
 
-    for row in point.row + 1..map.max_buffer_row() + 1 {
-        let blank = map.buffer_snapshot.is_line_blank(row);
+    for row in point.row + 1..map.max_buffer_row().0 + 1 {
+        let blank = map.buffer_snapshot.is_line_blank(MultiBufferRow(row));
         if blank != is_current_line_blank {
             let previous_row = row - 1;
-            return Point::new(previous_row, map.buffer_snapshot.line_len(previous_row))
-                .to_display_point(map);
+            return Point::new(
+                previous_row,
+                map.buffer_snapshot.line_len(MultiBufferRow(previous_row)),
+            )
+            .to_display_point(map);
         }
     }
 

crates/vim/src/test.rs šŸ”—

@@ -6,7 +6,7 @@ mod vim_test_context;
 use std::time::Duration;
 
 use command_palette::CommandPalette;
-use editor::DisplayPoint;
+use editor::{display_map::DisplayRow, DisplayPoint};
 use futures::StreamExt;
 use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext};
 pub use neovim_backed_binding_test_context::*;
@@ -235,7 +235,7 @@ async fn test_selection_on_search(cx: &mut gpui::TestAppContext) {
         let highlights = editor.all_text_background_highlights(cx);
         assert_eq!(3, highlights.len());
         assert_eq!(
-            DisplayPoint::new(2, 0)..DisplayPoint::new(2, 2),
+            DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 2),
             highlights[0].0
         )
     });

crates/vim/src/utils.rs šŸ”—

@@ -3,6 +3,7 @@ use std::time::Duration;
 use editor::{ClipboardSelection, Editor};
 use gpui::{ClipboardItem, ViewContext};
 use language::{CharKind, Point};
+use multi_buffer::MultiBufferRow;
 use settings::Settings;
 
 use crate::{state::Mode, UseSystemClipboard, Vim, VimSettings};
@@ -74,10 +75,10 @@ fn copy_selections_content_internal(
             // contains a newline (so that delete works as expected). We undo that change
             // here.
             let is_last_line = linewise
-                && end.row == buffer.max_buffer_row()
+                && end.row == buffer.max_buffer_row().0
                 && buffer.max_point().column > 0
-                && start.row < buffer.max_buffer_row()
-                && start == Point::new(start.row, buffer.line_len(start.row));
+                && start.row < buffer.max_buffer_row().0
+                && start == Point::new(start.row, buffer.line_len(MultiBufferRow(start.row)));
 
             if is_last_line {
                 start = Point::new(start.row + 1, 0);
@@ -96,7 +97,7 @@ fn copy_selections_content_internal(
             clipboard_selections.push(ClipboardSelection {
                 len: text.len() - initial_len,
                 is_entire_line: linewise,
-                first_line_indent: buffer.indent_size_for_line(start.row).len,
+                first_line_indent: buffer.indent_size_for_line(MultiBufferRow(start.row)).len,
             });
         }
     }

crates/vim/src/visual.rs šŸ”—

@@ -9,6 +9,7 @@ use editor::{
 };
 use gpui::{actions, ViewContext, WindowContext};
 use language::{Point, Selection, SelectionGoal};
+use multi_buffer::MultiBufferRow;
 use search::BufferSearchBar;
 use util::ResultExt;
 use workspace::{searchable::Direction, Workspace};
@@ -251,9 +252,9 @@ pub fn visual_block_motion(
                 break;
             }
             if tail.row() > head.row() {
-                row -= 1
+                row.0 -= 1
             } else {
-                row += 1
+                row.0 += 1
             }
         }
 
@@ -313,13 +314,15 @@ pub fn visual_object(object: Object, cx: &mut WindowContext) {
                             // trailing newline is included in its selection from the beginning.
                             if object == Object::Paragraph && range.start != range.end {
                                 let row_of_selection_end_line = selection.end.to_point(map).row;
-                                let new_selection_end =
-                                    if map.buffer_snapshot.line_len(row_of_selection_end_line) == 0
-                                    {
-                                        Point::new(row_of_selection_end_line + 1, 0)
-                                    } else {
-                                        Point::new(row_of_selection_end_line, 1)
-                                    };
+                                let new_selection_end = if map
+                                    .buffer_snapshot
+                                    .line_len(MultiBufferRow(row_of_selection_end_line))
+                                    == 0
+                                {
+                                    Point::new(row_of_selection_end_line + 1, 0)
+                                } else {
+                                    Point::new(row_of_selection_end_line, 1)
+                                };
                                 selection.end = new_selection_end.to_display_point(map);
                             }
                         }

crates/zed/src/zed.rs šŸ”—

@@ -910,7 +910,7 @@ mod tests {
     use super::*;
     use assets::Assets;
     use collections::HashSet;
-    use editor::{scroll::Autoscroll, DisplayPoint, Editor};
+    use editor::{display_map::DisplayRow, scroll::Autoscroll, DisplayPoint, Editor};
     use gpui::{
         actions, Action, AnyWindowHandle, AppContext, AssetSource, BorrowAppContext, Entity,
         TestAppContext, VisualTestContext, WindowHandle,
@@ -2229,9 +2229,8 @@ mod tests {
             .update(cx, |_, cx| {
                 editor1.update(cx, |editor, cx| {
                     editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                        s.select_display_ranges(
-                            [DisplayPoint::new(10, 0)..DisplayPoint::new(10, 0)],
-                        )
+                        s.select_display_ranges([DisplayPoint::new(DisplayRow(10), 0)
+                            ..DisplayPoint::new(DisplayRow(10), 0)])
                     });
                 });
             })
@@ -2256,9 +2255,8 @@ mod tests {
             .update(cx, |_, cx| {
                 editor3.update(cx, |editor, cx| {
                     editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                        s.select_display_ranges(
-                            [DisplayPoint::new(12, 0)..DisplayPoint::new(12, 0)],
-                        )
+                        s.select_display_ranges([DisplayPoint::new(DisplayRow(12), 0)
+                            ..DisplayPoint::new(DisplayRow(12), 0)])
                     });
                     editor.newline(&Default::default(), cx);
                     editor.newline(&Default::default(), cx);
@@ -2279,7 +2277,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file3.clone(), DisplayPoint::new(16, 0), 12.5)
+            (file3.clone(), DisplayPoint::new(DisplayRow(16), 0), 12.5)
         );
 
         workspace
@@ -2289,7 +2287,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file3.clone(), DisplayPoint::new(0, 0), 0.)
+            (file3.clone(), DisplayPoint::new(DisplayRow(0), 0), 0.)
         );
 
         workspace
@@ -2299,7 +2297,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file2.clone(), DisplayPoint::new(0, 0), 0.)
+            (file2.clone(), DisplayPoint::new(DisplayRow(0), 0), 0.)
         );
 
         workspace
@@ -2309,7 +2307,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file1.clone(), DisplayPoint::new(10, 0), 0.)
+            (file1.clone(), DisplayPoint::new(DisplayRow(10), 0), 0.)
         );
 
         workspace
@@ -2319,7 +2317,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file1.clone(), DisplayPoint::new(0, 0), 0.)
+            (file1.clone(), DisplayPoint::new(DisplayRow(0), 0), 0.)
         );
 
         // Go back one more time and ensure we don't navigate past the first item in the history.
@@ -2330,7 +2328,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file1.clone(), DisplayPoint::new(0, 0), 0.)
+            (file1.clone(), DisplayPoint::new(DisplayRow(0), 0), 0.)
         );
 
         workspace
@@ -2340,7 +2338,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file1.clone(), DisplayPoint::new(10, 0), 0.)
+            (file1.clone(), DisplayPoint::new(DisplayRow(10), 0), 0.)
         );
 
         workspace
@@ -2350,7 +2348,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file2.clone(), DisplayPoint::new(0, 0), 0.)
+            (file2.clone(), DisplayPoint::new(DisplayRow(0), 0), 0.)
         );
 
         // Go forward to an item that has been closed, ensuring it gets re-opened at the same
@@ -2373,7 +2371,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file3.clone(), DisplayPoint::new(0, 0), 0.)
+            (file3.clone(), DisplayPoint::new(DisplayRow(0), 0), 0.)
         );
 
         workspace
@@ -2383,7 +2381,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file3.clone(), DisplayPoint::new(16, 0), 12.5)
+            (file3.clone(), DisplayPoint::new(DisplayRow(16), 0), 12.5)
         );
 
         workspace
@@ -2393,7 +2391,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file3.clone(), DisplayPoint::new(0, 0), 0.)
+            (file3.clone(), DisplayPoint::new(DisplayRow(0), 0), 0.)
         );
 
         // Go back to an item that has been closed and removed from disk
@@ -2422,7 +2420,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file2.clone(), DisplayPoint::new(0, 0), 0.)
+            (file2.clone(), DisplayPoint::new(DisplayRow(0), 0), 0.)
         );
         workspace
             .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
@@ -2431,7 +2429,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file3.clone(), DisplayPoint::new(0, 0), 0.)
+            (file3.clone(), DisplayPoint::new(DisplayRow(0), 0), 0.)
         );
 
         // Modify file to collapse multiple nav history entries into the same location.
@@ -2440,9 +2438,8 @@ mod tests {
             .update(cx, |_, cx| {
                 editor1.update(cx, |editor, cx| {
                     editor.change_selections(None, cx, |s| {
-                        s.select_display_ranges(
-                            [DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)],
-                        )
+                        s.select_display_ranges([DisplayPoint::new(DisplayRow(15), 0)
+                            ..DisplayPoint::new(DisplayRow(15), 0)])
                     })
                 });
             })
@@ -2452,9 +2449,8 @@ mod tests {
                 .update(cx, |_, cx| {
                     editor1.update(cx, |editor, cx| {
                         editor.change_selections(None, cx, |s| {
-                            s.select_display_ranges([
-                                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)
-                            ])
+                            s.select_display_ranges([DisplayPoint::new(DisplayRow(3), 0)
+                                ..DisplayPoint::new(DisplayRow(3), 0)])
                         });
                     });
                 })
@@ -2464,9 +2460,8 @@ mod tests {
                 .update(cx, |_, cx| {
                     editor1.update(cx, |editor, cx| {
                         editor.change_selections(None, cx, |s| {
-                            s.select_display_ranges([
-                                DisplayPoint::new(13, 0)..DisplayPoint::new(13, 0)
-                            ])
+                            s.select_display_ranges([DisplayPoint::new(DisplayRow(13), 0)
+                                ..DisplayPoint::new(DisplayRow(13), 0)])
                         })
                     });
                 })
@@ -2477,9 +2472,8 @@ mod tests {
                 editor1.update(cx, |editor, cx| {
                     editor.transact(cx, |editor, cx| {
                         editor.change_selections(None, cx, |s| {
-                            s.select_display_ranges([
-                                DisplayPoint::new(2, 0)..DisplayPoint::new(14, 0)
-                            ])
+                            s.select_display_ranges([DisplayPoint::new(DisplayRow(2), 0)
+                                ..DisplayPoint::new(DisplayRow(14), 0)])
                         });
                         editor.insert("", cx);
                     })
@@ -2491,7 +2485,8 @@ mod tests {
             .update(cx, |_, cx| {
                 editor1.update(cx, |editor, cx| {
                     editor.change_selections(None, cx, |s| {
-                        s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
+                        s.select_display_ranges([DisplayPoint::new(DisplayRow(1), 0)
+                            ..DisplayPoint::new(DisplayRow(1), 0)])
                     })
                 });
             })
@@ -2503,7 +2498,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file1.clone(), DisplayPoint::new(2, 0), 0.)
+            (file1.clone(), DisplayPoint::new(DisplayRow(2), 0), 0.)
         );
         workspace
             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
@@ -2512,7 +2507,7 @@ mod tests {
             .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
-            (file1.clone(), DisplayPoint::new(3, 0), 0.)
+            (file1.clone(), DisplayPoint::new(DisplayRow(3), 0), 0.)
         );
 
         fn active_location(