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

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>
This commit is contained in:
Kirill Bulatov 2024-05-11 00:06:51 +03:00 committed by GitHub
parent 38f110852f
commit df41435d1a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 1726 additions and 1220 deletions

3
Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@ -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. //! 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 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 gpui::{px, Pixels, WindowTextSystem};
use language::Point; use language::Point;
use multi_buffer::MultiBufferSnapshot; use multi_buffer::{MultiBufferRow, MultiBufferSnapshot};
use serde::Deserialize; use serde::Deserialize;
use std::{ops::Range, sync::Arc}; use std::{ops::Range, sync::Arc};
@ -35,7 +37,7 @@ pub struct TextLayoutDetails {
pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint { pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
if point.column() > 0 { if point.column() > 0 {
*point.column_mut() -= 1; *point.column_mut() -= 1;
} else if point.row() > 0 { } else if point.row().0 > 0 {
*point.row_mut() -= 1; *point.row_mut() -= 1;
*point.column_mut() = map.line_len(point.row()); *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), _ => 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( let mut point = map.clip_point(
DisplayPoint::new(prev_row, map.line_len(prev_row)), DisplayPoint::new(prev_row, map.line_len(prev_row)),
Bias::Left, Bias::Left,
@ -137,7 +139,7 @@ pub(crate) fn up_by_rows(
} else if preserve_column_at_start { } else if preserve_column_at_start {
return (start, goal); return (start, goal);
} else { } else {
point = DisplayPoint::new(0, 0); point = DisplayPoint::new(DisplayRow(0), 0);
goal_x = px(0.); goal_x = px(0.);
} }
@ -166,7 +168,7 @@ pub(crate) fn down_by_rows(
_ => map.x_for_display_point(start, text_layout_details), _ => 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); let mut point = map.clip_point(DisplayPoint::new(new_row, 0), Bias::Right);
if point.row() > start.row() { if point.row() > start.row() {
*point.column_mut() = map.display_column_for_x(point.row(), goal_x, text_layout_details) *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 soft_line_start = map.clip_point(DisplayPoint::new(display_point.row(), 0), Bias::Right);
let indent_start = Point::new( let indent_start = Point::new(
point.row, 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); .to_display_point(map);
let line_start = map.prev_line_boundary(point).1; 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; let mut found_non_blank_line = false;
for row in (0..point.row + 1).rev() { 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 found_non_blank_line && blank {
if count <= 1 { if count <= 1 {
return Point::new(row, 0).to_display_point(map); return Point::new(row, 0).to_display_point(map);
@ -349,13 +353,13 @@ pub fn end_of_paragraph(
mut count: usize, mut count: usize,
) -> DisplayPoint { ) -> DisplayPoint {
let point = display_point.to_point(map); 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(); return map.max_point();
} }
let mut found_non_blank_line = false; let mut found_non_blank_line = false;
for row in point.row..map.max_buffer_row() + 1 { for row in point.row..map.max_buffer_row().next_row().0 {
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 found_non_blank_line && blank {
if count <= 1 { if count <= 1 {
return Point::new(row, 0).to_display_point(map); 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; let mut start = range.start;
// Loop over all the covered rows until the one containing the range end // Loop over all the covered rows until the one containing the range end
for row in range.start.row()..range.end.row() { for row in range.start.row().0..range.end.row().0 {
let row_end_column = map.line_len(row); let row_end_column = map.line_len(DisplayRow(row));
let end = map.clip_point(DisplayPoint::new(row, row_end_column), Bias::Left); let end = map.clip_point(
DisplayPoint::new(DisplayRow(row), row_end_column),
Bias::Left,
);
if start != end { if start != end {
result.push(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. // Add the final range from the start of the last end to the original range end.
@ -570,7 +577,7 @@ mod tests {
use crate::{ use crate::{
display_map::Inlay, display_map::Inlay,
test::{editor_test_context::EditorTestContext, marked_display_snapshot}, 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 gpui::{font, Context as _};
use language::Capability; use language::Capability;
@ -900,126 +907,126 @@ mod tests {
assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn"); assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn");
let col_2_x = let col_2_x = snapshot
snapshot.x_for_display_point(DisplayPoint::new(2, 2), &text_layout_details); .x_for_display_point(DisplayPoint::new(DisplayRow(2), 2), &text_layout_details);
// Can't move up into the first excerpt's header // Can't move up into the first excerpt's header
assert_eq!( assert_eq!(
up( up(
&snapshot, &snapshot,
DisplayPoint::new(2, 2), DisplayPoint::new(DisplayRow(2), 2),
SelectionGoal::HorizontalPosition(col_2_x.0), SelectionGoal::HorizontalPosition(col_2_x.0),
false, false,
&text_layout_details &text_layout_details
), ),
( (
DisplayPoint::new(2, 0), DisplayPoint::new(DisplayRow(2), 0),
SelectionGoal::HorizontalPosition(0.0) SelectionGoal::HorizontalPosition(0.0)
), ),
); );
assert_eq!( assert_eq!(
up( up(
&snapshot, &snapshot,
DisplayPoint::new(2, 0), DisplayPoint::new(DisplayRow(2), 0),
SelectionGoal::None, SelectionGoal::None,
false, false,
&text_layout_details &text_layout_details
), ),
( (
DisplayPoint::new(2, 0), DisplayPoint::new(DisplayRow(2), 0),
SelectionGoal::HorizontalPosition(0.0) SelectionGoal::HorizontalPosition(0.0)
), ),
); );
let col_4_x = let col_4_x = snapshot
snapshot.x_for_display_point(DisplayPoint::new(3, 4), &text_layout_details); .x_for_display_point(DisplayPoint::new(DisplayRow(3), 4), &text_layout_details);
// Move up and down within first excerpt // Move up and down within first excerpt
assert_eq!( assert_eq!(
up( up(
&snapshot, &snapshot,
DisplayPoint::new(3, 4), DisplayPoint::new(DisplayRow(3), 4),
SelectionGoal::HorizontalPosition(col_4_x.0), SelectionGoal::HorizontalPosition(col_4_x.0),
false, false,
&text_layout_details &text_layout_details
), ),
( (
DisplayPoint::new(2, 3), DisplayPoint::new(DisplayRow(2), 3),
SelectionGoal::HorizontalPosition(col_4_x.0) SelectionGoal::HorizontalPosition(col_4_x.0)
), ),
); );
assert_eq!( assert_eq!(
down( down(
&snapshot, &snapshot,
DisplayPoint::new(2, 3), DisplayPoint::new(DisplayRow(2), 3),
SelectionGoal::HorizontalPosition(col_4_x.0), SelectionGoal::HorizontalPosition(col_4_x.0),
false, false,
&text_layout_details &text_layout_details
), ),
( (
DisplayPoint::new(3, 4), DisplayPoint::new(DisplayRow(3), 4),
SelectionGoal::HorizontalPosition(col_4_x.0) SelectionGoal::HorizontalPosition(col_4_x.0)
), ),
); );
let col_5_x = let col_5_x = snapshot
snapshot.x_for_display_point(DisplayPoint::new(6, 5), &text_layout_details); .x_for_display_point(DisplayPoint::new(DisplayRow(6), 5), &text_layout_details);
// Move up and down across second excerpt's header // Move up and down across second excerpt's header
assert_eq!( assert_eq!(
up( up(
&snapshot, &snapshot,
DisplayPoint::new(6, 5), DisplayPoint::new(DisplayRow(6), 5),
SelectionGoal::HorizontalPosition(col_5_x.0), SelectionGoal::HorizontalPosition(col_5_x.0),
false, false,
&text_layout_details &text_layout_details
), ),
( (
DisplayPoint::new(3, 4), DisplayPoint::new(DisplayRow(3), 4),
SelectionGoal::HorizontalPosition(col_5_x.0) SelectionGoal::HorizontalPosition(col_5_x.0)
), ),
); );
assert_eq!( assert_eq!(
down( down(
&snapshot, &snapshot,
DisplayPoint::new(3, 4), DisplayPoint::new(DisplayRow(3), 4),
SelectionGoal::HorizontalPosition(col_5_x.0), SelectionGoal::HorizontalPosition(col_5_x.0),
false, false,
&text_layout_details &text_layout_details
), ),
( (
DisplayPoint::new(6, 5), DisplayPoint::new(DisplayRow(6), 5),
SelectionGoal::HorizontalPosition(col_5_x.0) SelectionGoal::HorizontalPosition(col_5_x.0)
), ),
); );
let max_point_x = let max_point_x = snapshot
snapshot.x_for_display_point(DisplayPoint::new(7, 2), &text_layout_details); .x_for_display_point(DisplayPoint::new(DisplayRow(7), 2), &text_layout_details);
// Can't move down off the end // Can't move down off the end
assert_eq!( assert_eq!(
down( down(
&snapshot, &snapshot,
DisplayPoint::new(7, 0), DisplayPoint::new(DisplayRow(7), 0),
SelectionGoal::HorizontalPosition(0.0), SelectionGoal::HorizontalPosition(0.0),
false, false,
&text_layout_details &text_layout_details
), ),
( (
DisplayPoint::new(7, 2), DisplayPoint::new(DisplayRow(7), 2),
SelectionGoal::HorizontalPosition(max_point_x.0) SelectionGoal::HorizontalPosition(max_point_x.0)
), ),
); );
assert_eq!( assert_eq!(
down( down(
&snapshot, &snapshot,
DisplayPoint::new(7, 2), DisplayPoint::new(DisplayRow(7), 2),
SelectionGoal::HorizontalPosition(max_point_x.0), SelectionGoal::HorizontalPosition(max_point_x.0),
false, false,
&text_layout_details &text_layout_details
), ),
( (
DisplayPoint::new(7, 2), DisplayPoint::new(DisplayRow(7), 2),
SelectionGoal::HorizontalPosition(max_point_x.0) SelectionGoal::HorizontalPosition(max_point_x.0)
), ),
); );

View File

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

View File

@ -37,7 +37,7 @@ impl Editor {
let scroll_margin_rows = self.vertical_scroll_margin() as u32; let scroll_margin_rows = self.vertical_scroll_margin() as u32;
let mut new_screen_top = self.selections.newest_display(cx).head(); 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; *new_screen_top.column_mut() = 0;
let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top); 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(); 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; *new_screen_top.column_mut() = 0;
let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top); 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(); let mut new_screen_top = self.selections.newest_display(cx).head();
*new_screen_top.row_mut() = new_screen_top *new_screen_top.row_mut() = new_screen_top
.row() .row()
.0
.saturating_sub(visible_rows.saturating_sub(scroll_margin_rows)); .saturating_sub(visible_rows.saturating_sub(scroll_margin_rows));
*new_screen_top.column_mut() = 0; *new_screen_top.column_mut() = 0;
let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);

View File

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

View File

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

View File

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

View File

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

View File

@ -30,18 +30,6 @@ pub struct DiffHunk<T> {
pub diff_base_byte_range: Range<usize>, 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> { impl sum_tree::Item for DiffHunk<Anchor> {
type Summary = DiffHunkSummary; type Summary = DiffHunkSummary;

View File

@ -352,6 +352,7 @@ mod tests {
editor editor
.highlighted_display_rows(HashSet::default(), cx) .highlighted_display_rows(HashSet::default(), cx)
.into_keys() .into_keys()
.map(|r| r.0)
.collect() .collect()
}) })
} }

View File

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

View File

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

View File

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

View File

@ -486,6 +486,7 @@ mod tests {
editor editor
.highlighted_display_rows(HashSet::default(), cx) .highlighted_display_rows(HashSet::default(), cx)
.into_keys() .into_keys()
.map(|r| r.0)
.collect() .collect()
}) })
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ use std::cmp;
use editor::{ use editor::{
display_map::ToDisplayPoint, movement, scroll::Autoscroll, ClipboardSelection, DisplayPoint, display_map::ToDisplayPoint, movement, scroll::Autoscroll, ClipboardSelection, DisplayPoint,
RowExt,
}; };
use gpui::{impl_actions, AppContext, ViewContext}; use gpui::{impl_actions, AppContext, ViewContext};
use language::{Bias, SelectionGoal}; 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())) .map(|selection| cmp::min(selection.start.column(), selection.end.column()))
.min() .min()
.unwrap(); .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() { while i < clipboard_selections.len() {
let cursor = let cursor =
display_map.clip_point(DisplayPoint::new(row, left), Bias::Left); display_map.clip_point(DisplayPoint::new(row, left), Bias::Left);
selections_to_process.push((cursor..cursor, false)); selections_to_process.push((cursor..cursor, false));
i += 1; i += 1;
row += 1; row.0 += 1;
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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