Detailed changes
@@ -7754,15 +7754,21 @@ dependencies = [
"ctor",
"env_logger 0.11.6",
"futures 0.3.31",
+ "git",
"gpui",
+ "indoc",
"itertools 0.14.0",
"language",
"log",
"parking_lot",
+ "pretty_assertions",
+ "project",
"rand 0.8.5",
+ "rope",
"serde",
"settings",
"smallvec",
+ "smol",
"sum_tree",
"text",
"theme",
@@ -117,7 +117,7 @@
"ctrl-alt-space": "editor::ShowCharacterPalette",
"ctrl-;": "editor::ToggleLineNumbers",
"ctrl-k ctrl-r": "editor::RevertSelectedHunks",
- "ctrl-'": "editor::ToggleHunkDiff",
+ "ctrl-'": "editor::ToggleSelectedDiffHunks",
"ctrl-\"": "editor::ExpandAllHunkDiffs",
"ctrl-i": "editor::ShowSignatureHelp",
"alt-g b": "editor::ToggleGitBlame",
@@ -127,7 +127,7 @@
"ctrl-cmd-space": "editor::ShowCharacterPalette",
"cmd-;": "editor::ToggleLineNumbers",
"cmd-alt-z": "editor::RevertSelectedHunks",
- "cmd-'": "editor::ToggleHunkDiff",
+ "cmd-'": "editor::ToggleSelectedDiffHunks",
"cmd-\"": "editor::ExpandAllHunkDiffs",
"cmd-alt-g b": "editor::ToggleGitBlame",
"cmd-i": "editor::ShowSignatureHelp",
@@ -436,7 +436,7 @@
"bindings": {
"d": "vim::CurrentLine",
"s": ["vim::PushOperator", "DeleteSurrounds"],
- "o": "editor::ToggleHunkDiff", // "d o"
+ "o": "editor::ToggleSelectedDiffHunks", // "d o"
"p": "editor::RevertSelectedHunks" // "d p"
}
},
@@ -250,22 +250,19 @@ impl InlineAssistant {
let newest_selection = newest_selection.unwrap();
let mut codegen_ranges = Vec::new();
- for (excerpt_id, buffer, buffer_range) in
- snapshot.excerpts_in_ranges(selections.iter().map(|selection| {
+ for (buffer, buffer_range, excerpt_id) in
+ snapshot.ranges_to_buffer_ranges(selections.iter().map(|selection| {
snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end)
}))
{
- let start = Anchor {
- buffer_id: Some(buffer.remote_id()),
- excerpt_id,
- text_anchor: buffer.anchor_before(buffer_range.start),
- };
- let end = Anchor {
- buffer_id: Some(buffer.remote_id()),
+ let start = buffer.anchor_before(buffer_range.start);
+ let end = buffer.anchor_after(buffer_range.end);
+
+ codegen_ranges.push(Anchor::range_in_buffer(
excerpt_id,
- text_anchor: buffer.anchor_after(buffer_range.end),
- };
- codegen_ranges.push(start..end);
+ buffer.remote_id(),
+ start..end,
+ ));
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
self.telemetry.report_assistant_event(AssistantEvent {
@@ -823,7 +820,7 @@ impl InlineAssistant {
let ranges = multibuffer_snapshot.range_to_buffer_ranges(assist.range.clone());
ranges
.first()
- .and_then(|(excerpt, _)| excerpt.buffer().language())
+ .and_then(|(buffer, _, _)| buffer.language())
.map(|language| language.name())
});
report_assistant_event(
@@ -2648,17 +2645,17 @@ impl CodegenAlternative {
) -> Self {
let snapshot = multi_buffer.read(cx).snapshot(cx);
- let (old_excerpt, _) = snapshot
+ let (buffer, _, _) = snapshot
.range_to_buffer_ranges(range.clone())
.pop()
.unwrap();
let old_buffer = cx.new_model(|cx| {
- let text = old_excerpt.buffer().as_rope().clone();
- let line_ending = old_excerpt.buffer().line_ending();
- let language = old_excerpt.buffer().language().cloned();
+ let text = buffer.as_rope().clone();
+ let line_ending = buffer.line_ending();
+ let language = buffer.language().cloned();
let language_registry = multi_buffer
.read(cx)
- .buffer(old_excerpt.buffer_id())
+ .buffer(buffer.remote_id())
.unwrap()
.read(cx)
.language_registry();
@@ -2898,7 +2895,7 @@ impl CodegenAlternative {
let ranges = snapshot.range_to_buffer_ranges(self.range.clone());
ranges
.first()
- .and_then(|(excerpt, _)| excerpt.buffer().language())
+ .and_then(|(buffer, _, _)| buffer.language())
.map(|language| language.name())
};
@@ -255,17 +255,17 @@ impl CodegenAlternative {
) -> Self {
let snapshot = buffer.read(cx).snapshot(cx);
- let (old_excerpt, _) = snapshot
+ let (old_buffer, _, _) = snapshot
.range_to_buffer_ranges(range.clone())
.pop()
.unwrap();
let old_buffer = cx.new_model(|cx| {
- let text = old_excerpt.buffer().as_rope().clone();
- let line_ending = old_excerpt.buffer().line_ending();
- let language = old_excerpt.buffer().language().cloned();
+ let text = old_buffer.as_rope().clone();
+ let line_ending = old_buffer.line_ending();
+ let language = old_buffer.language().cloned();
let language_registry = buffer
.read(cx)
- .buffer(old_excerpt.buffer_id())
+ .buffer(old_buffer.remote_id())
.unwrap()
.read(cx)
.language_registry();
@@ -475,7 +475,7 @@ impl CodegenAlternative {
let ranges = snapshot.range_to_buffer_ranges(self.range.clone());
ranges
.first()
- .and_then(|(excerpt, _)| excerpt.buffer().language())
+ .and_then(|(buffer, _, _)| buffer.language())
.map(|language| language.name())
};
@@ -320,22 +320,18 @@ impl InlineAssistant {
let newest_selection = newest_selection.unwrap();
let mut codegen_ranges = Vec::new();
- for (excerpt_id, buffer, buffer_range) in
- snapshot.excerpts_in_ranges(selections.iter().map(|selection| {
+ for (buffer, buffer_range, excerpt_id) in
+ snapshot.ranges_to_buffer_ranges(selections.iter().map(|selection| {
snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end)
}))
{
- let start = Anchor {
- buffer_id: Some(buffer.remote_id()),
+ let anchor_range = Anchor::range_in_buffer(
excerpt_id,
- text_anchor: buffer.anchor_before(buffer_range.start),
- };
- let end = Anchor {
- buffer_id: Some(buffer.remote_id()),
- excerpt_id,
- text_anchor: buffer.anchor_after(buffer_range.end),
- };
- codegen_ranges.push(start..end);
+ buffer.remote_id(),
+ buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end),
+ );
+
+ codegen_ranges.push(anchor_range);
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
self.telemetry.report_assistant_event(AssistantEvent {
@@ -901,7 +897,7 @@ impl InlineAssistant {
let ranges = snapshot.range_to_buffer_ranges(assist.range.clone());
ranges
.first()
- .and_then(|(excerpt, _)| excerpt.buffer().language())
+ .and_then(|(buffer, _, _)| buffer.language())
.map(|language| language.name())
});
report_assistant_event(
@@ -10,7 +10,7 @@ use editor::{
ToggleCodeActions, Undo,
},
test::editor_test_context::{AssertionContextManager, EditorTestContext},
- Editor,
+ Editor, RowInfo,
};
use fs::Fs;
use futures::StreamExt;
@@ -20,7 +20,6 @@ use language::{
language_settings::{AllLanguageSettings, InlayHintSettings},
FakeLspAdapter,
};
-use multi_buffer::MultiBufferRow;
use project::{
project_settings::{InlineBlameSettings, ProjectSettings},
SERVER_PROGRESS_THROTTLE_TIMEOUT,
@@ -2019,7 +2018,15 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
let blame = editor_b.blame().expect("editor_b should have blame now");
let entries = blame.update(cx, |blame, cx| {
blame
- .blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
+ .blame_for_rows(
+ &(0..4)
+ .map(|row| RowInfo {
+ buffer_row: Some(row),
+ ..Default::default()
+ })
+ .collect::<Vec<_>>(),
+ cx,
+ )
.collect::<Vec<_>>()
});
@@ -2058,7 +2065,15 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
let blame = editor_b.blame().expect("editor_b should have blame now");
let entries = blame.update(cx, |blame, cx| {
blame
- .blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
+ .blame_for_rows(
+ &(0..4)
+ .map(|row| RowInfo {
+ buffer_row: Some(row),
+ ..Default::default()
+ })
+ .collect::<Vec<_>>(),
+ cx,
+ )
.collect::<Vec<_>>()
});
@@ -2085,7 +2100,15 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
let blame = editor_b.blame().expect("editor_b should have blame now");
let entries = blame.update(cx, |blame, cx| {
blame
- .blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
+ .blame_for_rows(
+ &(0..4)
+ .map(|row| RowInfo {
+ buffer_row: Some(row),
+ ..Default::default()
+ })
+ .collect::<Vec<_>>(),
+ cx,
+ )
.collect::<Vec<_>>()
});
@@ -2593,7 +2593,7 @@ async fn test_git_diff_base_change(
change_set_local_a.read_with(cx_a, |change_set, cx| {
let buffer = buffer_local_a.read(cx);
assert_eq!(
- change_set.base_text_string(cx).as_deref(),
+ change_set.base_text_string().as_deref(),
Some(diff_base.as_str())
);
git::diff::assert_hunks(
@@ -2621,7 +2621,7 @@ async fn test_git_diff_base_change(
change_set_remote_a.read_with(cx_b, |change_set, cx| {
let buffer = buffer_remote_a.read(cx);
assert_eq!(
- change_set.base_text_string(cx).as_deref(),
+ change_set.base_text_string().as_deref(),
Some(diff_base.as_str())
);
git::diff::assert_hunks(
@@ -2643,7 +2643,7 @@ async fn test_git_diff_base_change(
change_set_local_a.read_with(cx_a, |change_set, cx| {
let buffer = buffer_local_a.read(cx);
assert_eq!(
- change_set.base_text_string(cx).as_deref(),
+ change_set.base_text_string().as_deref(),
Some(new_diff_base.as_str())
);
git::diff::assert_hunks(
@@ -2657,7 +2657,7 @@ async fn test_git_diff_base_change(
change_set_remote_a.read_with(cx_b, |change_set, cx| {
let buffer = buffer_remote_a.read(cx);
assert_eq!(
- change_set.base_text_string(cx).as_deref(),
+ change_set.base_text_string().as_deref(),
Some(new_diff_base.as_str())
);
git::diff::assert_hunks(
@@ -2703,7 +2703,7 @@ async fn test_git_diff_base_change(
change_set_local_b.read_with(cx_a, |change_set, cx| {
let buffer = buffer_local_b.read(cx);
assert_eq!(
- change_set.base_text_string(cx).as_deref(),
+ change_set.base_text_string().as_deref(),
Some(diff_base.as_str())
);
git::diff::assert_hunks(
@@ -2730,7 +2730,7 @@ async fn test_git_diff_base_change(
change_set_remote_b.read_with(cx_b, |change_set, cx| {
let buffer = buffer_remote_b.read(cx);
assert_eq!(
- change_set.base_text_string(cx).as_deref(),
+ change_set.base_text_string().as_deref(),
Some(diff_base.as_str())
);
git::diff::assert_hunks(
@@ -2752,7 +2752,7 @@ async fn test_git_diff_base_change(
change_set_local_b.read_with(cx_a, |change_set, cx| {
let buffer = buffer_local_b.read(cx);
assert_eq!(
- change_set.base_text_string(cx).as_deref(),
+ change_set.base_text_string().as_deref(),
Some(new_diff_base.as_str())
);
git::diff::assert_hunks(
@@ -2766,7 +2766,7 @@ async fn test_git_diff_base_change(
change_set_remote_b.read_with(cx_b, |change_set, cx| {
let buffer = buffer_remote_b.read(cx);
assert_eq!(
- change_set.base_text_string(cx).as_deref(),
+ change_set.base_text_string().as_deref(),
Some(new_diff_base.as_str())
);
git::diff::assert_hunks(
@@ -1342,7 +1342,7 @@ impl RandomizedTest for ProjectCollaborationTest {
.get_unstaged_changes(host_buffer.read(cx).remote_id())
.unwrap()
.read(cx)
- .base_text_string(cx)
+ .base_text_string()
});
let guest_diff_base = guest_project.read_with(client_cx, |project, cx| {
project
@@ -1351,7 +1351,7 @@ impl RandomizedTest for ProjectCollaborationTest {
.get_unstaged_changes(guest_buffer.read(cx).remote_id())
.unwrap()
.read(cx)
- .base_text_string(cx)
+ .base_text_string()
});
assert_eq!(
guest_diff_base, host_diff_base,
@@ -1,11 +1,11 @@
use std::time::Duration;
-use editor::{AnchorRangeExt, Editor};
+use editor::Editor;
use gpui::{
EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, Task, View,
ViewContext, WeakView,
};
-use language::{Diagnostic, DiagnosticEntry};
+use language::Diagnostic;
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
@@ -148,11 +148,7 @@ impl DiagnosticIndicator {
(buffer, cursor_position)
});
let new_diagnostic = buffer
- .diagnostics_in_range(cursor_position..cursor_position, false)
- .map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
- diagnostic,
- range: range.to_offset(&buffer),
- })
+ .diagnostics_in_range::<_, usize>(cursor_position..cursor_position)
.filter(|entry| !entry.range.is_empty())
.min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
.map(|entry| entry.diagnostic);
@@ -372,7 +372,7 @@ gpui::actions!(
ToggleAutoSignatureHelp,
ToggleGitBlame,
ToggleGitBlameInline,
- ToggleHunkDiff,
+ ToggleSelectedDiffHunks,
ToggleIndentGuides,
ToggleInlayHints,
ToggleInlineCompletions,
@@ -30,8 +30,8 @@ use crate::{
hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, RowExt,
};
pub use block_map::{
- Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap,
- BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
+ Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement,
+ BlockPoint, BlockProperties, BlockRows, BlockStyle, CustomBlockId, RenderBlock,
StickyHeaderExcerpt,
};
use block_map::{BlockRow, BlockSnapshot};
@@ -54,7 +54,7 @@ use language::{
use lsp::DiagnosticSeverity;
use multi_buffer::{
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
- ToOffset, ToPoint,
+ RowInfo, ToOffset, ToPoint,
};
use serde::Deserialize;
use std::{
@@ -68,7 +68,7 @@ use std::{
};
use sum_tree::{Bias, TreeMap};
use tab_map::{TabMap, TabSnapshot};
-use text::LineIndent;
+use text::{BufferId, LineIndent};
use ui::{px, SharedString, WindowContext};
use unicode_segmentation::UnicodeSegmentation;
use wrap_map::{WrapMap, WrapSnapshot};
@@ -367,10 +367,14 @@ impl DisplayMap {
block_map.unfold_buffer(buffer_id, self.buffer.read(cx), cx)
}
- pub(crate) fn buffer_folded(&self, buffer_id: language::BufferId) -> bool {
+ pub(crate) fn is_buffer_folded(&self, buffer_id: language::BufferId) -> bool {
self.block_map.folded_buffers.contains(&buffer_id)
}
+ pub(crate) fn folded_buffers(&self) -> &HashSet<BufferId> {
+ &self.block_map.folded_buffers
+ }
+
pub fn insert_creases(
&mut self,
creases: impl IntoIterator<Item = Crease<Anchor>>,
@@ -716,13 +720,8 @@ impl DisplaySnapshot {
self.buffer_snapshot.len() == 0
}
- pub fn buffer_rows(
- &self,
- start_row: DisplayRow,
- ) -> impl Iterator<Item = Option<MultiBufferRow>> + '_ {
- self.block_snapshot
- .buffer_rows(BlockRow(start_row.0))
- .map(|row| row.map(MultiBufferRow))
+ pub fn row_infos(&self, start_row: DisplayRow) -> impl Iterator<Item = RowInfo> + '_ {
+ self.block_snapshot.row_infos(BlockRow(start_row.0))
}
pub fn widest_line_number(&self) -> u32 {
@@ -7,8 +7,8 @@ use collections::{Bound, HashMap, HashSet};
use gpui::{AnyElement, AppContext, EntityId, Pixels, WindowContext};
use language::{Chunk, Patch, Point};
use multi_buffer::{
- Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferRow, MultiBufferSnapshot, ToOffset,
- ToPoint as _,
+ Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferRow, MultiBufferSnapshot, RowInfo,
+ ToOffset, ToPoint as _,
};
use parking_lot::Mutex;
use std::{
@@ -399,9 +399,9 @@ pub struct BlockChunks<'a> {
}
#[derive(Clone)]
-pub struct BlockBufferRows<'a> {
+pub struct BlockRows<'a> {
transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
- input_buffer_rows: wrap_map::WrapBufferRows<'a>,
+ input_rows: wrap_map::WrapRows<'a>,
output_row: BlockRow,
started: bool,
}
@@ -777,14 +777,12 @@ impl BlockMap {
if let Some(new_buffer_id) = new_buffer_id {
let first_excerpt = excerpt_boundary.next.clone().unwrap();
if folded_buffers.contains(&new_buffer_id) {
- let mut buffer_end = Point::new(excerpt_boundary.row.0, 0)
- + excerpt_boundary.next.as_ref().unwrap().text_summary.lines;
+ let mut last_excerpt_end_row = first_excerpt.end_row;
while let Some(next_boundary) = boundaries.peek() {
if let Some(next_excerpt_boundary) = &next_boundary.next {
if next_excerpt_boundary.buffer_id == new_buffer_id {
- buffer_end = Point::new(next_boundary.row.0, 0)
- + next_excerpt_boundary.text_summary.lines;
+ last_excerpt_end_row = next_excerpt_boundary.end_row;
} else {
break;
}
@@ -793,7 +791,15 @@ impl BlockMap {
boundaries.next();
}
- let wrap_end_row = wrap_snapshot.make_wrap_point(buffer_end, Bias::Right).row();
+ let wrap_end_row = wrap_snapshot
+ .make_wrap_point(
+ Point::new(
+ last_excerpt_end_row.0,
+ buffer.line_len(last_excerpt_end_row),
+ ),
+ Bias::Right,
+ )
+ .row();
return Some((
BlockPlacement::Replace(WrapRow(wrap_row)..=WrapRow(wrap_end_row)),
@@ -1360,7 +1366,7 @@ impl BlockSnapshot {
}
}
- pub(super) fn buffer_rows(&self, start_row: BlockRow) -> BlockBufferRows {
+ pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
cursor.seek(&start_row, Bias::Right, &());
let (output_start, input_start) = cursor.start();
@@ -1373,9 +1379,9 @@ impl BlockSnapshot {
0
};
let input_start_row = input_start.0 + overshoot;
- BlockBufferRows {
+ BlockRows {
transforms: cursor,
- input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
+ input_rows: self.wrap_snapshot.row_infos(input_start_row),
output_row: start_row,
started: false,
}
@@ -1480,7 +1486,7 @@ impl BlockSnapshot {
}
BlockId::ExcerptBoundary(next_excerpt_id) => {
if let Some(next_excerpt_id) = next_excerpt_id {
- let excerpt_range = buffer.range_for_excerpt::<Point>(next_excerpt_id)?;
+ let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
self.wrap_snapshot
.make_wrap_point(excerpt_range.start, Bias::Left)
} else {
@@ -1488,10 +1494,9 @@ impl BlockSnapshot {
.make_wrap_point(buffer.max_point(), Bias::Left)
}
}
- BlockId::FoldedBuffer(excerpt_id) => self.wrap_snapshot.make_wrap_point(
- buffer.range_for_excerpt::<Point>(excerpt_id)?.start,
- Bias::Left,
- ),
+ BlockId::FoldedBuffer(excerpt_id) => self
+ .wrap_snapshot
+ .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
};
let wrap_row = WrapRow(wrap_point.row());
@@ -1832,8 +1837,8 @@ impl<'a> Iterator for BlockChunks<'a> {
}
}
-impl<'a> Iterator for BlockBufferRows<'a> {
- type Item = Option<u32>;
+impl<'a> Iterator for BlockRows<'a> {
+ type Item = RowInfo;
fn next(&mut self) -> Option<Self::Item> {
if self.started {
@@ -1862,7 +1867,7 @@ impl<'a> Iterator for BlockBufferRows<'a> {
.as_ref()
.map_or(true, |block| block.is_replacement())
{
- self.input_buffer_rows.seek(self.transforms.start().1 .0);
+ self.input_rows.seek(self.transforms.start().1 .0);
}
}
@@ -1870,15 +1875,15 @@ impl<'a> Iterator for BlockBufferRows<'a> {
if let Some(block) = transform.block.as_ref() {
if block.is_replacement() && self.transforms.start().0 == self.output_row {
if matches!(block, Block::FoldedBuffer { .. }) {
- Some(None)
+ Some(RowInfo::default())
} else {
- Some(self.input_buffer_rows.next().unwrap())
+ Some(self.input_rows.next().unwrap())
}
} else {
- Some(None)
+ Some(RowInfo::default())
}
} else {
- Some(self.input_buffer_rows.next().unwrap())
+ Some(self.input_rows.next().unwrap())
}
}
}
@@ -2153,7 +2158,10 @@ mod tests {
);
assert_eq!(
- snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
+ snapshot
+ .row_infos(BlockRow(0))
+ .map(|row_info| row_info.buffer_row)
+ .collect::<Vec<_>>(),
&[
Some(0),
None,
@@ -2603,7 +2611,10 @@ mod tests {
"\n\n\n111\n\n\n\n\n222\n\n\n333\n\n\n444\n\n\n\n\n555\n\n\n666\n"
);
assert_eq!(
- blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
+ blocks_snapshot
+ .row_infos(BlockRow(0))
+ .map(|i| i.buffer_row)
+ .collect::<Vec<_>>(),
vec![
None,
None,
@@ -2679,7 +2690,10 @@ mod tests {
"\n\n\n111\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n"
);
assert_eq!(
- blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
+ blocks_snapshot
+ .row_infos(BlockRow(0))
+ .map(|i| i.buffer_row)
+ .collect::<Vec<_>>(),
vec![
None,
None,
@@ -2754,7 +2768,10 @@ mod tests {
"\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n"
);
assert_eq!(
- blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
+ blocks_snapshot
+ .row_infos(BlockRow(0))
+ .map(|i| i.buffer_row)
+ .collect::<Vec<_>>(),
vec![
None,
None,
@@ -2819,7 +2836,10 @@ mod tests {
);
assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n\n555\n\n\n666\n\n");
assert_eq!(
- blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
+ blocks_snapshot
+ .row_infos(BlockRow(0))
+ .map(|i| i.buffer_row)
+ .collect::<Vec<_>>(),
vec![
None,
None,
@@ -2873,7 +2893,10 @@ mod tests {
"Should have extra newline for 111 buffer, due to a new block added when it was folded"
);
assert_eq!(
- blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
+ blocks_snapshot
+ .row_infos(BlockRow(0))
+ .map(|i| i.buffer_row)
+ .collect::<Vec<_>>(),
vec![
None,
None,
@@ -2927,7 +2950,10 @@ mod tests {
"Should have a single, first buffer left after folding"
);
assert_eq!(
- blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
+ blocks_snapshot
+ .row_infos(BlockRow(0))
+ .map(|i| i.buffer_row)
+ .collect::<Vec<_>>(),
vec![
None,
None,
@@ -2997,7 +3023,10 @@ mod tests {
);
assert_eq!(blocks_snapshot.text(), "\n");
assert_eq!(
- blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
+ blocks_snapshot
+ .row_infos(BlockRow(0))
+ .map(|i| i.buffer_row)
+ .collect::<Vec<_>>(),
vec![None, None],
"When fully folded, should be no buffer rows"
);
@@ -3295,7 +3324,8 @@ mod tests {
let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
let input_buffer_rows = buffer_snapshot
- .buffer_rows(MultiBufferRow(0))
+ .row_infos(MultiBufferRow(0))
+ .map(|row| row.buffer_row)
.collect::<Vec<_>>();
let mut expected_buffer_rows = Vec::new();
let mut expected_text = String::new();
@@ -3450,7 +3480,8 @@ mod tests {
);
assert_eq!(
blocks_snapshot
- .buffer_rows(BlockRow(start_row as u32))
+ .row_infos(BlockRow(start_row as u32))
+ .map(|row_info| row_info.buffer_row)
.collect::<Vec<_>>(),
&expected_buffer_rows[start_row..],
"incorrect buffer_rows starting at row {:?}",
@@ -4,7 +4,9 @@ use super::{
};
use gpui::{AnyElement, ElementId, WindowContext};
use language::{Chunk, ChunkRenderer, Edit, Point, TextSummary};
-use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToOffset};
+use multi_buffer::{
+ Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset,
+};
use std::{
any::TypeId,
cmp::{self, Ordering},
@@ -336,9 +338,7 @@ impl FoldMap {
let mut folds = self.snapshot.folds.iter().peekable();
while let Some(fold) = folds.next() {
if let Some(next_fold) = folds.peek() {
- let comparison = fold
- .range
- .cmp(&next_fold.range, &self.snapshot.inlay_snapshot.buffer);
+ let comparison = fold.range.cmp(&next_fold.range, self.snapshot.buffer());
assert!(comparison.is_le());
}
}
@@ -578,6 +578,10 @@ pub struct FoldSnapshot {
}
impl FoldSnapshot {
+ pub fn buffer(&self) -> &MultiBufferSnapshot {
+ &self.inlay_snapshot.buffer
+ }
+
#[cfg(test)]
pub fn text(&self) -> String {
self.chunks(FoldOffset(0)..self.len(), false, Highlights::default())
@@ -673,7 +677,7 @@ impl FoldSnapshot {
(line_end - line_start) as u32
}
- pub fn buffer_rows(&self, start_row: u32) -> FoldBufferRows {
+ pub fn row_infos(&self, start_row: u32) -> FoldRows {
if start_row > self.transforms.summary().output.lines.row {
panic!("invalid display row {}", start_row);
}
@@ -684,11 +688,11 @@ impl FoldSnapshot {
let overshoot = fold_point.0 - cursor.start().0 .0;
let inlay_point = InlayPoint(cursor.start().1 .0 + overshoot);
- let input_buffer_rows = self.inlay_snapshot.buffer_rows(inlay_point.row());
+ let input_rows = self.inlay_snapshot.row_infos(inlay_point.row());
- FoldBufferRows {
+ FoldRows {
fold_point,
- input_buffer_rows,
+ input_rows,
cursor,
}
}
@@ -843,8 +847,8 @@ fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: TextSummary) {
transforms.update_last(
|last| {
if !last.is_fold() {
- last.summary.input += summary.clone();
- last.summary.output += summary.clone();
+ last.summary.input += summary;
+ last.summary.output += summary;
did_merge = true;
}
},
@@ -854,7 +858,7 @@ fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: TextSummary) {
transforms.push(
Transform {
summary: TransformSummary {
- input: summary.clone(),
+ input: summary,
output: summary,
},
placeholder: None,
@@ -1134,25 +1138,25 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
}
#[derive(Clone)]
-pub struct FoldBufferRows<'a> {
+pub struct FoldRows<'a> {
cursor: Cursor<'a, Transform, (FoldPoint, InlayPoint)>,
- input_buffer_rows: InlayBufferRows<'a>,
+ input_rows: InlayBufferRows<'a>,
fold_point: FoldPoint,
}
-impl<'a> FoldBufferRows<'a> {
+impl<'a> FoldRows<'a> {
pub(crate) fn seek(&mut self, row: u32) {
let fold_point = FoldPoint::new(row, 0);
self.cursor.seek(&fold_point, Bias::Left, &());
let overshoot = fold_point.0 - self.cursor.start().0 .0;
let inlay_point = InlayPoint(self.cursor.start().1 .0 + overshoot);
- self.input_buffer_rows.seek(inlay_point.row());
+ self.input_rows.seek(inlay_point.row());
self.fold_point = fold_point;
}
}
-impl<'a> Iterator for FoldBufferRows<'a> {
- type Item = Option<u32>;
+impl<'a> Iterator for FoldRows<'a> {
+ type Item = RowInfo;
fn next(&mut self) -> Option<Self::Item> {
let mut traversed_fold = false;
@@ -1166,11 +1170,11 @@ impl<'a> Iterator for FoldBufferRows<'a> {
if self.cursor.item().is_some() {
if traversed_fold {
- self.input_buffer_rows.seek(self.cursor.start().1.row());
- self.input_buffer_rows.next();
+ self.input_rows.seek(self.cursor.start().1 .0.row);
+ self.input_rows.next();
}
*self.fold_point.row_mut() += 1;
- self.input_buffer_rows.next()
+ self.input_rows.next()
} else {
None
}
@@ -1683,12 +1687,12 @@ mod tests {
.row();
expected_buffer_rows.extend(
inlay_snapshot
- .buffer_rows(prev_row)
+ .row_infos(prev_row)
.take((1 + fold_start - prev_row) as usize),
);
prev_row = 1 + fold_end;
}
- expected_buffer_rows.extend(inlay_snapshot.buffer_rows(prev_row));
+ expected_buffer_rows.extend(inlay_snapshot.row_infos(prev_row));
assert_eq!(
expected_buffer_rows.len(),
@@ -1777,7 +1781,7 @@ mod tests {
let mut fold_row = 0;
while fold_row < expected_buffer_rows.len() as u32 {
assert_eq!(
- snapshot.buffer_rows(fold_row).collect::<Vec<_>>(),
+ snapshot.row_infos(fold_row).collect::<Vec<_>>(),
expected_buffer_rows[(fold_row as usize)..],
"wrong buffer rows starting at fold row {}",
fold_row,
@@ -1892,10 +1896,19 @@ mod tests {
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
assert_eq!(snapshot.text(), "aaβ―cccc\ndβ―eeeee\nffffff\n");
assert_eq!(
- snapshot.buffer_rows(0).collect::<Vec<_>>(),
+ snapshot
+ .row_infos(0)
+ .map(|info| info.buffer_row)
+ .collect::<Vec<_>>(),
[Some(0), Some(3), Some(5), Some(6)]
);
- assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [Some(6)]);
+ assert_eq!(
+ snapshot
+ .row_infos(3)
+ .map(|info| info.buffer_row)
+ .collect::<Vec<_>>(),
+ [Some(6)]
+ );
}
fn init_test(cx: &mut gpui::AppContext) {
@@ -1,7 +1,9 @@
use crate::{HighlightStyles, InlayId};
use collections::BTreeSet;
use language::{Chunk, Edit, Point, TextSummary};
-use multi_buffer::{Anchor, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, ToOffset};
+use multi_buffer::{
+ Anchor, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, RowInfo, ToOffset,
+};
use std::{
cmp,
ops::{Add, AddAssign, Range, Sub, SubAssign},
@@ -67,11 +69,11 @@ impl Inlay {
impl sum_tree::Item for Transform {
type Summary = TransformSummary;
- fn summary(&self, _cx: &()) -> Self::Summary {
+ fn summary(&self, _: &()) -> Self::Summary {
match self {
Transform::Isomorphic(summary) => TransformSummary {
- input: summary.clone(),
- output: summary.clone(),
+ input: *summary,
+ output: *summary,
},
Transform::Inlay(inlay) => TransformSummary {
input: TextSummary::default(),
@@ -362,14 +364,14 @@ impl<'a> InlayBufferRows<'a> {
}
impl<'a> Iterator for InlayBufferRows<'a> {
- type Item = Option<u32>;
+ type Item = RowInfo;
fn next(&mut self) -> Option<Self::Item> {
let buffer_row = if self.inlay_row == 0 {
self.buffer_rows.next().unwrap()
} else {
match self.transforms.item()? {
- Transform::Inlay(_) => None,
+ Transform::Inlay(_) => Default::default(),
Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(),
}
};
@@ -448,7 +450,7 @@ impl InlayMap {
new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &());
if let Some(Transform::Isomorphic(transform)) = cursor.item() {
if cursor.end(&()).0 == buffer_edit.old.start {
- push_isomorphic(&mut new_transforms, transform.clone());
+ push_isomorphic(&mut new_transforms, *transform);
cursor.next(&());
}
}
@@ -892,7 +894,7 @@ impl InlaySnapshot {
}
pub fn text_summary(&self) -> TextSummary {
- self.transforms.summary().output.clone()
+ self.transforms.summary().output
}
pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
@@ -945,7 +947,7 @@ impl InlaySnapshot {
summary
}
- pub fn buffer_rows(&self, row: u32) -> InlayBufferRows<'_> {
+ pub fn row_infos(&self, row: u32) -> InlayBufferRows<'_> {
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
let inlay_point = InlayPoint::new(row, 0);
cursor.seek(&inlay_point, Bias::Left, &());
@@ -967,7 +969,7 @@ impl InlaySnapshot {
InlayBufferRows {
transforms: cursor,
inlay_row: inlay_point.row(),
- buffer_rows: self.buffer.buffer_rows(buffer_row),
+ buffer_rows: self.buffer.row_infos(buffer_row),
max_buffer_row,
}
}
@@ -1477,7 +1479,10 @@ mod tests {
);
assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
assert_eq!(
- inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
+ inlay_snapshot
+ .row_infos(0)
+ .map(|info| info.buffer_row)
+ .collect::<Vec<_>>(),
vec![Some(0), None, Some(1), None, None, Some(2)]
);
}
@@ -1548,7 +1553,7 @@ mod tests {
}
assert_eq!(inlay_snapshot.text(), expected_text.to_string());
- let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
+ let expected_buffer_rows = inlay_snapshot.row_infos(0).collect::<Vec<_>>();
assert_eq!(
expected_buffer_rows.len() as u32,
expected_text.max_point().row + 1
@@ -1556,7 +1561,7 @@ mod tests {
for row_start in 0..expected_buffer_rows.len() {
assert_eq!(
inlay_snapshot
- .buffer_rows(row_start as u32)
+ .row_infos(row_start as u32)
.collect::<Vec<_>>(),
&expected_buffer_rows[row_start..],
"incorrect buffer rows starting at {}",
@@ -272,8 +272,8 @@ impl TabSnapshot {
}
}
- pub fn buffer_rows(&self, row: u32) -> fold_map::FoldBufferRows<'_> {
- self.fold_snapshot.buffer_rows(row)
+ pub fn rows(&self, row: u32) -> fold_map::FoldRows<'_> {
+ self.fold_snapshot.row_infos(row)
}
#[cfg(test)]
@@ -1,11 +1,11 @@
use super::{
- fold_map::FoldBufferRows,
+ fold_map::FoldRows,
tab_map::{self, TabEdit, TabPoint, TabSnapshot},
Highlights,
};
use gpui::{AppContext, Context, Font, LineWrapper, Model, ModelContext, Pixels, Task};
use language::{Chunk, Point};
-use multi_buffer::MultiBufferSnapshot;
+use multi_buffer::{MultiBufferSnapshot, RowInfo};
use smol::future::yield_now;
use std::sync::LazyLock;
use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
@@ -60,16 +60,16 @@ pub struct WrapChunks<'a> {
}
#[derive(Clone)]
-pub struct WrapBufferRows<'a> {
- input_buffer_rows: FoldBufferRows<'a>,
- input_buffer_row: Option<u32>,
+pub struct WrapRows<'a> {
+ input_buffer_rows: FoldRows<'a>,
+ input_buffer_row: RowInfo,
output_row: u32,
soft_wrapped: bool,
max_output_row: u32,
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
}
-impl<'a> WrapBufferRows<'a> {
+impl<'a> WrapRows<'a> {
pub(crate) fn seek(&mut self, start_row: u32) {
self.transforms
.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
@@ -717,7 +717,7 @@ impl WrapSnapshot {
self.transforms.summary().output.longest_row
}
- pub fn buffer_rows(&self, start_row: u32) -> WrapBufferRows {
+ pub fn row_infos(&self, start_row: u32) -> WrapRows {
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
let mut input_row = transforms.start().1.row();
@@ -725,9 +725,9 @@ impl WrapSnapshot {
input_row += start_row - transforms.start().0.row();
}
let soft_wrapped = transforms.item().map_or(false, |t| !t.is_isomorphic());
- let mut input_buffer_rows = self.tab_snapshot.buffer_rows(input_row);
+ let mut input_buffer_rows = self.tab_snapshot.rows(input_row);
let input_buffer_row = input_buffer_rows.next().unwrap();
- WrapBufferRows {
+ WrapRows {
transforms,
input_buffer_row,
input_buffer_rows,
@@ -847,7 +847,7 @@ impl WrapSnapshot {
}
let text = language::Rope::from(self.text().as_str());
- let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0);
+ let mut input_buffer_rows = self.tab_snapshot.rows(0);
let mut expected_buffer_rows = Vec::new();
let mut prev_tab_row = 0;
for display_row in 0..=self.max_point().row() {
@@ -855,7 +855,7 @@ impl WrapSnapshot {
if tab_point.row() == prev_tab_row && display_row != 0 {
expected_buffer_rows.push(None);
} else {
- expected_buffer_rows.push(input_buffer_rows.next().unwrap());
+ expected_buffer_rows.push(input_buffer_rows.next().unwrap().buffer_row);
}
prev_tab_row = tab_point.row();
@@ -864,7 +864,8 @@ impl WrapSnapshot {
for start_display_row in 0..expected_buffer_rows.len() {
assert_eq!(
- self.buffer_rows(start_display_row as u32)
+ self.row_infos(start_display_row as u32)
+ .map(|row_info| row_info.buffer_row)
.collect::<Vec<_>>(),
&expected_buffer_rows[start_display_row..],
"invalid buffer_rows({}..)",
@@ -958,8 +959,8 @@ impl<'a> Iterator for WrapChunks<'a> {
}
}
-impl<'a> Iterator for WrapBufferRows<'a> {
- type Item = Option<u32>;
+impl<'a> Iterator for WrapRows<'a> {
+ type Item = RowInfo;
fn next(&mut self) -> Option<Self::Item> {
if self.output_row > self.max_output_row {
@@ -968,6 +969,7 @@ impl<'a> Iterator for WrapBufferRows<'a> {
let buffer_row = self.input_buffer_row;
let soft_wrapped = self.soft_wrapped;
+ let diff_status = self.input_buffer_row.diff_status;
self.output_row += 1;
self.transforms
@@ -979,7 +981,15 @@ impl<'a> Iterator for WrapBufferRows<'a> {
self.soft_wrapped = true;
}
- Some(if soft_wrapped { None } else { buffer_row })
+ Some(if soft_wrapped {
+ RowInfo {
+ buffer_row: None,
+ multibuffer_row: None,
+ diff_status,
+ }
+ } else {
+ buffer_row
+ })
}
}
@@ -25,7 +25,6 @@ mod git;
mod highlight_matching_bracket;
mod hover_links;
mod hover_popover;
-mod hunk_diff;
mod indent_guides;
mod inlay_hint_cache;
pub mod items;
@@ -56,7 +55,7 @@ use anyhow::{anyhow, Context as _, Result};
use blink_manager::BlinkManager;
use client::{Collaborator, ParticipantIndex};
use clock::ReplicaId;
-use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
+use collections::{BTreeMap, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing};
use display_map::*;
pub use display_map::{DisplayPoint, FoldPlaceholder};
@@ -89,8 +88,6 @@ use gpui::{
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
-pub(crate) use hunk_diff::HoveredHunk;
-use hunk_diff::{diff_hunk_to_display, DiffMap, DiffMapSnapshot};
use indent_guides::ActiveIndentGuidesState;
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
pub use inline_completion::Direction;
@@ -101,7 +98,8 @@ use language::{
language_settings::{self, all_language_settings, language_settings, InlayHintSettings},
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
CursorShape, Diagnostic, Documentation, EditPreview, HighlightedEdits, IndentKind, IndentSize,
- Language, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
+ Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId,
+ TreeSitterOptions,
};
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
use linked_editing_ranges::refresh_linked_ranges;
@@ -123,14 +121,13 @@ use lsp::{
use language::BufferSnapshot;
use movement::TextLayoutDetails;
pub use multi_buffer::{
- Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
- ToPoint,
+ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo,
+ ToOffset, ToPoint,
};
use multi_buffer::{
ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, ToOffsetUtf16,
};
use project::{
- buffer_store::BufferChangeSet,
lsp_store::{FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
project_settings::{GitGutterSetting, ProjectSettings},
CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink,
@@ -165,7 +162,7 @@ use text::{BufferId, OffsetUtf16, Rope};
use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings};
use ui::{
h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconName, IconSize,
- PopoverMenuHandle, Tooltip,
+ Tooltip,
};
use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
use workspace::item::{ItemHandle, PreviewTabsSettings};
@@ -271,7 +268,6 @@ impl InlayId {
}
}
-enum DiffRowHighlight {}
enum DocumentHighlightRead {}
enum DocumentHighlightWrite {}
enum InputComposition {}
@@ -650,7 +646,6 @@ pub struct Editor {
nav_history: Option<ItemNavHistory>,
context_menu: RefCell<Option<CodeContextMenu>>,
mouse_context_menu: Option<MouseContextMenu>,
- hunk_controls_menu_handle: PopoverMenuHandle<ui::ContextMenu>,
completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
signature_help_state: SignatureHelpState,
auto_signature_help: Option<bool>,
@@ -685,7 +680,6 @@ pub struct Editor {
show_inline_completions_override: Option<bool>,
menu_inline_completions_policy: MenuInlineCompletionsPolicy,
inlay_hint_cache: InlayHintCache,
- diff_map: DiffMap,
next_inlay_id: usize,
_subscriptions: Vec<Subscription>,
pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
@@ -755,7 +749,6 @@ pub struct EditorSnapshot {
git_blame_gutter_max_author_length: Option<usize>,
pub display_snapshot: DisplaySnapshot,
pub placeholder_text: Option<Arc<str>>,
- diff_map: DiffMapSnapshot,
is_focused: bool,
scroll_anchor: ScrollAnchor,
ongoing_scroll: OngoingScroll,
@@ -1245,7 +1238,12 @@ impl Editor {
let mut code_action_providers = Vec::new();
if let Some(project) = project.clone() {
- get_unstaged_changes_for_buffers(&project, buffer.read(cx).all_buffers(), cx);
+ get_unstaged_changes_for_buffers(
+ &project,
+ buffer.read(cx).all_buffers(),
+ buffer.clone(),
+ cx,
+ );
code_action_providers.push(Rc::new(project) as Rc<_>);
}
@@ -1295,7 +1293,6 @@ impl Editor {
nav_history: None,
context_menu: RefCell::new(None),
mouse_context_menu: None,
- hunk_controls_menu_handle: PopoverMenuHandle::default(),
completion_tasks: Default::default(),
signature_help_state: SignatureHelpState::default(),
auto_signature_help: None,
@@ -1329,7 +1326,7 @@ impl Editor {
inline_completion_provider: None,
active_inline_completion: None,
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
- diff_map: DiffMap::default(),
+
gutter_hovered: false,
pixel_position_of_newest_cursor: None,
last_bounds: None,
@@ -1605,7 +1602,6 @@ impl Editor {
scroll_anchor: self.scroll_manager.anchor(),
ongoing_scroll: self.scroll_manager.ongoing_scroll(),
placeholder_text: self.placeholder_text.clone(),
- diff_map: self.diff_map.snapshot(),
is_focused: self.focus_handle.is_focused(cx),
current_line_highlight: self
.current_line_highlight
@@ -3602,9 +3598,9 @@ impl Editor {
multi_buffer_snapshot
.range_to_buffer_ranges(multi_buffer_visible_range)
.into_iter()
- .filter(|(_, excerpt_visible_range)| !excerpt_visible_range.is_empty())
- .filter_map(|(excerpt, excerpt_visible_range)| {
- let buffer_file = project::File::from_dyn(excerpt.buffer().file())?;
+ .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
+ .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
+ let buffer_file = project::File::from_dyn(buffer.file())?;
let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
let worktree_entry = buffer_worktree
.read(cx)
@@ -3613,17 +3609,17 @@ impl Editor {
return None;
}
- let language = excerpt.buffer().language()?;
+ let language = buffer.language()?;
if let Some(restrict_to_languages) = restrict_to_languages {
if !restrict_to_languages.contains(language) {
return None;
}
}
Some((
- excerpt.id(),
+ excerpt_id,
(
- multi_buffer.buffer(excerpt.buffer_id()).unwrap(),
- excerpt.buffer().version().clone(),
+ multi_buffer.buffer(buffer.remote_id()).unwrap(),
+ buffer.version().clone(),
excerpt_visible_range,
),
))
@@ -4536,10 +4532,12 @@ impl Editor {
buffer_id,
excerpt_id,
text_anchor: start,
+ diff_base_anchor: None,
}..Anchor {
buffer_id,
excerpt_id,
text_anchor: end,
+ diff_base_anchor: None,
};
if highlight.kind == lsp::DocumentHighlightKind::WRITE {
write_ranges.push(range);
@@ -5262,7 +5260,7 @@ impl Editor {
}))
}
- #[cfg(any(feature = "test-support", test))]
+ #[cfg(any(test, feature = "test-support"))]
pub fn context_menu_visible(&self) -> bool {
self.context_menu
.borrow()
@@ -6126,10 +6124,9 @@ impl Editor {
pub fn revert_file(&mut self, _: &RevertFile, cx: &mut ViewContext<Self>) {
let mut revert_changes = HashMap::default();
let snapshot = self.snapshot(cx);
- for hunk in hunks_for_ranges(
- Some(Point::zero()..snapshot.buffer_snapshot.max_point()).into_iter(),
- &snapshot,
- ) {
+ for hunk in snapshot
+ .hunks_for_ranges(Some(Point::zero()..snapshot.buffer_snapshot.max_point()).into_iter())
+ {
self.prepare_revert_change(&mut revert_changes, &hunk, cx);
}
if !revert_changes.is_empty() {
@@ -6147,7 +6144,20 @@ impl Editor {
}
pub fn revert_selected_hunks(&mut self, _: &RevertSelectedHunks, cx: &mut ViewContext<Self>) {
- let revert_changes = self.gather_revert_changes(&self.selections.all(cx), cx);
+ let selections = self.selections.all(cx).into_iter().map(|s| s.range());
+ self.revert_hunks_in_ranges(selections, cx);
+ }
+
+ fn revert_hunks_in_ranges(
+ &mut self,
+ ranges: impl Iterator<Item = Range<Point>>,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ let mut revert_changes = HashMap::default();
+ let snapshot = self.snapshot(cx);
+ for hunk in &snapshot.hunks_for_ranges(ranges) {
+ self.prepare_revert_change(&mut revert_changes, &hunk, cx);
+ }
if !revert_changes.is_empty() {
self.transact(cx, |editor, cx| {
editor.revert(revert_changes, cx);
@@ -6155,18 +6165,6 @@ impl Editor {
}
}
- fn revert_hunk(&mut self, hunk: HoveredHunk, cx: &mut ViewContext<Editor>) {
- let snapshot = self.buffer.read(cx).read(cx);
- if let Some(hunk) = crate::hunk_diff::to_diff_hunk(&hunk, &snapshot) {
- drop(snapshot);
- let mut revert_changes = HashMap::default();
- self.prepare_revert_change(&mut revert_changes, &hunk, cx);
- if !revert_changes.is_empty() {
- self.revert(revert_changes, cx)
- }
- }
- }
-
pub fn open_active_item_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
let project_path = buffer.read(cx).project_path(cx)?;
@@ -6184,33 +6182,20 @@ impl Editor {
}
}
- fn gather_revert_changes(
- &self,
- selections: &[Selection<Point>],
- cx: &mut ViewContext<Editor>,
- ) -> HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>> {
- let mut revert_changes = HashMap::default();
- let snapshot = self.snapshot(cx);
- for hunk in hunks_for_selections(&snapshot, selections) {
- self.prepare_revert_change(&mut revert_changes, &hunk, cx);
- }
- revert_changes
- }
-
pub fn prepare_revert_change(
&self,
revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
hunk: &MultiBufferDiffHunk,
- cx: &AppContext,
+ cx: &mut WindowContext,
) -> Option<()> {
- let buffer = self.buffer.read(cx).buffer(hunk.buffer_id)?;
+ let buffer = self.buffer.read(cx);
+ let change_set = buffer.change_set_for(hunk.buffer_id)?;
+ let buffer = buffer.buffer(hunk.buffer_id)?;
let buffer = buffer.read(cx);
- let change_set = &self.diff_map.diff_bases.get(&hunk.buffer_id)?.change_set;
let original_text = change_set
.read(cx)
.base_text
.as_ref()?
- .read(cx)
.as_rope()
.slice(hunk.diff_base_byte_range.clone());
let buffer_snapshot = buffer.snapshot();
@@ -6551,12 +6536,8 @@ impl Editor {
// Don't move lines across excerpts
if buffer
- .excerpt_boundaries_in_range((
- Bound::Excluded(insertion_point),
- Bound::Included(range_to_move.end),
- ))
- .next()
- .is_none()
+ .excerpt_containing(insertion_point..range_to_move.end)
+ .is_some()
{
let text = buffer
.text_for_range(range_to_move.clone())
@@ -6649,12 +6630,8 @@ impl Editor {
// Don't move lines across excerpt boundaries
if buffer
- .excerpt_boundaries_in_range((
- Bound::Excluded(range_to_move.start),
- Bound::Included(insertion_point),
- ))
- .next()
- .is_none()
+ .excerpt_containing(range_to_move.start..insertion_point)
+ .is_some()
{
let mut text = String::from("\n");
text.extend(buffer.text_for_range(range_to_move.clone()));
@@ -9282,11 +9259,7 @@ impl Editor {
let snapshot = buffer.snapshot(cx);
let mut excerpt_ids = selections
.iter()
- .flat_map(|selection| {
- snapshot
- .excerpts_for_range(selection.range())
- .map(|excerpt| excerpt.id())
- })
+ .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
.collect::<Vec<_>>();
excerpt_ids.sort();
excerpt_ids.dedup();
@@ -9306,6 +9279,30 @@ impl Editor {
})
}
+ pub fn go_to_singleton_buffer_point(&mut self, point: Point, cx: &mut ViewContext<Self>) {
+ self.go_to_singleton_buffer_range(point..point, cx);
+ }
+
+ pub fn go_to_singleton_buffer_range(
+ &mut self,
+ range: Range<Point>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ let multibuffer = self.buffer().read(cx);
+ let Some(buffer) = multibuffer.as_singleton() else {
+ return;
+ };
+ let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
+ return;
+ };
+ let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
+ return;
+ };
+ self.change_selections(Some(Autoscroll::center()), cx, |s| {
+ s.select_anchor_ranges([start..end])
+ });
+ }
+
fn go_to_diagnostic(&mut self, _: &GoToDiagnostic, cx: &mut ViewContext<Self>) {
self.go_to_diagnostic_impl(Direction::Next, cx)
}
@@ -9321,7 +9318,14 @@ impl Editor {
// If there is an active Diagnostic Popover jump to its diagnostic instead.
if direction == Direction::Next {
if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
- self.activate_diagnostics(popover.group_id(), cx);
+ let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
+ return;
+ };
+ self.activate_diagnostics(
+ buffer_id,
+ popover.local_diagnostic.diagnostic.group_id,
+ cx,
+ );
if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
let primary_range_start = active_diagnostics.primary_range.start;
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
@@ -9352,25 +9356,27 @@ impl Editor {
};
let snapshot = self.snapshot(cx);
loop {
- let diagnostics = if direction == Direction::Prev {
- buffer.diagnostics_in_range(0..search_start, true)
+ let mut diagnostics;
+ if direction == Direction::Prev {
+ diagnostics = buffer
+ .diagnostics_in_range::<_, usize>(0..search_start)
+ .collect::<Vec<_>>();
+ diagnostics.reverse();
} else {
- buffer.diagnostics_in_range(search_start..buffer.len(), false)
- }
- .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start));
- let search_start_anchor = buffer.anchor_after(search_start);
+ diagnostics = buffer
+ .diagnostics_in_range::<_, usize>(search_start..buffer.len())
+ .collect::<Vec<_>>();
+ };
let group = diagnostics
+ .into_iter()
+ .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
// relies on diagnostics_in_range to return diagnostics with the same starting range to
// be sorted in a stable way
// skip until we are at current active diagnostic, if it exists
.skip_while(|entry| {
let is_in_range = match direction {
- Direction::Prev => {
- entry.range.start.cmp(&search_start_anchor, &buffer).is_ge()
- }
- Direction::Next => {
- entry.range.start.cmp(&search_start_anchor, &buffer).is_le()
- }
+ Direction::Prev => entry.range.end > search_start,
+ Direction::Next => entry.range.start < search_start,
};
is_in_range
&& self
@@ -9381,7 +9387,7 @@ impl Editor {
.find_map(|entry| {
if entry.diagnostic.is_primary
&& entry.diagnostic.severity <= DiagnosticSeverity::WARNING
- && !(entry.range.start == entry.range.end)
+ && entry.range.start != entry.range.end
// if we match with the active diagnostic, skip it
&& Some(entry.diagnostic.group_id)
!= self.active_diagnostics.as_ref().map(|d| d.group_id)
@@ -9393,8 +9399,10 @@ impl Editor {
});
if let Some((primary_range, group_id)) = group {
- self.activate_diagnostics(group_id, cx);
- let primary_range = primary_range.to_offset(&buffer);
+ let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
+ return;
+ };
+ self.activate_diagnostics(buffer_id, group_id, cx);
if self.active_diagnostics.is_some() {
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select(vec![Selection {
@@ -9439,21 +9447,25 @@ impl Editor {
position: Point,
cx: &mut ViewContext<Editor>,
) -> Option<MultiBufferDiffHunk> {
- for (ix, position) in [position, Point::zero()].into_iter().enumerate() {
- if let Some(hunk) = self.go_to_next_hunk_in_direction(
- snapshot,
- position,
- ix > 0,
- snapshot.diff_map.diff_hunks_in_range(
- position + Point::new(1, 0)..snapshot.buffer_snapshot.max_point(),
- &snapshot.buffer_snapshot,
- ),
- cx,
- ) {
- return Some(hunk);
- }
+ let mut hunk = snapshot
+ .buffer_snapshot
+ .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
+ .find(|hunk| hunk.row_range.start.0 > position.row);
+ if hunk.is_none() {
+ hunk = snapshot
+ .buffer_snapshot
+ .diff_hunks_in_range(Point::zero()..position)
+ .find(|hunk| hunk.row_range.end.0 < position.row)
}
- None
+ if let Some(hunk) = &hunk {
+ let destination = Point::new(hunk.row_range.start.0, 0);
+ self.unfold_ranges(&[destination..destination], false, false, cx);
+ self.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.select_ranges(vec![destination..destination]);
+ });
+ }
+
+ hunk
}
fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext<Self>) {
@@ -9468,52 +9480,19 @@ impl Editor {
position: Point,
cx: &mut ViewContext<Editor>,
) -> Option<MultiBufferDiffHunk> {
- for (ix, position) in [position, snapshot.buffer_snapshot.max_point()]
- .into_iter()
- .enumerate()
- {
- if let Some(hunk) = self.go_to_next_hunk_in_direction(
- snapshot,
- position,
- ix > 0,
- snapshot
- .diff_map
- .diff_hunks_in_range_rev(Point::zero()..position, &snapshot.buffer_snapshot),
- cx,
- ) {
- return Some(hunk);
- }
+ let mut hunk = snapshot.buffer_snapshot.diff_hunk_before(position);
+ if hunk.is_none() {
+ hunk = snapshot.buffer_snapshot.diff_hunk_before(Point::MAX);
}
- None
- }
-
- fn go_to_next_hunk_in_direction(
- &mut self,
- snapshot: &DisplaySnapshot,
- initial_point: Point,
- is_wrapped: bool,
- hunks: impl Iterator<Item = MultiBufferDiffHunk>,
- cx: &mut ViewContext<Editor>,
- ) -> Option<MultiBufferDiffHunk> {
- let display_point = initial_point.to_display_point(snapshot);
- let mut hunks = hunks
- .map(|hunk| (diff_hunk_to_display(&hunk, snapshot), hunk))
- .filter(|(display_hunk, _)| {
- is_wrapped || !display_hunk.contains_display_row(display_point.row())
- })
- .dedup();
-
- if let Some((display_hunk, hunk)) = hunks.next() {
+ if let Some(hunk) = &hunk {
+ let destination = Point::new(hunk.row_range.start.0, 0);
+ self.unfold_ranges(&[destination..destination], false, false, cx);
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
- let row = display_hunk.start_display_row();
- let point = DisplayPoint::new(row, 0);
- s.select_display_ranges([point..point]);
+ s.select_ranges(vec![destination..destination]);
});
-
- Some(hunk)
- } else {
- None
}
+
+ hunk
}
pub fn go_to_definition(
@@ -9762,15 +9741,12 @@ impl Editor {
};
let pane = workspace.read(cx).active_pane().clone();
- let range = target.range.to_offset(target.buffer.read(cx));
+ let range = target.range.to_point(target.buffer.read(cx));
let range = editor.range_for_match(&range);
+ let range = collapse_multiline_range(range);
if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
- let buffer = target.buffer.read(cx);
- let range = check_multiline_range(buffer, range);
- editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
- s.select_ranges([range]);
- });
+ editor.go_to_singleton_buffer_range(range.clone(), cx);
} else {
cx.window_context().defer(move |cx| {
let target_editor: View<Self> =
@@ -9793,15 +9769,7 @@ impl Editor {
// When selecting a definition in a different buffer, disable the nav history
// to avoid creating a history entry at the previous cursor location.
pane.update(cx, |pane, _| pane.disable_history());
- let buffer = target.buffer.read(cx);
- let range = check_multiline_range(buffer, range);
- target_editor.change_selections(
- Some(Autoscroll::focused()),
- cx,
- |s| {
- s.select_ranges([range]);
- },
- );
+ target_editor.go_to_singleton_buffer_range(range, cx);
pane.update(cx, |pane, _| pane.enable_history());
});
});
@@ -10420,11 +10388,12 @@ impl Editor {
let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
BTreeMap::new();
for selection_range in selection_ranges {
- for (excerpt, buffer_range) in snapshot.range_to_buffer_ranges(selection_range)
+ for (buffer, buffer_range, _) in
+ snapshot.range_to_buffer_ranges(selection_range)
{
- let buffer_id = excerpt.buffer_id();
- let start = excerpt.buffer().anchor_before(buffer_range.start);
- let end = excerpt.buffer().anchor_after(buffer_range.end);
+ let buffer_id = buffer.remote_id();
+ let start = buffer.anchor_before(buffer_range.start);
+ let end = buffer.anchor_after(buffer_range.end);
buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
buffer_id_to_ranges
.entry(buffer_id)
@@ -10499,12 +10468,11 @@ impl Editor {
let buffer = self.buffer.read(cx).snapshot(cx);
let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
let is_valid = buffer
- .diagnostics_in_range(active_diagnostics.primary_range.clone(), false)
+ .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone())
.any(|entry| {
- let range = entry.range.to_offset(&buffer);
entry.diagnostic.is_primary
- && !range.is_empty()
- && range.start == primary_range_start
+ && !entry.range.is_empty()
+ && entry.range.start == primary_range_start
&& entry.diagnostic.message == active_diagnostics.primary_message
});
@@ -10524,7 +10492,12 @@ impl Editor {
}
}
- fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext<Self>) {
+ fn activate_diagnostics(
+ &mut self,
+ buffer_id: BufferId,
+ group_id: usize,
+ cx: &mut ViewContext<Self>,
+ ) {
self.dismiss_diagnostics(cx);
let snapshot = self.snapshot(cx);
self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
@@ -10532,21 +10505,17 @@ impl Editor {
let mut primary_range = None;
let mut primary_message = None;
- let mut group_end = Point::zero();
let diagnostic_group = buffer
- .diagnostic_group(group_id)
+ .diagnostic_group(buffer_id, group_id)
.filter_map(|entry| {
- let start = entry.range.start.to_point(&buffer);
- let end = entry.range.end.to_point(&buffer);
+ let start = entry.range.start;
+ let end = entry.range.end;
if snapshot.is_line_folded(MultiBufferRow(start.row))
&& (start.row == end.row
|| snapshot.is_line_folded(MultiBufferRow(end.row)))
{
return None;
}
- if end > group_end {
- group_end = end;
- }
if entry.diagnostic.is_primary {
primary_range = Some(entry.range.clone());
primary_message = Some(entry.diagnostic.message.clone());
@@ -10579,7 +10548,8 @@ impl Editor {
.collect();
Some(ActiveDiagnosticGroup {
- primary_range,
+ primary_range: buffer.anchor_before(primary_range.start)
+ ..buffer.anchor_after(primary_range.end),
primary_message,
group_id,
blocks,
@@ -10721,17 +10691,16 @@ impl Editor {
}
} else {
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
- let mut toggled_buffers = HashSet::default();
- for (_, buffer_snapshot, _) in
- multi_buffer_snapshot.excerpts_in_ranges(self.selections.disjoint_anchor_ranges())
- {
- let buffer_id = buffer_snapshot.remote_id();
- if toggled_buffers.insert(buffer_id) {
- if self.buffer_folded(buffer_id, cx) {
- self.unfold_buffer(buffer_id, cx);
- } else {
- self.fold_buffer(buffer_id, cx);
- }
+ let buffer_ids: HashSet<_> = multi_buffer_snapshot
+ .ranges_to_buffer_ranges(self.selections.disjoint_anchor_ranges())
+ .map(|(snapshot, _, _)| snapshot.remote_id())
+ .collect();
+
+ for buffer_id in buffer_ids {
+ if self.is_buffer_folded(buffer_id, cx) {
+ self.unfold_buffer(buffer_id, cx);
+ } else {
+ self.fold_buffer(buffer_id, cx);
}
}
}
@@ -10804,14 +10773,13 @@ impl Editor {
self.fold_creases(to_fold, true, cx);
} else {
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
- let mut folded_buffers = HashSet::default();
- for (_, buffer_snapshot, _) in
- multi_buffer_snapshot.excerpts_in_ranges(self.selections.disjoint_anchor_ranges())
- {
- let buffer_id = buffer_snapshot.remote_id();
- if folded_buffers.insert(buffer_id) {
- self.fold_buffer(buffer_id, cx);
- }
+
+ let buffer_ids: HashSet<_> = multi_buffer_snapshot
+ .ranges_to_buffer_ranges(self.selections.disjoint_anchor_ranges())
+ .map(|(snapshot, _, _)| snapshot.remote_id())
+ .collect();
+ for buffer_id in buffer_ids {
+ self.fold_buffer(buffer_id, cx);
}
}
}
@@ -10885,11 +10853,14 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx);
- let Some((_, _, buffer)) = snapshot.as_singleton() else {
- return;
- };
- let creases = buffer
- .function_body_fold_ranges(0..buffer.len())
+
+ let ranges = snapshot
+ .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
+ .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
+ .collect::<Vec<_>>();
+
+ let creases = ranges
+ .into_iter()
.map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
.collect();
@@ -10967,14 +10938,12 @@ impl Editor {
self.unfold_ranges(&ranges, true, true, cx);
} else {
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
- let mut unfolded_buffers = HashSet::default();
- for (_, buffer_snapshot, _) in
- multi_buffer_snapshot.excerpts_in_ranges(self.selections.disjoint_anchor_ranges())
- {
- let buffer_id = buffer_snapshot.remote_id();
- if unfolded_buffers.insert(buffer_id) {
- self.unfold_buffer(buffer_id, cx);
- }
+ let buffer_ids: HashSet<_> = multi_buffer_snapshot
+ .ranges_to_buffer_ranges(self.selections.disjoint_anchor_ranges())
+ .map(|(snapshot, _, _)| snapshot.remote_id())
+ .collect();
+ for buffer_id in buffer_ids {
+ self.unfold_buffer(buffer_id, cx);
}
}
}
@@ -11096,10 +11065,6 @@ impl Editor {
self.request_autoscroll(Autoscroll::fit(), cx);
}
- for buffer_id in buffers_affected {
- Self::sync_expanded_diff_hunks(&mut self.diff_map, buffer_id, cx);
- }
-
cx.notify();
if let Some(active_diagnostics) = self.active_diagnostics.take() {
@@ -11131,7 +11096,7 @@ impl Editor {
}
pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut ViewContext<Self>) {
- if self.buffer().read(cx).is_singleton() || self.buffer_folded(buffer_id, cx) {
+ if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
return;
}
let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
@@ -11148,7 +11113,7 @@ impl Editor {
}
pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut ViewContext<Self>) {
- if self.buffer().read(cx).is_singleton() || !self.buffer_folded(buffer_id, cx) {
+ if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
return;
}
let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
@@ -11165,8 +11130,12 @@ impl Editor {
cx.notify();
}
- pub fn buffer_folded(&self, buffer: BufferId, cx: &AppContext) -> bool {
- self.display_map.read(cx).buffer_folded(buffer)
+ pub fn is_buffer_folded(&self, buffer: BufferId, cx: &AppContext) -> bool {
+ self.display_map.read(cx).is_buffer_folded(buffer)
+ }
+
+ pub fn folded_buffers<'a>(&self, cx: &'a AppContext) -> &'a HashSet<BufferId> {
+ self.display_map.read(cx).folded_buffers()
}
/// Removes any folds with the given ranges.
@@ -11207,10 +11176,6 @@ impl Editor {
self.request_autoscroll(Autoscroll::fit(), cx);
}
- for buffer_id in buffers_affected {
- Self::sync_expanded_diff_hunks(&mut self.diff_map, buffer_id, cx);
- }
-
cx.notify();
self.scrollbar_marker_state.dirty = true;
self.active_indent_guides_state.dirty = true;
@@ -11220,6 +11185,108 @@ impl Editor {
self.display_map.read(cx).fold_placeholder.clone()
}
+ pub fn set_expand_all_diff_hunks(&mut self, cx: &mut AppContext) {
+ self.buffer.update(cx, |buffer, cx| {
+ buffer.set_all_diff_hunks_expanded(cx);
+ });
+ }
+
+ pub fn expand_all_diff_hunks(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext<Self>) {
+ self.buffer.update(cx, |buffer, cx| {
+ buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
+ });
+ }
+
+ pub fn toggle_selected_diff_hunks(
+ &mut self,
+ _: &ToggleSelectedDiffHunks,
+ cx: &mut ViewContext<Self>,
+ ) {
+ let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
+ self.toggle_diff_hunks_in_ranges(ranges, cx);
+ }
+
+ pub fn expand_selected_diff_hunks(&mut self, cx: &mut ViewContext<Self>) {
+ let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
+ self.buffer
+ .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
+ }
+
+ pub fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<Self>) -> bool {
+ self.buffer.update(cx, |buffer, cx| {
+ let ranges = vec![Anchor::min()..Anchor::max()];
+ if !buffer.all_diff_hunks_expanded()
+ && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
+ {
+ buffer.collapse_diff_hunks(ranges, cx);
+ true
+ } else {
+ false
+ }
+ })
+ }
+
+ fn toggle_diff_hunks_in_ranges(
+ &mut self,
+ ranges: Vec<Range<Anchor>>,
+ cx: &mut ViewContext<'_, Editor>,
+ ) {
+ self.buffer.update(cx, |buffer, cx| {
+ if buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx) {
+ buffer.collapse_diff_hunks(ranges, cx)
+ } else {
+ buffer.expand_diff_hunks(ranges, cx)
+ }
+ })
+ }
+
+ pub(crate) fn apply_all_diff_hunks(
+ &mut self,
+ _: &ApplyAllDiffHunks,
+ cx: &mut ViewContext<Self>,
+ ) {
+ let buffers = self.buffer.read(cx).all_buffers();
+ for branch_buffer in buffers {
+ branch_buffer.update(cx, |branch_buffer, cx| {
+ branch_buffer.merge_into_base(Vec::new(), cx);
+ });
+ }
+
+ if let Some(project) = self.project.clone() {
+ self.save(true, project, cx).detach_and_log_err(cx);
+ }
+ }
+
+ pub(crate) fn apply_selected_diff_hunks(
+ &mut self,
+ _: &ApplyDiffHunk,
+ cx: &mut ViewContext<Self>,
+ ) {
+ let snapshot = self.snapshot(cx);
+ let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx).into_iter());
+ let mut ranges_by_buffer = HashMap::default();
+ self.transact(cx, |editor, cx| {
+ for hunk in hunks {
+ if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
+ ranges_by_buffer
+ .entry(buffer.clone())
+ .or_insert_with(Vec::new)
+ .push(hunk.buffer_range.to_offset(buffer.read(cx)));
+ }
+ }
+
+ for (buffer, ranges) in ranges_by_buffer {
+ buffer.update(cx, |buffer, cx| {
+ buffer.merge_into_base(ranges, cx);
+ });
+ }
+ });
+
+ if let Some(project) = self.project.clone() {
+ self.save(true, project, cx).detach_and_log_err(cx);
+ }
+ }
+
pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut ViewContext<Self>) {
if hovered != self.gutter_hovered {
self.gutter_hovered = hovered;
@@ -11751,29 +11818,22 @@ impl Editor {
let selection = self.selections.newest::<Point>(cx);
let selection_range = selection.range();
- let (buffer, selection) = if let Some(buffer) = self.buffer().read(cx).as_singleton() {
- (buffer, selection_range.start.row..selection_range.end.row)
- } else {
- let multi_buffer = self.buffer().read(cx);
- let multi_buffer_snapshot = multi_buffer.snapshot(cx);
- let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
-
- let (excerpt, range) = if selection.reversed {
- buffer_ranges.first()
- } else {
- buffer_ranges.last()
- }?;
+ let multi_buffer = self.buffer().read(cx);
+ let multi_buffer_snapshot = multi_buffer.snapshot(cx);
+ let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
- let snapshot = excerpt.buffer();
- let selection = text::ToPoint::to_point(&range.start, &snapshot).row
- ..text::ToPoint::to_point(&range.end, &snapshot).row;
- (
- multi_buffer.buffer(excerpt.buffer_id()).unwrap().clone(),
- selection,
- )
- };
+ let (buffer, range, _) = if selection.reversed {
+ buffer_ranges.first()
+ } else {
+ buffer_ranges.last()
+ }?;
- Some((buffer, selection))
+ let selection = text::ToPoint::to_point(&range.start, &buffer).row
+ ..text::ToPoint::to_point(&range.end, &buffer).row;
+ Some((
+ multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
+ selection,
+ ))
});
let Some((buffer, selection)) = buffer_and_selection else {
@@ -12543,9 +12603,14 @@ impl Editor {
} => {
self.tasks_update_task = Some(self.refresh_runnables(cx));
let buffer_id = buffer.read(cx).remote_id();
- if !self.diff_map.diff_bases.contains_key(&buffer_id) {
+ if self.buffer.read(cx).change_set_for(buffer_id).is_none() {
if let Some(project) = &self.project {
- get_unstaged_changes_for_buffers(project, [buffer.clone()], cx);
+ get_unstaged_changes_for_buffers(
+ project,
+ [buffer.clone()],
+ self.buffer.clone(),
+ cx,
+ );
}
}
cx.emit(EditorEvent::ExcerptsAdded {
@@ -12664,14 +12729,14 @@ impl Editor {
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
let mut new_selections_by_buffer = HashMap::default();
for selection in selections {
- for (excerpt, range) in
+ for (buffer, range, _) in
multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
{
- let mut range = range.to_point(excerpt.buffer());
+ let mut range = range.to_point(buffer);
range.start.column = 0;
- range.end.column = excerpt.buffer().line_len(range.end.row);
+ range.end.column = buffer.line_len(range.end.row);
new_selections_by_buffer
- .entry(multi_buffer.buffer(excerpt.buffer_id()).unwrap())
+ .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
.or_insert(Vec::new())
.push(range)
}
@@ -12772,13 +12837,13 @@ impl Editor {
let selections = self.selections.all::<usize>(cx);
let multi_buffer = self.buffer.read(cx);
for selection in selections {
- for (excerpt, mut range) in multi_buffer
+ for (buffer, mut range, _) in multi_buffer
.snapshot(cx)
.range_to_buffer_ranges(selection.range())
{
// When editing branch buffers, jump to the corresponding location
// in their base buffer.
- let mut buffer_handle = multi_buffer.buffer(excerpt.buffer_id()).unwrap();
+ let mut buffer_handle = multi_buffer.buffer(buffer.remote_id()).unwrap();
let buffer = buffer_handle.read(cx);
if let Some(base_buffer) = buffer.base_buffer() {
range = buffer.range_to_version(range, &base_buffer.read(cx).version());
@@ -19,11 +19,11 @@ use language::{
},
BracketPairConfig,
Capability::ReadWrite,
- FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
- LanguageName, Override, ParsedMarkdown, Point,
+ FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
+ Override, ParsedMarkdown, Point,
};
use language_settings::{Formatter, FormatterList, IndentGuideSettings};
-use multi_buffer::MultiBufferIndentGuide;
+use multi_buffer::IndentGuide;
use parking_lot::Mutex;
use pretty_assertions::{assert_eq, assert_ne};
use project::{buffer_store::BufferChangeSet, FakeFs};
@@ -3363,8 +3363,8 @@ async fn test_custom_newlines_cause_no_false_positive_diffs(
let snapshot = editor.snapshot(cx);
assert_eq!(
snapshot
- .diff_map
- .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
+ .buffer_snapshot
+ .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
.collect::<Vec<_>>(),
Vec::new(),
"Should not have any diffs for files with custom newlines"
@@ -5480,6 +5480,109 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
});
}
+#[gpui::test]
+async fn test_fold_function_bodies(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |_| {});
+
+ let base_text = r#"
+ impl A {
+ // this is an unstaged comment
+
+ fn b() {
+ c();
+ }
+
+ // this is another unstaged comment
+
+ fn d() {
+ // e
+ // f
+ }
+ }
+
+ fn g() {
+ // h
+ }
+ "#
+ .unindent();
+
+ let text = r#"
+ Λimpl A {
+
+ fn b() {
+ c();
+ }
+
+ fn d() {
+ // e
+ // f
+ }
+ }
+
+ fn g() {
+ // h
+ }
+ "#
+ .unindent();
+
+ let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
+ cx.set_state(&text);
+ cx.set_diff_base(&base_text);
+ cx.update_editor(|editor, cx| {
+ editor.expand_all_diff_hunks(&Default::default(), cx);
+ });
+
+ cx.assert_state_with_diff(
+ "
+ Λimpl A {
+ - // this is an unstaged comment
+
+ fn b() {
+ c();
+ }
+
+ - // this is another unstaged comment
+ -
+ fn d() {
+ // e
+ // f
+ }
+ }
+
+ fn g() {
+ // h
+ }
+ "
+ .unindent(),
+ );
+
+ let expected_display_text = "
+ impl A {
+ // this is an unstaged comment
+
+ fn b() {
+ β―
+ }
+
+ // this is another unstaged comment
+
+ fn d() {
+ β―
+ }
+ }
+
+ fn g() {
+ β―
+ }
+ "
+ .unindent();
+
+ cx.update_editor(|editor, cx| {
+ editor.fold_function_bodies(&FoldFunctionBodies, cx);
+ assert_eq!(editor.display_text(cx), expected_display_text);
+ });
+}
+
#[gpui::test]
async fn test_autoindent(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -10319,7 +10422,7 @@ async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
}
#[gpui::test]
-async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
+async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
@@ -10420,7 +10523,26 @@ async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext)
);
cx.update_editor(|editor, cx| {
- for _ in 0..3 {
+ editor.go_to_prev_hunk(&GoToPrevHunk, cx);
+ });
+
+ cx.assert_editor_state(
+ &r#"
+ Λuse some::modified;
+
+
+ fn main() {
+ println!("hello there");
+
+ println!("around the");
+ println!("world");
+ }
+ "#
+ .unindent(),
+ );
+
+ cx.update_editor(|editor, cx| {
+ for _ in 0..2 {
editor.go_to_prev_hunk(&GoToPrevHunk, cx);
}
});
@@ -10442,11 +10564,10 @@ async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext)
cx.update_editor(|editor, cx| {
editor.fold(&Fold, cx);
+ });
- //Make sure that the fold only gets one hunk
- for _ in 0..4 {
- editor.go_to_next_hunk(&GoToHunk, cx);
- }
+ cx.update_editor(|editor, cx| {
+ editor.go_to_next_hunk(&GoToHunk, cx);
});
cx.assert_editor_state(
@@ -11815,6 +11936,39 @@ async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
);
}
+#[gpui::test]
+async fn test_deleting_over_diff_hunk(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |_| {});
+ let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
+ let base_text = indoc! {r#"
+ one
+
+ two
+ three
+ "#};
+
+ cx.set_diff_base(base_text);
+ cx.set_state("\nΛ\n");
+ cx.executor().run_until_parked();
+ cx.update_editor(|editor, cx| {
+ editor.expand_selected_diff_hunks(cx);
+ });
+ cx.executor().run_until_parked();
+ cx.update_editor(|editor, cx| {
+ editor.backspace(&Default::default(), cx);
+ });
+ cx.run_until_parked();
+ cx.assert_state_with_diff(
+ indoc! {r#"
+
+ - two
+ - threeΛ
+ +
+ "#}
+ .to_string(),
+ );
+}
+
#[gpui::test]
async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -12019,13 +12173,11 @@ async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
(buffer_3.clone(), base_text_3),
] {
let change_set = cx.new_model(|cx| {
- BufferChangeSet::new_with_base_text(
- diff_base.to_string(),
- buffer.read(cx).text_snapshot(),
- cx,
- )
+ BufferChangeSet::new_with_base_text(diff_base.to_string(), &buffer, cx)
});
- editor.diff_map.add_change_set(change_set, cx)
+ editor
+ .buffer
+ .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx));
}
});
cx.executor().run_until_parked();
@@ -12385,7 +12537,10 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
+async fn test_toggle_selected_diff_hunks(
+ executor: BackgroundExecutor,
+ cx: &mut gpui::TestAppContext,
+) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
@@ -12423,7 +12578,7 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
cx.update_editor(|editor, cx| {
editor.go_to_next_hunk(&GoToHunk, cx);
- editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
+ editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, cx);
});
executor.run_until_parked();
cx.assert_state_with_diff(
@@ -12443,12 +12598,34 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
);
cx.update_editor(|editor, cx| {
- for _ in 0..3 {
+ for _ in 0..2 {
editor.go_to_next_hunk(&GoToHunk, cx);
- editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
+ editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, cx);
}
});
executor.run_until_parked();
+ cx.assert_state_with_diff(
+ r#"
+ - use some::mod;
+ + Λuse some::modified;
+
+
+ fn main() {
+ - println!("hello");
+ + println!("hello there");
+
+ + println!("around the");
+ println!("world");
+ }
+ "#
+ .unindent(),
+ );
+
+ cx.update_editor(|editor, cx| {
+ editor.go_to_next_hunk(&GoToHunk, cx);
+ editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, cx);
+ });
+ executor.run_until_parked();
cx.assert_state_with_diff(
r#"
- use some::mod;
@@ -12534,7 +12711,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
executor.run_until_parked();
cx.update_editor(|editor, cx| {
- editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
+ editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
});
executor.run_until_parked();
cx.assert_state_with_diff(
@@ -12579,7 +12756,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
);
cx.update_editor(|editor, cx| {
- editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
+ editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
});
executor.run_until_parked();
cx.assert_state_with_diff(
@@ -12602,170 +12779,6 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
);
}
-#[gpui::test]
-async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
- init_test(cx, |_| {});
-
- let mut cx = EditorTestContext::new(cx).await;
-
- let diff_base = r#"
- use some::mod1;
- use some::mod2;
-
- const A: u32 = 42;
- const B: u32 = 42;
- const C: u32 = 42;
-
- fn main() {
- println!("hello");
-
- println!("world");
- }
-
- fn another() {
- println!("another");
- }
-
- fn another2() {
- println!("another2");
- }
- "#
- .unindent();
-
- cx.set_state(
- &r#"
- Β«use some::mod2;
-
- const A: u32 = 42;
- const C: u32 = 42;
-
- fn main() {
- //println!("hello");
-
- println!("world");
- //
- //ΛΒ»
- }
-
- fn another() {
- println!("another");
- println!("another");
- }
-
- println!("another2");
- }
- "#
- .unindent(),
- );
-
- cx.set_diff_base(&diff_base);
- executor.run_until_parked();
-
- cx.update_editor(|editor, cx| {
- editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
- });
- executor.run_until_parked();
-
- cx.assert_state_with_diff(
- r#"
- - use some::mod1;
- Β«use some::mod2;
-
- const A: u32 = 42;
- - const B: u32 = 42;
- const C: u32 = 42;
-
- fn main() {
- - println!("hello");
- + //println!("hello");
-
- println!("world");
- + //
- + //ΛΒ»
- }
-
- fn another() {
- println!("another");
- + println!("another");
- }
-
- - fn another2() {
- println!("another2");
- }
- "#
- .unindent(),
- );
-
- // Fold across some of the diff hunks. They should no longer appear expanded.
- cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
- cx.executor().run_until_parked();
-
- // Hunks are not shown if their position is within a fold
- cx.assert_state_with_diff(
- r#"
- Β«use some::mod2;
-
- const A: u32 = 42;
- const C: u32 = 42;
-
- fn main() {
- //println!("hello");
-
- println!("world");
- //
- //ΛΒ»
- }
-
- fn another() {
- println!("another");
- + println!("another");
- }
-
- - fn another2() {
- println!("another2");
- }
- "#
- .unindent(),
- );
-
- cx.update_editor(|editor, cx| {
- editor.select_all(&SelectAll, cx);
- editor.unfold_lines(&UnfoldLines, cx);
- });
- cx.executor().run_until_parked();
-
- // The deletions reappear when unfolding.
- cx.assert_state_with_diff(
- r#"
- - use some::mod1;
- Β«use some::mod2;
-
- const A: u32 = 42;
- - const B: u32 = 42;
- const C: u32 = 42;
-
- fn main() {
- - println!("hello");
- + //println!("hello");
-
- println!("world");
- + //
- + //
- }
-
- fn another() {
- println!("another");
- + println!("another");
- }
-
- - fn another2() {
- println!("another2");
- }
- ΛΒ»"#
- .unindent(),
- );
-}
-
#[gpui::test]
async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -12849,13 +12862,11 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
(buffer_3.clone(), file_3_old),
] {
let change_set = cx.new_model(|cx| {
- BufferChangeSet::new_with_base_text(
- diff_base.to_string(),
- buffer.read(cx).text_snapshot(),
- cx,
- )
+ BufferChangeSet::new_with_base_text(diff_base.to_string(), &buffer, cx)
});
- editor.diff_map.add_change_set(change_set, cx)
+ editor
+ .buffer
+ .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx));
}
})
.unwrap();
@@ -12895,7 +12906,7 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
cx.update_editor(|editor, cx| {
editor.select_all(&SelectAll, cx);
- editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
+ editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, cx);
});
cx.executor().run_until_parked();
@@ -12962,17 +12973,18 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext
let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
editor
.update(cx, |editor, cx| {
- let buffer = buffer.read(cx).text_snapshot();
let change_set = cx
- .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
- editor.diff_map.add_change_set(change_set, cx)
+ .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), &buffer, cx));
+ editor
+ .buffer
+ .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx))
})
.unwrap();
let mut cx = EditorTestContext::for_editor(editor, cx).await;
cx.run_until_parked();
- cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
+ cx.update_editor(|editor, cx| editor.expand_all_diff_hunks(&Default::default(), cx));
cx.executor().run_until_parked();
cx.assert_state_with_diff(
@@ -12981,8 +12993,6 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext
- bbb
+ BBB
- - ddd
- - eee
+ EEE
fff
"
@@ -13036,7 +13046,7 @@ async fn test_edits_around_expanded_insertion_hunks(
executor.run_until_parked();
cx.update_editor(|editor, cx| {
- editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
+ editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
});
executor.run_until_parked();
@@ -13055,7 +13065,7 @@ async fn test_edits_around_expanded_insertion_hunks(
println!("world");
}
- "#
+ "#
.unindent(),
);
@@ -13078,7 +13088,7 @@ async fn test_edits_around_expanded_insertion_hunks(
println!("world");
}
- "#
+ "#
.unindent(),
);
@@ -13102,7 +13112,7 @@ async fn test_edits_around_expanded_insertion_hunks(
println!("world");
}
- "#
+ "#
.unindent(),
);
@@ -13127,7 +13137,7 @@ async fn test_edits_around_expanded_insertion_hunks(
println!("world");
}
- "#
+ "#
.unindent(),
);
@@ -13153,7 +13163,7 @@ async fn test_edits_around_expanded_insertion_hunks(
println!("world");
}
- "#
+ "#
.unindent(),
);
@@ -13164,21 +13174,63 @@ async fn test_edits_around_expanded_insertion_hunks(
executor.run_until_parked();
cx.assert_state_with_diff(
r#"
- use some::mod1;
- - use some::mod2;
- -
- - const A: u32 = 42;
Λ
fn main() {
println!("hello");
println!("world");
}
- "#
+ "#
.unindent(),
);
}
+#[gpui::test]
+async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+ cx.set_diff_base(indoc! { "
+ one
+ two
+ three
+ four
+ five
+ "
+ });
+ cx.set_state(indoc! { "
+ one
+ Λthree
+ five
+ "});
+ cx.run_until_parked();
+ cx.update_editor(|editor, cx| {
+ editor.toggle_selected_diff_hunks(&Default::default(), cx);
+ });
+ cx.assert_state_with_diff(
+ indoc! { "
+ one
+ - two
+ Λthree
+ - four
+ five
+ "}
+ .to_string(),
+ );
+ cx.update_editor(|editor, cx| {
+ editor.toggle_selected_diff_hunks(&Default::default(), cx);
+ });
+
+ cx.assert_state_with_diff(
+ indoc! { "
+ one
+ Λthree
+ five
+ "}
+ .to_string(),
+ );
+}
+
#[gpui::test]
async fn test_edits_around_expanded_deletion_hunks(
executor: BackgroundExecutor,
@@ -13227,7 +13279,7 @@ async fn test_edits_around_expanded_deletion_hunks(
executor.run_until_parked();
cx.update_editor(|editor, cx| {
- editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
+ editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
});
executor.run_until_parked();
@@ -13246,7 +13298,7 @@ async fn test_edits_around_expanded_deletion_hunks(
println!("world");
}
- "#
+ "#
.unindent(),
);
@@ -13269,7 +13321,7 @@ async fn test_edits_around_expanded_deletion_hunks(
println!("world");
}
- "#
+ "#
.unindent(),
);
@@ -13292,7 +13344,7 @@ async fn test_edits_around_expanded_deletion_hunks(
println!("world");
}
- "#
+ "#
.unindent(),
);
@@ -13316,6 +13368,71 @@ async fn test_edits_around_expanded_deletion_hunks(
println!("world");
}
+ "#
+ .unindent(),
+ );
+}
+
+#[gpui::test]
+async fn test_backspace_after_deletion_hunk(
+ executor: BackgroundExecutor,
+ cx: &mut gpui::TestAppContext,
+) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+
+ let base_text = r#"
+ one
+ two
+ three
+ four
+ five
+ "#
+ .unindent();
+ executor.run_until_parked();
+ cx.set_state(
+ &r#"
+ one
+ two
+ fΛour
+ five
+ "#
+ .unindent(),
+ );
+
+ cx.set_diff_base(&base_text);
+ executor.run_until_parked();
+
+ cx.update_editor(|editor, cx| {
+ editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
+ });
+ executor.run_until_parked();
+
+ cx.assert_state_with_diff(
+ r#"
+ one
+ two
+ - three
+ fΛour
+ five
+ "#
+ .unindent(),
+ );
+
+ cx.update_editor(|editor, cx| {
+ editor.backspace(&Backspace, cx);
+ editor.backspace(&Backspace, cx);
+ });
+ executor.run_until_parked();
+ cx.assert_state_with_diff(
+ r#"
+ one
+ two
+ - threeΛ
+ - four
+ + our
+ five
"#
.unindent(),
);
@@ -13369,7 +13486,7 @@ async fn test_edit_after_expanded_modification_hunk(
cx.set_diff_base(&diff_base);
executor.run_until_parked();
cx.update_editor(|editor, cx| {
- editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
+ editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
});
executor.run_until_parked();
@@ -13478,22 +13595,14 @@ fn assert_indent_guides(
);
}
- let expected: Vec<_> = expected
- .into_iter()
- .map(|guide| MultiBufferIndentGuide {
- multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
- buffer: guide,
- })
- .collect();
-
assert_eq!(indent_guides, expected, "Indent guides do not match");
}
fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
IndentGuide {
buffer_id,
- start_row,
- end_row,
+ start_row: MultiBufferRow(start_row),
+ end_row: MultiBufferRow(end_row),
depth,
tab_size: 4,
settings: IndentGuideSettings {
@@ -13945,6 +14054,105 @@ async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppCont
);
}
+#[gpui::test]
+async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |_| {});
+ let mut cx = EditorTestContext::new(cx).await;
+ let text = indoc! {
+ "
+ impl A {
+ fn b() {
+ 0;
+ 3;
+ 5;
+ 6;
+ 7;
+ }
+ }
+ "
+ };
+ let base_text = indoc! {
+ "
+ impl A {
+ fn b() {
+ 0;
+ 1;
+ 2;
+ 3;
+ 4;
+ }
+ fn c() {
+ 5;
+ 6;
+ 7;
+ }
+ }
+ "
+ };
+
+ cx.update_editor(|editor, cx| {
+ editor.set_text(text, cx);
+
+ editor.buffer().update(cx, |multibuffer, cx| {
+ let buffer = multibuffer.as_singleton().unwrap();
+ let change_set = cx.new_model(|cx| {
+ let mut change_set = BufferChangeSet::new(&buffer, cx);
+ change_set.recalculate_diff_sync(
+ base_text.into(),
+ buffer.read(cx).text_snapshot(),
+ true,
+ cx,
+ );
+ change_set
+ });
+
+ multibuffer.set_all_diff_hunks_expanded(cx);
+ multibuffer.add_change_set(change_set, cx);
+
+ buffer.read(cx).remote_id()
+ })
+ });
+
+ cx.assert_state_with_diff(
+ indoc! { "
+ impl A {
+ fn b() {
+ 0;
+ - 1;
+ - 2;
+ 3;
+ - 4;
+ - }
+ - fn c() {
+ 5;
+ 6;
+ 7;
+ }
+ }
+ Λ"
+ }
+ .to_string(),
+ );
+
+ let mut actual_guides = cx.update_editor(|editor, cx| {
+ editor
+ .snapshot(cx)
+ .buffer_snapshot
+ .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
+ .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
+ .collect::<Vec<_>>()
+ });
+ actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
+ assert_eq!(
+ actual_guides,
+ vec![
+ (MultiBufferRow(1)..=MultiBufferRow(12), 0),
+ (MultiBufferRow(2)..=MultiBufferRow(6), 1),
+ (MultiBufferRow(9)..=MultiBufferRow(11), 1),
+ ]
+ );
+}
+
#[gpui::test]
fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -15229,7 +15437,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
#[track_caller]
fn assert_hunk_revert(
not_reverted_text_with_selections: &str,
- expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
+ expected_hunk_statuses_before: Vec<DiffHunkStatus>,
expected_reverted_text_with_selections: &str,
base_text: &str,
cx: &mut EditorLspTestContext,
@@ -15238,12 +15446,12 @@ fn assert_hunk_revert(
cx.set_diff_base(base_text);
cx.executor().run_until_parked();
- let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
+ let actual_hunk_statuses_before = cx.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx);
let reverted_hunk_statuses = snapshot
- .diff_map
- .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
- .map(|hunk| hunk_status(&hunk))
+ .buffer_snapshot
+ .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
+ .map(|hunk| hunk.status())
.collect::<Vec<_>>();
editor.revert_selected_hunks(&RevertSelectedHunks, cx);
@@ -15251,5 +15459,5 @@ fn assert_hunk_revert(
});
cx.executor().run_until_parked();
cx.assert_editor_state(expected_reverted_text_with_selections);
- assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
+ assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
}
@@ -12,19 +12,17 @@ use crate::{
hover_popover::{
self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
},
- hunk_diff::{diff_hunk_to_display, DisplayDiffHunk},
- hunk_status,
items::BufferSearchHighlights,
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint, DisplayRow,
DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
- EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions,
- HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, InlineCompletion, JumpData,
- LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase,
- Selection, SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR,
- FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
- MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
+ EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
+ GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
+ InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point,
+ RevertSelectedHunks, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap,
+ StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
+ GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
};
use client::ParticipantIndex;
use collections::{BTreeMap, HashMap, HashSet};
@@ -46,12 +44,12 @@ use language::{
IndentGuideBackgroundColoring, IndentGuideColoring, IndentGuideSettings,
ShowWhitespaceSetting,
},
- ChunkRendererContext, DiagnosticEntry,
+ ChunkRendererContext,
};
use lsp::DiagnosticSeverity;
use multi_buffer::{
- Anchor, AnchorRangeExt, ExcerptId, ExcerptInfo, ExpandExcerptDirection, MultiBufferPoint,
- MultiBufferRow, ToOffset,
+ Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow,
+ RowInfo, ToOffset,
};
use project::project_settings::{GitGutterSetting, ProjectSettings};
use settings::Settings;
@@ -70,12 +68,27 @@ use sum_tree::Bias;
use text::BufferId;
use theme::{ActiveTheme, Appearance, PlayerColor};
use ui::{
- prelude::*, ButtonLike, ButtonStyle, ContextMenu, KeyBinding, Tooltip, POPOVER_Y_PADDING,
+ h_flex, prelude::*, ButtonLike, ButtonStyle, ContextMenu, IconButtonShape, KeyBinding, Tooltip,
+ POPOVER_Y_PADDING,
};
use unicode_segmentation::UnicodeSegmentation;
use util::{RangeExt, ResultExt};
use workspace::{item::Item, notifications::NotifyTaskExt, Workspace};
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum DisplayDiffHunk {
+ Folded {
+ display_row: DisplayRow,
+ },
+
+ Unfolded {
+ diff_base_byte_range: Range<usize>,
+ display_row_range: Range<DisplayRow>,
+ multi_buffer_range: Range<Anchor>,
+ status: DiffHunkStatus,
+ },
+}
+
struct SelectionLayout {
head: DisplayPoint,
cursor_shape: CursorShape,
@@ -381,8 +394,8 @@ impl EditorElement {
register_action(view, cx, Editor::copy_file_location);
register_action(view, cx, Editor::toggle_git_blame);
register_action(view, cx, Editor::toggle_git_blame_inline);
- register_action(view, cx, Editor::toggle_hunk_diff);
- register_action(view, cx, Editor::expand_all_hunk_diffs);
+ register_action(view, cx, Editor::toggle_selected_diff_hunks);
+ register_action(view, cx, Editor::expand_all_diff_hunks);
register_action(view, cx, |editor, action, cx| {
if let Some(task) = editor.format(action, cx) {
task.detach_and_notify_err(cx);
@@ -509,7 +522,7 @@ impl EditorElement {
fn mouse_left_down(
editor: &mut Editor,
event: &MouseDownEvent,
- hovered_hunk: Option<HoveredHunk>,
+ hovered_hunk: Option<Range<Anchor>>,
position_map: &PositionMap,
text_hitbox: &Hitbox,
gutter_hitbox: &Hitbox,
@@ -524,7 +537,7 @@ impl EditorElement {
let mut modifiers = event.modifiers;
if let Some(hovered_hunk) = hovered_hunk {
- editor.toggle_hovered_hunk(&hovered_hunk, cx);
+ editor.toggle_diff_hunks_in_ranges(vec![hovered_hunk], cx);
cx.notify();
return;
} else if gutter_hitbox.is_hovered(cx) {
@@ -1252,7 +1265,7 @@ impl EditorElement {
let editor = self.editor.read(cx);
let is_singleton = editor.is_singleton(cx);
// Git
- (is_singleton && scrollbar_settings.git_diff && !snapshot.diff_map.is_empty())
+ (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_diff_hunks())
||
// Buffer Search Results
(is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
@@ -1491,108 +1504,77 @@ impl EditorElement {
// Folds contained in a hunk are ignored apart from shrinking visual size
// If a fold contains any hunks then that fold line is marked as modified
- fn layout_gutter_git_hunks(
+ fn layout_gutter_diff_hunks(
&self,
line_height: Pixels,
gutter_hitbox: &Hitbox,
display_rows: Range<DisplayRow>,
- anchor_range: Range<Anchor>,
snapshot: &EditorSnapshot,
cx: &mut WindowContext,
) -> Vec<(DisplayDiffHunk, Option<Hitbox>)> {
- let buffer_snapshot = &snapshot.buffer_snapshot;
let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(snapshot);
let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(snapshot);
- let git_gutter_setting = ProjectSettings::get_global(cx)
- .git
- .git_gutter
- .unwrap_or_default();
+ let mut display_hunks = Vec::<(DisplayDiffHunk, Option<Hitbox>)>::new();
+ let folded_buffers = self.editor.read(cx).folded_buffers(cx);
- self.editor.update(cx, |editor, cx| {
- let expanded_hunks = &editor.diff_map.hunks;
- let expanded_hunks_start_ix = expanded_hunks
- .binary_search_by(|hunk| {
- hunk.hunk_range
- .end
- .cmp(&anchor_range.start, &buffer_snapshot)
- .then(Ordering::Less)
- })
- .unwrap_err();
- let mut expanded_hunks = expanded_hunks[expanded_hunks_start_ix..].iter().peekable();
+ for hunk in snapshot
+ .buffer_snapshot
+ .diff_hunks_in_range(buffer_start..buffer_end)
+ {
+ if folded_buffers.contains(&hunk.buffer_id) {
+ continue;
+ }
- let mut display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)> = editor
- .diff_map
- .snapshot
- .diff_hunks_in_range(buffer_start..buffer_end, &buffer_snapshot)
- .filter_map(|hunk| {
- let display_hunk = diff_hunk_to_display(&hunk, snapshot);
+ let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
+ let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
- if let DisplayDiffHunk::Unfolded {
- multi_buffer_range,
- status,
- ..
- } = &display_hunk
- {
- let mut is_expanded = false;
- while let Some(expanded_hunk) = expanded_hunks.peek() {
- match expanded_hunk
- .hunk_range
- .start
- .cmp(&multi_buffer_range.start, &buffer_snapshot)
- {
- Ordering::Less => {
- expanded_hunks.next();
- }
- Ordering::Equal => {
- is_expanded = true;
- break;
- }
- Ordering::Greater => {
- break;
- }
- }
- }
- match status {
- DiffHunkStatus::Added => {}
- DiffHunkStatus::Modified => {}
- DiffHunkStatus::Removed => {
- if is_expanded {
- return None;
- }
- }
- }
- }
+ let hunk_display_start = snapshot.point_to_display_point(hunk_start_point, Bias::Left);
+ let hunk_display_end = snapshot.point_to_display_point(hunk_end_point, Bias::Right);
- Some(display_hunk)
- })
- .dedup()
- .map(|hunk| (hunk, None))
- .collect();
+ let display_hunk = if hunk_display_start.column() != 0 || hunk_display_end.column() != 0
+ {
+ DisplayDiffHunk::Folded {
+ display_row: hunk_display_start.row(),
+ }
+ } else {
+ DisplayDiffHunk::Unfolded {
+ status: hunk.status(),
+ diff_base_byte_range: hunk.diff_base_byte_range,
+ display_row_range: hunk_display_start.row()..hunk_display_end.row(),
+ multi_buffer_range: Anchor::range_in_buffer(
+ hunk.excerpt_id,
+ hunk.buffer_id,
+ hunk.buffer_range,
+ ),
+ }
+ };
- if let GitGutterSetting::TrackedFiles = git_gutter_setting {
- for (hunk, hitbox) in &mut display_hunks {
- if let DisplayDiffHunk::Unfolded { .. } = hunk {
- let hunk_bounds = Self::diff_hunk_bounds(
- snapshot,
- line_height,
- gutter_hitbox.bounds,
- &hunk,
- );
- *hitbox = Some(cx.insert_hitbox(hunk_bounds, true));
- };
+ display_hunks.push((display_hunk, None));
+ }
+
+ let git_gutter_setting = ProjectSettings::get_global(cx)
+ .git
+ .git_gutter
+ .unwrap_or_default();
+ if let GitGutterSetting::TrackedFiles = git_gutter_setting {
+ for (hunk, hitbox) in &mut display_hunks {
+ if matches!(hunk, DisplayDiffHunk::Unfolded { .. }) {
+ let hunk_bounds =
+ Self::diff_hunk_bounds(snapshot, line_height, gutter_hitbox.bounds, hunk);
+ *hitbox = Some(cx.insert_hitbox(hunk_bounds, true));
}
}
+ }
- display_hunks
- })
+ display_hunks
}
#[allow(clippy::too_many_arguments)]
fn layout_inline_blame(
&self,
display_row: DisplayRow,
- display_snapshot: &DisplaySnapshot,
+ row_info: &RowInfo,
line_layout: &LineWithInvisibles,
crease_trailer: Option<&CreaseTrailerLayout>,
em_width: Pixels,
@@ -1615,9 +1597,6 @@ impl EditorElement {
.as_ref()
.map(|(w, _)| w.clone());
- let display_point = DisplayPoint::new(display_row, 0);
- let buffer_row = MultiBufferRow(display_point.to_point(display_snapshot).row);
-
let editor = self.editor.read(cx);
let blame = editor.blame.clone()?;
let padding = {
@@ -1641,7 +1620,7 @@ impl EditorElement {
let blame_entry = blame
.update(cx, |blame, cx| {
- blame.blame_for_rows([Some(buffer_row)], cx).next()
+ blame.blame_for_rows(&[*row_info], cx).next()
})
.flatten()?;
@@ -1680,7 +1659,7 @@ impl EditorElement {
#[allow(clippy::too_many_arguments)]
fn layout_blame_entries(
&self,
- buffer_rows: impl Iterator<Item = Option<MultiBufferRow>>,
+ buffer_rows: &[RowInfo],
em_width: Pixels,
scroll_position: gpui::Point<f32>,
line_height: Pixels,
@@ -1776,7 +1755,7 @@ impl EditorElement {
let start_x = content_origin.x + total_width - scroll_pixel_position.x;
if start_x >= text_origin.x {
let (offset_y, length) = Self::calculate_indent_guide_bounds(
- indent_guide.multibuffer_row_range.clone(),
+ indent_guide.start_row..indent_guide.end_row,
line_height,
snapshot,
);
@@ -1910,7 +1889,7 @@ impl EditorElement {
.buffer_snapshot
.buffer_line_for_row(multibuffer_row)
.map(|(buffer_snapshot, _)| buffer_snapshot.remote_id())
- .map(|buffer_id| editor.buffer_folded(buffer_id, cx))
+ .map(|buffer_id| editor.is_buffer_folded(buffer_id, cx))
.unwrap_or(false);
if buffer_folded {
return None;
@@ -2017,7 +1996,7 @@ impl EditorElement {
let end = rows.end.max(relative_to);
let buffer_rows = snapshot
- .buffer_rows(start)
+ .row_infos(start)
.take(1 + end.minus(start) as usize)
.collect::<Vec<_>>();
@@ -2025,7 +2004,7 @@ impl EditorElement {
let mut delta = 1;
let mut i = head_idx + 1;
while i < buffer_rows.len() as u32 {
- if buffer_rows[i as usize].is_some() {
+ if buffer_rows[i as usize].buffer_row.is_some() {
if rows.contains(&DisplayRow(i + start.0)) {
relative_rows.insert(DisplayRow(i + start.0), delta);
}
@@ -2035,13 +2014,13 @@ impl EditorElement {
}
delta = 1;
i = head_idx.min(buffer_rows.len() as u32 - 1);
- while i > 0 && buffer_rows[i as usize].is_none() {
+ while i > 0 && buffer_rows[i as usize].buffer_row.is_none() {
i -= 1;
}
while i > 0 {
i -= 1;
- if buffer_rows[i as usize].is_some() {
+ if buffer_rows[i as usize].buffer_row.is_some() {
if rows.contains(&DisplayRow(i + start.0)) {
relative_rows.insert(DisplayRow(i + start.0), delta);
}
@@ -2060,7 +2039,7 @@ impl EditorElement {
line_height: Pixels,
scroll_position: gpui::Point<f32>,
rows: Range<DisplayRow>,
- buffer_rows: impl Iterator<Item = Option<MultiBufferRow>>,
+ buffer_rows: &[RowInfo],
newest_selection_head: Option<DisplayPoint>,
snapshot: &EditorSnapshot,
cx: &mut WindowContext,
@@ -2100,15 +2079,17 @@ impl EditorElement {
let line_numbers = buffer_rows
.into_iter()
.enumerate()
- .flat_map(|(ix, buffer_row)| {
- let buffer_row = buffer_row?;
- line_number.clear();
+ .flat_map(|(ix, row_info)| {
let display_row = DisplayRow(rows.start.0 + ix as u32);
- let non_relative_number = buffer_row.0 + 1;
+ line_number.clear();
+ let non_relative_number = row_info.buffer_row? + 1;
let number = relative_rows
.get(&display_row)
.unwrap_or(&non_relative_number);
write!(&mut line_number, "{number}").unwrap();
+ if row_info.diff_status == Some(DiffHunkStatus::Removed) {
+ return None;
+ }
let color = cx.theme().colors().editor_line_number;
let shaped_line = self
@@ -2152,7 +2133,7 @@ impl EditorElement {
fn layout_crease_toggles(
&self,
rows: Range<DisplayRow>,
- buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
+ row_infos: &[RowInfo],
active_rows: &BTreeMap<DisplayRow, bool>,
snapshot: &EditorSnapshot,
cx: &mut WindowContext,
@@ -2161,22 +2142,15 @@ impl EditorElement {
&& snapshot.mode == EditorMode::Full
&& self.editor.read(cx).is_singleton(cx);
if include_fold_statuses {
- buffer_rows
+ row_infos
.into_iter()
.enumerate()
- .map(|(ix, row)| {
- if let Some(multibuffer_row) = row {
- let display_row = DisplayRow(rows.start.0 + ix as u32);
- let active = active_rows.contains_key(&display_row);
- snapshot.render_crease_toggle(
- multibuffer_row,
- active,
- self.editor.clone(),
- cx,
- )
- } else {
- None
- }
+ .map(|(ix, info)| {
+ let row = info.multibuffer_row?;
+ let display_row = DisplayRow(rows.start.0 + ix as u32);
+ let active = active_rows.contains_key(&display_row);
+
+ snapshot.render_crease_toggle(row, active, self.editor.clone(), cx)
})
.collect()
} else {
@@ -2186,15 +2160,15 @@ impl EditorElement {
fn layout_crease_trailers(
&self,
- buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
+ buffer_rows: impl IntoIterator<Item = RowInfo>,
snapshot: &EditorSnapshot,
cx: &mut WindowContext,
) -> Vec<Option<AnyElement>> {
buffer_rows
.into_iter()
- .map(|row| {
- if let Some(multibuffer_row) = row {
- snapshot.render_crease_trailer(multibuffer_row, cx)
+ .map(|row_info| {
+ if let Some(row) = row_info.multibuffer_row {
+ snapshot.render_crease_trailer(row, cx)
} else {
None
}
@@ -3687,6 +3661,76 @@ impl EditorElement {
}
}
+ #[allow(clippy::too_many_arguments)]
+ fn layout_diff_hunk_controls(
+ &self,
+ row_range: Range<DisplayRow>,
+ row_infos: &[RowInfo],
+ text_hitbox: &Hitbox,
+ position_map: &PositionMap,
+ newest_cursor_position: Option<DisplayPoint>,
+ line_height: Pixels,
+ scroll_pixel_position: gpui::Point<Pixels>,
+ display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
+ editor: View<Editor>,
+ cx: &mut WindowContext,
+ ) -> Vec<AnyElement> {
+ let point_for_position =
+ position_map.point_for_position(text_hitbox.bounds, cx.mouse_position());
+
+ let mut controls = vec![];
+
+ let active_positions = [
+ Some(point_for_position.previous_valid),
+ newest_cursor_position,
+ ];
+
+ for (hunk, _) in display_hunks {
+ if let DisplayDiffHunk::Unfolded {
+ display_row_range,
+ multi_buffer_range,
+ status,
+ ..
+ } = &hunk
+ {
+ if display_row_range.start < row_range.start
+ || display_row_range.start >= row_range.end
+ {
+ continue;
+ }
+ let row_ix = (display_row_range.start - row_range.start).0 as usize;
+ if row_infos[row_ix].diff_status.is_none() {
+ continue;
+ }
+ if row_infos[row_ix].diff_status == Some(DiffHunkStatus::Added)
+ && *status != DiffHunkStatus::Added
+ {
+ continue;
+ }
+ if active_positions
+ .iter()
+ .any(|p| p.map_or(false, |p| display_row_range.contains(&p.row())))
+ {
+ let y = display_row_range.start.as_f32() * line_height
+ + text_hitbox.bounds.top()
+ - scroll_pixel_position.y;
+ let x = text_hitbox.bounds.right() - px(100.);
+
+ let mut element =
+ diff_hunk_controls(multi_buffer_range.clone(), line_height, &editor, cx);
+ element.prepaint_as_root(
+ gpui::Point::new(x, y),
+ size(px(100.0), line_height).into(),
+ cx,
+ );
+ controls.push(element);
+ }
+ }
+ }
+
+ controls
+ }
+
#[allow(clippy::too_many_arguments)]
fn layout_signature_help(
&self,
@@ -4047,31 +4091,38 @@ impl EditorElement {
Corners::all(px(0.)),
))
}
- DisplayDiffHunk::Unfolded { status, .. } => {
- hitbox.as_ref().map(|hunk_hitbox| match status {
- DiffHunkStatus::Added => (
- hunk_hitbox.bounds,
- cx.theme().status().created,
- Corners::all(px(0.)),
- ),
- DiffHunkStatus::Modified => (
- hunk_hitbox.bounds,
- cx.theme().status().modified,
- Corners::all(px(0.)),
- ),
- DiffHunkStatus::Removed => (
- Bounds::new(
- point(
- hunk_hitbox.origin.x - hunk_hitbox.size.width,
- hunk_hitbox.origin.y,
- ),
- size(hunk_hitbox.size.width * px(2.), hunk_hitbox.size.height),
+ DisplayDiffHunk::Unfolded {
+ status,
+ display_row_range,
+ ..
+ } => hitbox.as_ref().map(|hunk_hitbox| match status {
+ DiffHunkStatus::Added => (
+ hunk_hitbox.bounds,
+ cx.theme().status().created,
+ Corners::all(px(0.)),
+ ),
+ DiffHunkStatus::Modified => (
+ hunk_hitbox.bounds,
+ cx.theme().status().modified,
+ Corners::all(px(0.)),
+ ),
+ DiffHunkStatus::Removed if !display_row_range.is_empty() => (
+ hunk_hitbox.bounds,
+ cx.theme().status().deleted,
+ Corners::all(px(0.)),
+ ),
+ DiffHunkStatus::Removed => (
+ Bounds::new(
+ point(
+ hunk_hitbox.origin.x - hunk_hitbox.size.width,
+ hunk_hitbox.origin.y,
),
- cx.theme().status().deleted,
- Corners::all(1. * line_height),
+ size(hunk_hitbox.size.width * px(2.), hunk_hitbox.size.height),
),
- })
- }
+ cx.theme().status().deleted,
+ Corners::all(1. * line_height),
+ ),
+ }),
};
if let Some((hunk_bounds, background_color, corner_radii)) = hunk_to_paint {
@@ -4110,8 +4161,19 @@ impl EditorElement {
display_row_range,
status,
..
- } => match status {
- DiffHunkStatus::Added | DiffHunkStatus::Modified => {
+ } => {
+ if *status == DiffHunkStatus::Removed && display_row_range.is_empty() {
+ let row = display_row_range.start;
+
+ let offset = line_height / 2.;
+ let start_y = row.as_f32() * line_height - offset - scroll_top;
+ let end_y = start_y + line_height;
+
+ let width = (0.35 * line_height).floor();
+ let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
+ let highlight_size = size(width, end_y - start_y);
+ Bounds::new(highlight_origin, highlight_size)
+ } else {
let start_row = display_row_range.start;
let end_row = display_row_range.end;
// If we're in a multibuffer, row range span might include an
@@ -4139,19 +4201,7 @@ impl EditorElement {
let highlight_size = size(width, end_y - start_y);
Bounds::new(highlight_origin, highlight_size)
}
- DiffHunkStatus::Removed => {
- let row = display_row_range.start;
-
- let offset = line_height / 2.;
- let start_y = row.as_f32() * line_height - offset - scroll_top;
- let end_y = start_y + line_height;
-
- let width = (0.35 * line_height).floor();
- let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
- let highlight_size = size(width, end_y - start_y);
- Bounds::new(highlight_origin, highlight_size)
- }
- },
+ }
}
}
@@ -4266,6 +4316,7 @@ impl EditorElement {
self.paint_redactions(layout, cx);
self.paint_cursors(layout, cx);
self.paint_inline_blame(layout, cx);
+ self.paint_diff_hunk_controls(layout, cx);
cx.with_element_namespace("crease_trailers", |cx| {
for trailer in layout.crease_trailers.iter_mut().flatten() {
trailer.element.paint(cx);
@@ -4733,10 +4784,8 @@ impl EditorElement {
let max_point = snapshot.display_snapshot.buffer_snapshot.max_point();
let mut marker_quads = Vec::new();
if scrollbar_settings.git_diff {
- let marker_row_ranges = snapshot
- .diff_map
- .diff_hunks(&snapshot.buffer_snapshot)
- .map(|hunk| {
+ let marker_row_ranges =
+ snapshot.buffer_snapshot.diff_hunks().map(|hunk| {
let start_display_row =
MultiBufferPoint::new(hunk.row_range.start.0, 0)
.to_display_point(&snapshot.display_snapshot)
@@ -4748,7 +4797,7 @@ impl EditorElement {
if end_display_row != start_display_row {
end_display_row.0 -= 1;
}
- let color = match hunk_status(&hunk) {
+ let color = match &hunk.status() {
DiffHunkStatus::Added => theme.status().created,
DiffHunkStatus::Modified => theme.status().modified,
DiffHunkStatus::Removed => theme.status().deleted,
@@ -4804,11 +4853,7 @@ impl EditorElement {
if scrollbar_settings.diagnostics != ScrollbarDiagnostics::None {
let diagnostics = snapshot
.buffer_snapshot
- .diagnostics_in_range(Point::zero()..max_point, false)
- .map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
- diagnostic,
- range: range.to_point(&snapshot.buffer_snapshot),
- })
+ .diagnostics_in_range::<_, Point>(Point::zero()..max_point)
// Don't show diagnostics the user doesn't care about
.filter(|diagnostic| {
match (
@@ -4948,6 +4993,12 @@ impl EditorElement {
}
}
+ fn paint_diff_hunk_controls(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
+ for mut diff_hunk_control in layout.diff_hunk_controls.drain(..) {
+ diff_hunk_control.paint(cx);
+ }
+ }
+
fn paint_blocks(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
for mut block in layout.blocks.drain(..) {
block.element.paint(cx);
@@ -5033,12 +5084,7 @@ impl EditorElement {
});
}
- fn paint_mouse_listeners(
- &mut self,
- layout: &EditorLayout,
- hovered_hunk: Option<HoveredHunk>,
- cx: &mut WindowContext,
- ) {
+ fn paint_mouse_listeners(&mut self, layout: &EditorLayout, cx: &mut WindowContext) {
self.paint_scroll_wheel_listener(layout, cx);
cx.on_mouse_event({
@@ -5046,6 +5092,26 @@ impl EditorElement {
let editor = self.editor.clone();
let text_hitbox = layout.text_hitbox.clone();
let gutter_hitbox = layout.gutter_hitbox.clone();
+ let hovered_hunk =
+ layout
+ .display_hunks
+ .iter()
+ .find_map(|(hunk, hunk_hitbox)| match hunk {
+ DisplayDiffHunk::Folded { .. } => None,
+ DisplayDiffHunk::Unfolded {
+ multi_buffer_range, ..
+ } => {
+ if hunk_hitbox
+ .as_ref()
+ .map(|hitbox| hitbox.is_hovered(cx))
+ .unwrap_or(false)
+ {
+ Some(multi_buffer_range.clone())
+ } else {
+ None
+ }
+ }
+ });
let line_numbers = layout.line_numbers.clone();
move |event: &MouseDownEvent, phase, cx| {
@@ -6232,12 +6298,15 @@ impl Element for EditorElement {
);
let end_row = DisplayRow(end_row);
- let buffer_rows = snapshot
- .buffer_rows(start_row)
+ let row_infos = snapshot
+ .row_infos(start_row)
.take((start_row..end_row).len())
- .collect::<Vec<_>>();
- let is_row_soft_wrapped =
- |row| buffer_rows.get(row).copied().flatten().is_none();
+ .collect::<Vec<RowInfo>>();
+ let is_row_soft_wrapped = |row: usize| {
+ row_infos
+ .get(row)
+ .map_or(true, |info| info.buffer_row.is_none())
+ };
let start_anchor = if start_row == Default::default() {
Anchor::min()
@@ -6254,9 +6323,21 @@ impl Element for EditorElement {
)
};
- let highlighted_rows = self
+ let mut highlighted_rows = self
.editor
.update(cx, |editor, cx| editor.highlighted_display_rows(cx));
+
+ for (ix, row_info) in row_infos.iter().enumerate() {
+ let color = match row_info.diff_status {
+ Some(DiffHunkStatus::Added) => style.status.created_background,
+ Some(DiffHunkStatus::Removed) => style.status.deleted_background,
+ _ => continue,
+ };
+ highlighted_rows
+ .entry(start_row + DisplayRow(ix as u32))
+ .or_insert(color);
+ }
+
let highlighted_ranges = self.editor.read(cx).background_highlights_in_range(
start_anchor..end_anchor,
&snapshot.display_snapshot,
@@ -6288,7 +6369,7 @@ impl Element for EditorElement {
for selection in all_selections {
for buffer_id in snapshot
.buffer_snapshot
- .buffer_ids_in_selected_rows(selection)
+ .buffer_ids_for_range(selection.range())
{
if selected_buffer_ids.last() != Some(&buffer_id) {
selected_buffer_ids.push(buffer_id);
@@ -6323,7 +6404,7 @@ impl Element for EditorElement {
line_height,
scroll_position,
start_row..end_row,
- buffer_rows.iter().copied(),
+ &row_infos,
newest_selection_head,
&snapshot,
cx,
@@ -6332,21 +6413,20 @@ impl Element for EditorElement {
let mut crease_toggles = cx.with_element_namespace("crease_toggles", |cx| {
self.layout_crease_toggles(
start_row..end_row,
- buffer_rows.iter().copied(),
+ &row_infos,
&active_rows,
&snapshot,
cx,
)
});
let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| {
- self.layout_crease_trailers(buffer_rows.iter().copied(), &snapshot, cx)
+ self.layout_crease_trailers(row_infos.iter().copied(), &snapshot, cx)
});
- let display_hunks = self.layout_gutter_git_hunks(
+ let display_hunks = self.layout_gutter_diff_hunks(
line_height,
&gutter_hitbox,
start_row..end_row,
- start_anchor..end_anchor,
&snapshot,
cx,
);
@@ -6504,11 +6584,12 @@ impl Element for EditorElement {
let display_row = newest_selection_head.row();
if (start_row..end_row).contains(&display_row) {
let line_ix = display_row.minus(start_row) as usize;
+ let row_info = &row_infos[line_ix];
let line_layout = &line_layouts[line_ix];
let crease_trailer_layout = crease_trailers[line_ix].as_ref();
inline_blame = self.layout_inline_blame(
display_row,
- &snapshot.display_snapshot,
+ row_info,
line_layout,
crease_trailer_layout,
em_width,
@@ -6521,7 +6602,7 @@ impl Element for EditorElement {
}
let blamed_display_rows = self.layout_blame_entries(
- buffer_rows.into_iter(),
+ &row_infos,
em_width,
scroll_position,
line_height,
@@ -6612,22 +6693,6 @@ impl Element for EditorElement {
let gutter_settings = EditorSettings::get_global(cx).gutter;
- let expanded_add_hunks_by_rows = self.editor.update(cx, |editor, _| {
- editor
- .diff_map
- .hunks(false)
- .filter(|hunk| hunk.status == DiffHunkStatus::Added)
- .map(|expanded_hunk| {
- let start_row = expanded_hunk
- .hunk_range
- .start
- .to_display_point(&snapshot)
- .row();
- (start_row, expanded_hunk.clone())
- })
- .collect::<HashMap<_, _>>()
- });
-
let rows_with_hunk_bounds = display_hunks
.iter()
.filter_map(|(hunk, hitbox)| Some((hunk, hitbox.as_ref()?.bounds)))
@@ -6670,38 +6735,32 @@ impl Element for EditorElement {
if show_code_actions {
let newest_selection_point =
newest_selection_head.to_point(&snapshot.display_snapshot);
- let newest_selection_display_row =
- newest_selection_point.to_display_point(&snapshot).row();
- if !expanded_add_hunks_by_rows
- .contains_key(&newest_selection_display_row)
+ if !snapshot
+ .is_line_folded(MultiBufferRow(newest_selection_point.row))
{
- if !snapshot
- .is_line_folded(MultiBufferRow(newest_selection_point.row))
- {
- let buffer = snapshot.buffer_snapshot.buffer_line_for_row(
- MultiBufferRow(newest_selection_point.row),
- );
- if let Some((buffer, range)) = buffer {
- let buffer_id = buffer.remote_id();
- let row = range.start.row;
- let has_test_indicator = self
- .editor
- .read(cx)
- .tasks
- .contains_key(&(buffer_id, row));
-
- if !has_test_indicator {
- code_actions_indicator = self
- .layout_code_actions_indicator(
- line_height,
- newest_selection_head,
- scroll_pixel_position,
- &gutter_dimensions,
- &gutter_hitbox,
- &rows_with_hunk_bounds,
- cx,
- );
- }
+ let buffer = snapshot.buffer_snapshot.buffer_line_for_row(
+ MultiBufferRow(newest_selection_point.row),
+ );
+ if let Some((buffer, range)) = buffer {
+ let buffer_id = buffer.remote_id();
+ let row = range.start.row;
+ let has_test_indicator = self
+ .editor
+ .read(cx)
+ .tasks
+ .contains_key(&(buffer_id, row));
+
+ if !has_test_indicator {
+ code_actions_indicator = self
+ .layout_code_actions_indicator(
+ line_height,
+ newest_selection_head,
+ scroll_pixel_position,
+ &gutter_dimensions,
+ &gutter_hitbox,
+ &rows_with_hunk_bounds,
+ cx,
+ );
}
}
}
@@ -6816,18 +6875,35 @@ impl Element for EditorElement {
)
.unwrap();
+ let mode = snapshot.mode;
+
+ let position_map = Rc::new(PositionMap {
+ size: bounds.size,
+ scroll_pixel_position,
+ scroll_max,
+ line_layouts,
+ line_height,
+ em_width,
+ em_advance,
+ snapshot,
+ });
+
+ let hunk_controls = self.layout_diff_hunk_controls(
+ start_row..end_row,
+ &row_infos,
+ &text_hitbox,
+ &position_map,
+ newest_selection_head,
+ line_height,
+ scroll_pixel_position,
+ &display_hunks,
+ self.editor.clone(),
+ cx,
+ );
+
EditorLayout {
- mode: snapshot.mode,
- position_map: Rc::new(PositionMap {
- size: bounds.size,
- scroll_pixel_position,
- scroll_max,
- line_layouts,
- line_height,
- em_width,
- em_advance,
- snapshot,
- }),
+ mode,
+ position_map,
visible_display_row_range: start_row..end_row,
wrap_guides,
indent_guides,
@@ -6851,6 +6927,7 @@ impl Element for EditorElement {
visible_cursors,
selections,
inline_completion_popover,
+ diff_hunk_controls: hunk_controls,
mouse_context_menu,
test_indicators,
code_actions_indicator,
@@ -6889,37 +6966,11 @@ impl Element for EditorElement {
line_height: Some(self.style.text.line_height),
..Default::default()
};
- let hovered_hunk = layout
- .display_hunks
- .iter()
- .find_map(|(hunk, hunk_hitbox)| match hunk {
- DisplayDiffHunk::Folded { .. } => None,
- DisplayDiffHunk::Unfolded {
- diff_base_byte_range,
- multi_buffer_range,
- status,
- ..
- } => {
- if hunk_hitbox
- .as_ref()
- .map(|hitbox| hitbox.is_hovered(cx))
- .unwrap_or(false)
- {
- Some(HoveredHunk {
- status: *status,
- multi_buffer_range: multi_buffer_range.clone(),
- diff_base_byte_range: diff_base_byte_range.clone(),
- })
- } else {
- None
- }
- }
- });
let rem_size = self.rem_size(cx);
cx.with_rem_size(rem_size, |cx| {
cx.with_text_style(Some(text_style), |cx| {
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
- self.paint_mouse_listeners(layout, hovered_hunk, cx);
+ self.paint_mouse_listeners(layout, cx);
self.paint_background(layout, cx);
self.paint_indent_guides(layout, cx);
@@ -1,5 +1,3 @@
-use std::{sync::Arc, time::Duration};
-
use anyhow::Result;
use collections::HashMap;
use git::{
@@ -9,9 +7,10 @@ use git::{
use gpui::{AppContext, Model, ModelContext, Subscription, Task};
use http_client::HttpClient;
use language::{markdown, Bias, Buffer, BufferSnapshot, Edit, LanguageRegistry, ParsedMarkdown};
-use multi_buffer::MultiBufferRow;
+use multi_buffer::RowInfo;
use project::{Project, ProjectItem};
use smallvec::SmallVec;
+use std::{sync::Arc, time::Duration};
use sum_tree::SumTree;
use url::Url;
@@ -194,15 +193,15 @@ impl GitBlame {
pub fn blame_for_rows<'a>(
&'a mut self,
- rows: impl 'a + IntoIterator<Item = Option<MultiBufferRow>>,
+ rows: &'a [RowInfo],
cx: &AppContext,
) -> impl 'a + Iterator<Item = Option<BlameEntry>> {
self.sync(cx);
let mut cursor = self.entries.cursor::<u32>(&());
- rows.into_iter().map(move |row| {
- let row = row?;
- cursor.seek_forward(&row.0, Bias::Right, &());
+ rows.into_iter().map(move |info| {
+ let row = info.buffer_row?;
+ cursor.seek_forward(&row, Bias::Right, &());
cursor.item()?.blame.clone()
})
}
@@ -563,15 +562,38 @@ mod tests {
use unindent::Unindent as _;
use util::RandomCharIter;
- macro_rules! assert_blame_rows {
- ($blame:expr, $rows:expr, $expected:expr, $cx:expr) => {
- assert_eq!(
- $blame
- .blame_for_rows($rows.map(MultiBufferRow).map(Some), $cx)
- .collect::<Vec<_>>(),
- $expected
- );
- };
+ // macro_rules! assert_blame_rows {
+ // ($blame:expr, $rows:expr, $expected:expr, $cx:expr) => {
+ // assert_eq!(
+ // $blame
+ // .blame_for_rows($rows.map(MultiBufferRow).map(Some), $cx)
+ // .collect::<Vec<_>>(),
+ // $expected
+ // );
+ // };
+ // }
+
+ #[track_caller]
+ fn assert_blame_rows(
+ blame: &mut GitBlame,
+ rows: Range<u32>,
+ expected: Vec<Option<BlameEntry>>,
+ cx: &mut ModelContext<GitBlame>,
+ ) {
+ assert_eq!(
+ blame
+ .blame_for_rows(
+ &rows
+ .map(|row| RowInfo {
+ buffer_row: Some(row),
+ ..Default::default()
+ })
+ .collect::<Vec<_>>(),
+ cx
+ )
+ .collect::<Vec<_>>(),
+ expected
+ );
}
fn init_test(cx: &mut gpui::TestAppContext) {
@@ -634,7 +656,15 @@ mod tests {
blame.update(cx, |blame, cx| {
assert_eq!(
blame
- .blame_for_rows((0..1).map(MultiBufferRow).map(Some), cx)
+ .blame_for_rows(
+ &(0..1)
+ .map(|row| RowInfo {
+ buffer_row: Some(row),
+ ..Default::default()
+ })
+ .collect::<Vec<_>>(),
+ cx
+ )
.collect::<Vec<_>>(),
vec![None]
);
@@ -698,7 +728,15 @@ mod tests {
// All lines
assert_eq!(
blame
- .blame_for_rows((0..8).map(MultiBufferRow).map(Some), cx)
+ .blame_for_rows(
+ &(0..8)
+ .map(|buffer_row| RowInfo {
+ buffer_row: Some(buffer_row),
+ ..Default::default()
+ })
+ .collect::<Vec<_>>(),
+ cx
+ )
.collect::<Vec<_>>(),
vec![
Some(blame_entry("1b1b1b", 0..1)),
@@ -714,7 +752,15 @@ mod tests {
// Subset of lines
assert_eq!(
blame
- .blame_for_rows((1..4).map(MultiBufferRow).map(Some), cx)
+ .blame_for_rows(
+ &(1..4)
+ .map(|buffer_row| RowInfo {
+ buffer_row: Some(buffer_row),
+ ..Default::default()
+ })
+ .collect::<Vec<_>>(),
+ cx
+ )
.collect::<Vec<_>>(),
vec![
Some(blame_entry("0d0d0d", 1..2)),
@@ -725,7 +771,17 @@ mod tests {
// Subset of lines, with some not displayed
assert_eq!(
blame
- .blame_for_rows(vec![Some(MultiBufferRow(1)), None, None], cx)
+ .blame_for_rows(
+ &[
+ RowInfo {
+ buffer_row: Some(1),
+ ..Default::default()
+ },
+ Default::default(),
+ Default::default(),
+ ],
+ cx
+ )
.collect::<Vec<_>>(),
vec![Some(blame_entry("0d0d0d", 1..2)), None, None]
);
@@ -777,16 +833,16 @@ mod tests {
git_blame.update(cx, |blame, cx| {
// Sanity check before edits: make sure that we get the same blame entry for all
// lines.
- assert_blame_rows!(
+ assert_blame_rows(
blame,
- (0..4),
+ 0..4,
vec![
Some(blame_entry("1b1b1b", 0..4)),
Some(blame_entry("1b1b1b", 0..4)),
Some(blame_entry("1b1b1b", 0..4)),
Some(blame_entry("1b1b1b", 0..4)),
],
- cx
+ cx,
);
});
@@ -795,11 +851,11 @@ mod tests {
buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "X")], None, cx);
});
git_blame.update(cx, |blame, cx| {
- assert_blame_rows!(
+ assert_blame_rows(
blame,
- (0..2),
+ 0..2,
vec![None, Some(blame_entry("1b1b1b", 0..4))],
- cx
+ cx,
);
});
// Modify a single line, in the middle of the line
@@ -807,21 +863,21 @@ mod tests {
buffer.edit([(Point::new(1, 2)..Point::new(1, 2), "X")], None, cx);
});
git_blame.update(cx, |blame, cx| {
- assert_blame_rows!(
+ assert_blame_rows(
blame,
- (1..4),
+ 1..4,
vec![
None,
Some(blame_entry("1b1b1b", 0..4)),
- Some(blame_entry("1b1b1b", 0..4))
+ Some(blame_entry("1b1b1b", 0..4)),
],
- cx
+ cx,
);
});
// Before we insert a newline at the end, sanity check:
git_blame.update(cx, |blame, cx| {
- assert_blame_rows!(blame, (3..4), vec![Some(blame_entry("1b1b1b", 0..4))], cx);
+ assert_blame_rows(blame, 3..4, vec![Some(blame_entry("1b1b1b", 0..4))], cx);
});
// Insert a newline at the end
buffer.update(cx, |buffer, cx| {
@@ -829,17 +885,17 @@ mod tests {
});
// Only the new line is marked as edited:
git_blame.update(cx, |blame, cx| {
- assert_blame_rows!(
+ assert_blame_rows(
blame,
- (3..5),
+ 3..5,
vec![Some(blame_entry("1b1b1b", 0..4)), None],
- cx
+ cx,
);
});
// Before we insert a newline at the start, sanity check:
git_blame.update(cx, |blame, cx| {
- assert_blame_rows!(blame, (2..3), vec![Some(blame_entry("1b1b1b", 0..4)),], cx);
+ assert_blame_rows(blame, 2..3, vec![Some(blame_entry("1b1b1b", 0..4))], cx);
});
// Usage example
@@ -849,11 +905,11 @@ mod tests {
});
// Only the new line is marked as edited:
git_blame.update(cx, |blame, cx| {
- assert_blame_rows!(
+ assert_blame_rows(
blame,
- (2..4),
- vec![None, Some(blame_entry("1b1b1b", 0..4)),],
- cx
+ 2..4,
+ vec![None, Some(blame_entry("1b1b1b", 0..4))],
+ cx,
);
});
}
@@ -146,7 +146,7 @@ impl ProjectDiffEditor {
let editor = cx.new_view(|cx| {
let mut diff_display_editor =
Editor::for_multibuffer(excerpts.clone(), Some(project.clone()), true, cx);
- diff_display_editor.set_expand_all_diff_hunks();
+ diff_display_editor.set_expand_all_diff_hunks(cx);
diff_display_editor
});
@@ -310,9 +310,11 @@ impl ProjectDiffEditor {
.update(&mut cx, |project_diff_editor, cx| {
project_diff_editor.update_excerpts(id, new_changes, new_entry_order, cx);
project_diff_editor.editor.update(cx, |editor, cx| {
- for change_set in change_sets {
- editor.diff_map.add_change_set(change_set, cx)
- }
+ editor.buffer.update(cx, |buffer, cx| {
+ for change_set in change_sets {
+ buffer.add_change_set(change_set, cx)
+ }
+ });
});
})
.ok();
@@ -1105,6 +1107,8 @@ mod tests {
path::{Path, PathBuf},
};
+ use crate::test::editor_test_context::assert_state_with_diff;
+
use super::*;
// TODO finish
@@ -1183,19 +1187,13 @@ mod tests {
let change_set = cx.new_model(|cx| {
BufferChangeSet::new_with_base_text(
old_text.clone(),
- file_a_editor
- .buffer()
- .read(cx)
- .as_singleton()
- .unwrap()
- .read(cx)
- .text_snapshot(),
+ &file_a_editor.buffer().read(cx).as_singleton().unwrap(),
cx,
)
});
- file_a_editor
- .diff_map
- .add_change_set(change_set.clone(), cx);
+ file_a_editor.buffer.update(cx, |buffer, cx| {
+ buffer.add_change_set(change_set.clone(), cx)
+ });
project.update(cx, |project, cx| {
project.buffer_store().update(cx, |buffer_store, cx| {
buffer_store.set_change_set(
@@ -1225,15 +1223,17 @@ mod tests {
cx.executor()
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));
cx.run_until_parked();
-
- project_diff_editor.update(cx, |project_diff_editor, cx| {
- assert_eq!(
- // TODO assert it better: extract added text (based on the background changes) and deleted text (based on the deleted blocks added)
- project_diff_editor.editor.read(cx).text(cx),
- format!("{change}{old_text}"),
- "Should have a new change shown in the beginning, and the old text shown as deleted text afterwards"
- );
- });
+ let editor = project_diff_editor.update(cx, |view, _| view.editor.clone());
+
+ assert_state_with_diff(
+ &editor,
+ cx,
+ indoc::indoc! {
+ "
+ - This is file_a
+ + an edit after git addThis is file_aΛ",
+ },
+ );
}
fn init_test(cx: &mut gpui::TestAppContext) {
@@ -265,12 +265,9 @@ fn show_hover(
let local_diagnostic = snapshot
.buffer_snapshot
- .diagnostics_in_range(anchor..anchor, false)
+ .diagnostics_in_range::<_, usize>(anchor..anchor)
// Find the entry with the most specific range
- .min_by_key(|entry| {
- let range = entry.range.to_offset(&snapshot.buffer_snapshot);
- range.end - range.start
- });
+ .min_by_key(|entry| entry.range.len());
let diagnostic_popover = if let Some(local_diagnostic) = local_diagnostic {
let text = match local_diagnostic.diagnostic.source {
@@ -279,6 +276,15 @@ fn show_hover(
}
None => local_diagnostic.diagnostic.message.clone(),
};
+ let local_diagnostic = DiagnosticEntry {
+ diagnostic: local_diagnostic.diagnostic,
+ range: snapshot
+ .buffer_snapshot
+ .anchor_before(local_diagnostic.range.start)
+ ..snapshot
+ .buffer_snapshot
+ .anchor_after(local_diagnostic.range.end),
+ };
let mut border_color: Option<Hsla> = None;
let mut background_color: Option<Hsla> = None;
@@ -770,7 +776,7 @@ impl InfoPopover {
#[derive(Debug, Clone)]
pub struct DiagnosticPopover {
- local_diagnostic: DiagnosticEntry<Anchor>,
+ pub(crate) local_diagnostic: DiagnosticEntry<Anchor>,
parsed_content: Option<View<Markdown>>,
border_color: Option<Hsla>,
background_color: Option<Hsla>,
@@ -823,10 +829,6 @@ impl DiagnosticPopover {
diagnostic_div.into_any_element()
}
-
- pub fn group_id(&self) -> usize {
- self.local_diagnostic.diagnostic.group_id
- }
}
#[cfg(test)]
@@ -1,1505 +0,0 @@
-use collections::{HashMap, HashSet};
-use git::diff::DiffHunkStatus;
-use gpui::{
- Action, AppContext, Corner, CursorStyle, Hsla, Model, MouseButton, Subscription, Task, View,
-};
-use language::{Buffer, BufferId, Point};
-use multi_buffer::{
- Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow,
- MultiBufferSnapshot, ToOffset, ToPoint,
-};
-use project::buffer_store::BufferChangeSet;
-use std::{ops::Range, sync::Arc};
-use sum_tree::TreeMap;
-use text::OffsetRangeExt;
-use ui::{
- prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement,
- ParentElement, PopoverMenu, Styled, Tooltip, ViewContext, VisualContext,
-};
-use util::RangeExt;
-use workspace::Item;
-
-use crate::{
- editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyAllDiffHunks,
- ApplyDiffHunk, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight,
- DisplayRow, DisplaySnapshot, Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk,
- RevertFile, RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
-};
-
-#[derive(Debug, Clone)]
-pub(super) struct HoveredHunk {
- pub multi_buffer_range: Range<Anchor>,
- pub status: DiffHunkStatus,
- pub diff_base_byte_range: Range<usize>,
-}
-
-#[derive(Default)]
-pub(super) struct DiffMap {
- pub(crate) hunks: Vec<ExpandedHunk>,
- pub(crate) diff_bases: HashMap<BufferId, DiffBaseState>,
- pub(crate) snapshot: DiffMapSnapshot,
- hunk_update_tasks: HashMap<Option<BufferId>, Task<()>>,
- expand_all: bool,
-}
-
-#[derive(Debug, Clone)]
-pub(super) struct ExpandedHunk {
- pub blocks: Vec<CustomBlockId>,
- pub hunk_range: Range<Anchor>,
- pub diff_base_byte_range: Range<usize>,
- pub status: DiffHunkStatus,
- pub folded: bool,
-}
-
-#[derive(Clone, Debug, Default)]
-pub(crate) struct DiffMapSnapshot(TreeMap<BufferId, git::diff::BufferDiff>);
-
-pub(crate) struct DiffBaseState {
- pub(crate) change_set: Model<BufferChangeSet>,
- pub(crate) last_version: Option<usize>,
- _subscription: Subscription,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum DisplayDiffHunk {
- Folded {
- display_row: DisplayRow,
- },
-
- Unfolded {
- diff_base_byte_range: Range<usize>,
- display_row_range: Range<DisplayRow>,
- multi_buffer_range: Range<Anchor>,
- status: DiffHunkStatus,
- },
-}
-
-impl DiffMap {
- pub fn snapshot(&self) -> DiffMapSnapshot {
- self.snapshot.clone()
- }
-
- pub fn add_change_set(
- &mut self,
- change_set: Model<BufferChangeSet>,
- cx: &mut ViewContext<Editor>,
- ) {
- let buffer_id = change_set.read(cx).buffer_id;
- self.snapshot
- .0
- .insert(buffer_id, change_set.read(cx).diff_to_buffer.clone());
- self.diff_bases.insert(
- buffer_id,
- DiffBaseState {
- last_version: None,
- _subscription: cx.observe(&change_set, move |editor, change_set, cx| {
- editor
- .diff_map
- .snapshot
- .0
- .insert(buffer_id, change_set.read(cx).diff_to_buffer.clone());
- Editor::sync_expanded_diff_hunks(&mut editor.diff_map, buffer_id, cx);
- }),
- change_set,
- },
- );
- Editor::sync_expanded_diff_hunks(self, buffer_id, cx);
- }
-
- pub fn hunks(&self, include_folded: bool) -> impl Iterator<Item = &ExpandedHunk> {
- self.hunks
- .iter()
- .filter(move |hunk| include_folded || !hunk.folded)
- }
-}
-
-impl DiffMapSnapshot {
- pub fn is_empty(&self) -> bool {
- self.0.values().all(|diff| diff.is_empty())
- }
-
- pub fn diff_hunks<'a>(
- &'a self,
- buffer_snapshot: &'a MultiBufferSnapshot,
- ) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
- self.diff_hunks_in_range(0..buffer_snapshot.len(), buffer_snapshot)
- }
-
- pub fn diff_hunks_in_range<'a, T: ToOffset>(
- &'a self,
- range: Range<T>,
- buffer_snapshot: &'a MultiBufferSnapshot,
- ) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
- let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot);
- buffer_snapshot
- .excerpts_for_range(range.clone())
- .filter_map(move |excerpt| {
- let buffer = excerpt.buffer();
- let buffer_id = buffer.remote_id();
- let diff = self.0.get(&buffer_id)?;
- let buffer_range = excerpt.map_range_to_buffer(range.clone());
- let buffer_range =
- buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end);
- Some(
- diff.hunks_intersecting_range(buffer_range, excerpt.buffer())
- .map(move |hunk| {
- let start =
- excerpt.map_point_from_buffer(Point::new(hunk.row_range.start, 0));
- let end =
- excerpt.map_point_from_buffer(Point::new(hunk.row_range.end, 0));
- MultiBufferDiffHunk {
- row_range: MultiBufferRow(start.row)..MultiBufferRow(end.row),
- buffer_id,
- buffer_range: hunk.buffer_range.clone(),
- diff_base_byte_range: hunk.diff_base_byte_range.clone(),
- }
- }),
- )
- })
- .flatten()
- }
-
- pub fn diff_hunks_in_range_rev<'a, T: ToOffset>(
- &'a self,
- range: Range<T>,
- buffer_snapshot: &'a MultiBufferSnapshot,
- ) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
- let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot);
- buffer_snapshot
- .excerpts_for_range_rev(range.clone())
- .filter_map(move |excerpt| {
- let buffer = excerpt.buffer();
- let buffer_id = buffer.remote_id();
- let diff = self.0.get(&buffer_id)?;
- let buffer_range = excerpt.map_range_to_buffer(range.clone());
- let buffer_range =
- buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end);
- Some(
- diff.hunks_intersecting_range_rev(buffer_range, excerpt.buffer())
- .map(move |hunk| {
- let start_row = excerpt
- .map_point_from_buffer(Point::new(hunk.row_range.start, 0))
- .row;
- let end_row = excerpt
- .map_point_from_buffer(Point::new(hunk.row_range.end, 0))
- .row;
- MultiBufferDiffHunk {
- row_range: MultiBufferRow(start_row)..MultiBufferRow(end_row),
- buffer_id,
- buffer_range: hunk.buffer_range.clone(),
- diff_base_byte_range: hunk.diff_base_byte_range.clone(),
- }
- }),
- )
- })
- .flatten()
- }
-}
-
-impl Editor {
- pub fn set_expand_all_diff_hunks(&mut self) {
- self.diff_map.expand_all = true;
- }
-
- pub(super) fn toggle_hovered_hunk(
- &mut self,
- hovered_hunk: &HoveredHunk,
- cx: &mut ViewContext<Editor>,
- ) {
- let editor_snapshot = self.snapshot(cx);
- if let Some(diff_hunk) = to_diff_hunk(hovered_hunk, &editor_snapshot.buffer_snapshot) {
- self.toggle_hunks_expanded(vec![diff_hunk], cx);
- self.change_selections(None, cx, |selections| selections.refresh());
- }
- }
-
- pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext<Self>) {
- let snapshot = self.snapshot(cx);
- let selections = self.selections.all(cx);
- self.toggle_hunks_expanded(hunks_for_selections(&snapshot, &selections), cx);
- }
-
- pub fn expand_all_hunk_diffs(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext<Self>) {
- let snapshot = self.snapshot(cx);
- let display_rows_with_expanded_hunks = self
- .diff_map
- .hunks(false)
- .map(|hunk| &hunk.hunk_range)
- .map(|anchor_range| {
- (
- anchor_range
- .start
- .to_display_point(&snapshot.display_snapshot)
- .row(),
- anchor_range
- .end
- .to_display_point(&snapshot.display_snapshot)
- .row(),
- )
- })
- .collect::<HashMap<_, _>>();
- let hunks = self
- .diff_map
- .snapshot
- .diff_hunks(&snapshot.display_snapshot.buffer_snapshot)
- .filter(|hunk| {
- let hunk_display_row_range = Point::new(hunk.row_range.start.0, 0)
- .to_display_point(&snapshot.display_snapshot)
- ..Point::new(hunk.row_range.end.0, 0)
- .to_display_point(&snapshot.display_snapshot);
- let row_range_end =
- display_rows_with_expanded_hunks.get(&hunk_display_row_range.start.row());
- row_range_end.is_none() || row_range_end != Some(&hunk_display_row_range.end.row())
- });
- self.toggle_hunks_expanded(hunks.collect(), cx);
- }
-
- fn toggle_hunks_expanded(
- &mut self,
- hunks_to_toggle: Vec<MultiBufferDiffHunk>,
- cx: &mut ViewContext<Self>,
- ) {
- if self.diff_map.expand_all {
- return;
- }
-
- let previous_toggle_task = self.diff_map.hunk_update_tasks.remove(&None);
- let new_toggle_task = cx.spawn(move |editor, mut cx| async move {
- if let Some(task) = previous_toggle_task {
- task.await;
- }
-
- editor
- .update(&mut cx, |editor, cx| {
- let snapshot = editor.snapshot(cx);
- let mut hunks_to_toggle = hunks_to_toggle.into_iter().fuse().peekable();
- let mut highlights_to_remove = Vec::with_capacity(editor.diff_map.hunks.len());
- let mut blocks_to_remove = HashSet::default();
- let mut hunks_to_expand = Vec::new();
- editor.diff_map.hunks.retain(|expanded_hunk| {
- if expanded_hunk.folded {
- return true;
- }
- let expanded_hunk_row_range = expanded_hunk
- .hunk_range
- .start
- .to_display_point(&snapshot)
- .row()
- ..expanded_hunk
- .hunk_range
- .end
- .to_display_point(&snapshot)
- .row();
- let mut retain = true;
- while let Some(hunk_to_toggle) = hunks_to_toggle.peek() {
- match diff_hunk_to_display(hunk_to_toggle, &snapshot) {
- DisplayDiffHunk::Folded { .. } => {
- hunks_to_toggle.next();
- continue;
- }
- DisplayDiffHunk::Unfolded {
- diff_base_byte_range,
- display_row_range,
- multi_buffer_range,
- status,
- } => {
- let hunk_to_toggle_row_range = display_row_range;
- if hunk_to_toggle_row_range.start > expanded_hunk_row_range.end
- {
- break;
- } else if expanded_hunk_row_range == hunk_to_toggle_row_range {
- highlights_to_remove.push(expanded_hunk.hunk_range.clone());
- blocks_to_remove
- .extend(expanded_hunk.blocks.iter().copied());
- hunks_to_toggle.next();
- retain = false;
- break;
- } else {
- hunks_to_expand.push(HoveredHunk {
- status,
- multi_buffer_range,
- diff_base_byte_range,
- });
- hunks_to_toggle.next();
- continue;
- }
- }
- }
- }
-
- retain
- });
- for hunk in hunks_to_toggle {
- let remaining_hunk_point_range = Point::new(hunk.row_range.start.0, 0)
- ..Point::new(hunk.row_range.end.0, 0);
- let hunk_start = snapshot
- .buffer_snapshot
- .anchor_before(remaining_hunk_point_range.start);
- let hunk_end = snapshot
- .buffer_snapshot
- .anchor_in_excerpt(hunk_start.excerpt_id, hunk.buffer_range.end)
- .unwrap();
- hunks_to_expand.push(HoveredHunk {
- status: hunk_status(&hunk),
- multi_buffer_range: hunk_start..hunk_end,
- diff_base_byte_range: hunk.diff_base_byte_range.clone(),
- });
- }
-
- editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx);
- editor.remove_blocks(blocks_to_remove, None, cx);
- for hunk in hunks_to_expand {
- editor.expand_diff_hunk(None, &hunk, cx);
- }
- cx.notify();
- })
- .ok();
- });
-
- self.diff_map
- .hunk_update_tasks
- .insert(None, cx.background_executor().spawn(new_toggle_task));
- }
-
- pub(super) fn expand_diff_hunk(
- &mut self,
- diff_base_buffer: Option<Model<Buffer>>,
- hunk: &HoveredHunk,
- cx: &mut ViewContext<Editor>,
- ) -> Option<()> {
- let buffer = self.buffer.clone();
- let multi_buffer_snapshot = buffer.read(cx).snapshot(cx);
- let hunk_range = hunk.multi_buffer_range.clone();
- let buffer_id = hunk_range.start.buffer_id?;
- let diff_base_buffer = diff_base_buffer.or_else(|| {
- self.diff_map
- .diff_bases
- .get(&buffer_id)?
- .change_set
- .read(cx)
- .base_text
- .clone()
- })?;
-
- let diff_base = diff_base_buffer.read(cx);
- let diff_start_row = diff_base
- .offset_to_point(hunk.diff_base_byte_range.start)
- .row;
- let diff_end_row = diff_base.offset_to_point(hunk.diff_base_byte_range.end).row;
- let deleted_text_lines = diff_end_row - diff_start_row;
-
- let block_insert_index = self
- .diff_map
- .hunks
- .binary_search_by(|probe| {
- probe
- .hunk_range
- .start
- .cmp(&hunk_range.start, &multi_buffer_snapshot)
- })
- .err()?;
-
- let blocks;
- match hunk.status {
- DiffHunkStatus::Removed => {
- blocks = self.insert_blocks(
- [
- self.hunk_header_block(&hunk, cx),
- Self::deleted_text_block(hunk, diff_base_buffer, deleted_text_lines, cx),
- ],
- None,
- cx,
- );
- }
- DiffHunkStatus::Added => {
- self.highlight_rows::<DiffRowHighlight>(
- hunk_range.clone(),
- added_hunk_color(cx),
- false,
- cx,
- );
- blocks = self.insert_blocks([self.hunk_header_block(&hunk, cx)], None, cx);
- }
- DiffHunkStatus::Modified => {
- self.highlight_rows::<DiffRowHighlight>(
- hunk_range.clone(),
- added_hunk_color(cx),
- false,
- cx,
- );
- blocks = self.insert_blocks(
- [
- self.hunk_header_block(&hunk, cx),
- Self::deleted_text_block(hunk, diff_base_buffer, deleted_text_lines, cx),
- ],
- None,
- cx,
- );
- }
- };
- self.diff_map.hunks.insert(
- block_insert_index,
- ExpandedHunk {
- blocks,
- hunk_range,
- status: hunk.status,
- folded: false,
- diff_base_byte_range: hunk.diff_base_byte_range.clone(),
- },
- );
-
- Some(())
- }
-
- fn apply_diff_hunks_in_range(
- &mut self,
- range: Range<Anchor>,
- cx: &mut ViewContext<Editor>,
- ) -> Option<()> {
- let multi_buffer = self.buffer.read(cx);
- let multi_buffer_snapshot = multi_buffer.snapshot(cx);
- let (excerpt, range) = multi_buffer_snapshot
- .range_to_buffer_ranges(range)
- .into_iter()
- .next()?;
-
- multi_buffer
- .buffer(excerpt.buffer_id())
- .unwrap()
- .update(cx, |branch_buffer, cx| {
- branch_buffer.merge_into_base(vec![range], cx);
- });
-
- if let Some(project) = self.project.clone() {
- self.save(true, project, cx).detach_and_log_err(cx);
- }
-
- None
- }
-
- pub(crate) fn apply_all_diff_hunks(
- &mut self,
- _: &ApplyAllDiffHunks,
- cx: &mut ViewContext<Self>,
- ) {
- let buffers = self.buffer.read(cx).all_buffers();
- for branch_buffer in buffers {
- branch_buffer.update(cx, |branch_buffer, cx| {
- branch_buffer.merge_into_base(Vec::new(), cx);
- });
- }
-
- if let Some(project) = self.project.clone() {
- self.save(true, project, cx).detach_and_log_err(cx);
- }
- }
-
- pub(crate) fn apply_selected_diff_hunks(
- &mut self,
- _: &ApplyDiffHunk,
- cx: &mut ViewContext<Self>,
- ) {
- let snapshot = self.snapshot(cx);
- let hunks = hunks_for_selections(&snapshot, &self.selections.all(cx));
- let mut ranges_by_buffer = HashMap::default();
- self.transact(cx, |editor, cx| {
- for hunk in hunks {
- if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
- ranges_by_buffer
- .entry(buffer.clone())
- .or_insert_with(Vec::new)
- .push(hunk.buffer_range.to_offset(buffer.read(cx)));
- }
- }
-
- for (buffer, ranges) in ranges_by_buffer {
- buffer.update(cx, |buffer, cx| {
- buffer.merge_into_base(ranges, cx);
- });
- }
- });
-
- if let Some(project) = self.project.clone() {
- self.save(true, project, cx).detach_and_log_err(cx);
- }
- }
-
- fn has_multiple_hunks(&self, cx: &AppContext) -> bool {
- let snapshot = self.buffer.read(cx).snapshot(cx);
- let mut hunks = self.diff_map.snapshot.diff_hunks(&snapshot);
- hunks.nth(1).is_some()
- }
-
- fn hunk_header_block(
- &self,
- hunk: &HoveredHunk,
- cx: &mut ViewContext<Editor>,
- ) -> BlockProperties<Anchor> {
- let is_branch_buffer = self
- .buffer
- .read(cx)
- .point_to_buffer_offset(hunk.multi_buffer_range.start, cx)
- .map_or(false, |(buffer, _, _)| {
- buffer.read(cx).base_buffer().is_some()
- });
-
- let border_color = cx.theme().colors().border_variant;
- let bg_color = cx.theme().colors().editor_background;
- let gutter_color = match hunk.status {
- DiffHunkStatus::Added => cx.theme().status().created,
- DiffHunkStatus::Modified => cx.theme().status().modified,
- DiffHunkStatus::Removed => cx.theme().status().deleted,
- };
-
- BlockProperties {
- placement: BlockPlacement::Above(hunk.multi_buffer_range.start),
- height: 1,
- style: BlockStyle::Sticky,
- priority: 0,
- render: Arc::new({
- let editor = cx.view().clone();
- let hunk = hunk.clone();
- let has_multiple_hunks = self.has_multiple_hunks(cx);
-
- move |cx| {
- let hunk_controls_menu_handle =
- editor.read(cx).hunk_controls_menu_handle.clone();
-
- h_flex()
- .id(cx.block_id)
- .block_mouse_down()
- .h(cx.line_height())
- .w_full()
- .border_t_1()
- .border_color(border_color)
- .bg(bg_color)
- .child(
- div()
- .id("gutter-strip")
- .w(EditorElement::diff_hunk_strip_width(cx.line_height()))
- .h_full()
- .bg(gutter_color)
- .cursor(CursorStyle::PointingHand)
- .on_click({
- let editor = editor.clone();
- let hunk = hunk.clone();
- move |_event, cx| {
- editor.update(cx, |editor, cx| {
- editor.toggle_hovered_hunk(&hunk, cx);
- });
- }
- }),
- )
- .child(
- h_flex()
- .px_6()
- .size_full()
- .justify_end()
- .child(
- h_flex()
- .gap_1()
- .when(!is_branch_buffer, |row| {
- row.child(
- IconButton::new("next-hunk", IconName::ArrowDown)
- .shape(IconButtonShape::Square)
- .icon_size(IconSize::Small)
- .disabled(!has_multiple_hunks)
- .tooltip({
- let focus_handle = editor.focus_handle(cx);
- move |cx| {
- Tooltip::for_action_in(
- "Next Hunk",
- &GoToHunk,
- &focus_handle,
- cx,
- )
- }
- })
- .on_click({
- let editor = editor.clone();
- let hunk = hunk.clone();
- move |_event, cx| {
- editor.update(cx, |editor, cx| {
- editor.go_to_subsequent_hunk(
- hunk.multi_buffer_range.end,
- cx,
- );
- });
- }
- }),
- )
- .child(
- IconButton::new("prev-hunk", IconName::ArrowUp)
- .shape(IconButtonShape::Square)
- .icon_size(IconSize::Small)
- .disabled(!has_multiple_hunks)
- .tooltip({
- let focus_handle = editor.focus_handle(cx);
- move |cx| {
- Tooltip::for_action_in(
- "Previous Hunk",
- &GoToPrevHunk,
- &focus_handle,
- cx,
- )
- }
- })
- .on_click({
- let editor = editor.clone();
- let hunk = hunk.clone();
- move |_event, cx| {
- editor.update(cx, |editor, cx| {
- editor.go_to_preceding_hunk(
- hunk.multi_buffer_range.start,
- cx,
- );
- });
- }
- }),
- )
- })
- .child(
- IconButton::new("discard", IconName::Undo)
- .shape(IconButtonShape::Square)
- .icon_size(IconSize::Small)
- .tooltip({
- let focus_handle = editor.focus_handle(cx);
- move |cx| {
- Tooltip::for_action_in(
- "Discard Hunk",
- &RevertSelectedHunks,
- &focus_handle,
- cx,
- )
- }
- })
- .on_click({
- let editor = editor.clone();
- let hunk = hunk.clone();
- move |_event, cx| {
- editor.update(cx, |editor, cx| {
- editor.revert_hunk(hunk.clone(), cx);
- });
- }
- }),
- )
- .map(|this| {
- if is_branch_buffer {
- this.child(
- IconButton::new("apply", IconName::Check)
- .shape(IconButtonShape::Square)
- .icon_size(IconSize::Small)
- .tooltip({
- let focus_handle =
- editor.focus_handle(cx);
- move |cx| {
- Tooltip::for_action_in(
- "Apply Hunk",
- &ApplyDiffHunk,
- &focus_handle,
- cx,
- )
- }
- })
- .on_click({
- let editor = editor.clone();
- let hunk = hunk.clone();
- move |_event, cx| {
- editor.update(cx, |editor, cx| {
- editor
- .apply_diff_hunks_in_range(
- hunk.multi_buffer_range
- .clone(),
- cx,
- );
- });
- }
- }),
- )
- } else {
- this.child({
- let focus = editor.focus_handle(cx);
- PopoverMenu::new("hunk-controls-dropdown")
- .trigger(
- IconButton::new(
- "toggle_editor_selections_icon",
- IconName::EllipsisVertical,
- )
- .shape(IconButtonShape::Square)
- .icon_size(IconSize::Small)
- .style(ButtonStyle::Subtle)
- .toggle_state(
- hunk_controls_menu_handle
- .is_deployed(),
- )
- .when(
- !hunk_controls_menu_handle
- .is_deployed(),
- |this| {
- this.tooltip(|cx| {
- Tooltip::text(
- "Hunk Controls",
- cx,
- )
- })
- },
- ),
- )
- .anchor(Corner::TopRight)
- .with_handle(hunk_controls_menu_handle)
- .menu(move |cx| {
- let focus = focus.clone();
- let menu = ContextMenu::build(
- cx,
- move |menu, _| {
- menu.context(focus.clone())
- .action(
- "Discard All Hunks",
- RevertFile
- .boxed_clone(),
- )
- },
- );
- Some(menu)
- })
- })
- }
- }),
- )
- .when(!is_branch_buffer, |div| {
- div.child(
- IconButton::new("collapse", IconName::Close)
- .shape(IconButtonShape::Square)
- .icon_size(IconSize::Small)
- .tooltip({
- let focus_handle = editor.focus_handle(cx);
- move |cx| {
- Tooltip::for_action_in(
- "Collapse Hunk",
- &ToggleHunkDiff,
- &focus_handle,
- cx,
- )
- }
- })
- .on_click({
- let editor = editor.clone();
- let hunk = hunk.clone();
- move |_event, cx| {
- editor.update(cx, |editor, cx| {
- editor.toggle_hovered_hunk(&hunk, cx);
- });
- }
- }),
- )
- }),
- )
- .into_any_element()
- }
- }),
- }
- }
-
- fn deleted_text_block(
- hunk: &HoveredHunk,
- diff_base_buffer: Model<Buffer>,
- deleted_text_height: u32,
- cx: &mut ViewContext<Editor>,
- ) -> BlockProperties<Anchor> {
- let gutter_color = match hunk.status {
- DiffHunkStatus::Added => unreachable!(),
- DiffHunkStatus::Modified => cx.theme().status().modified,
- DiffHunkStatus::Removed => cx.theme().status().deleted,
- };
- let deleted_hunk_color = deleted_hunk_color(cx);
- let (editor_height, editor_with_deleted_text) =
- editor_with_deleted_text(diff_base_buffer, deleted_hunk_color, hunk, cx);
- let editor = cx.view().clone();
- let hunk = hunk.clone();
- let height = editor_height.max(deleted_text_height);
- BlockProperties {
- placement: BlockPlacement::Above(hunk.multi_buffer_range.start),
- height,
- style: BlockStyle::Flex,
- priority: 0,
- render: Arc::new(move |cx| {
- let width = EditorElement::diff_hunk_strip_width(cx.line_height());
- let gutter_dimensions = editor.read(cx.context).gutter_dimensions;
-
- h_flex()
- .id(cx.block_id)
- .block_mouse_down()
- .bg(deleted_hunk_color)
- .h(height as f32 * cx.line_height())
- .w_full()
- .child(
- h_flex()
- .id("gutter")
- .max_w(gutter_dimensions.full_width())
- .min_w(gutter_dimensions.full_width())
- .size_full()
- .child(
- h_flex()
- .id("gutter hunk")
- .bg(gutter_color)
- .pl(gutter_dimensions.margin
- + gutter_dimensions
- .git_blame_entries_width
- .unwrap_or_default())
- .max_w(width)
- .min_w(width)
- .size_full()
- .cursor(CursorStyle::PointingHand)
- .on_mouse_down(MouseButton::Left, {
- let editor = editor.clone();
- let hunk = hunk.clone();
- move |_event, cx| {
- editor.update(cx, |editor, cx| {
- editor.toggle_hovered_hunk(&hunk, cx);
- });
- }
- }),
- ),
- )
- .child(editor_with_deleted_text.clone())
- .into_any_element()
- }),
- }
- }
-
- pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<Editor>) -> bool {
- if self.diff_map.expand_all {
- return false;
- }
- self.diff_map.hunk_update_tasks.clear();
- self.clear_row_highlights::<DiffRowHighlight>();
- let to_remove = self
- .diff_map
- .hunks
- .drain(..)
- .flat_map(|expanded_hunk| expanded_hunk.blocks.into_iter())
- .collect::<HashSet<_>>();
- if to_remove.is_empty() {
- false
- } else {
- self.remove_blocks(to_remove, None, cx);
- true
- }
- }
-
- pub(super) fn sync_expanded_diff_hunks(
- diff_map: &mut DiffMap,
- buffer_id: BufferId,
- cx: &mut ViewContext<Self>,
- ) {
- let diff_base_state = diff_map.diff_bases.get_mut(&buffer_id);
- let mut diff_base_buffer = None;
- let mut diff_base_buffer_unchanged = true;
- if let Some(diff_base_state) = diff_base_state {
- diff_base_state.change_set.update(cx, |change_set, _| {
- if diff_base_state.last_version != Some(change_set.base_text_version) {
- diff_base_state.last_version = Some(change_set.base_text_version);
- diff_base_buffer_unchanged = false;
- }
- diff_base_buffer = change_set.base_text.clone();
- })
- }
-
- diff_map.hunk_update_tasks.remove(&Some(buffer_id));
-
- let new_sync_task = cx.spawn(move |editor, mut cx| async move {
- editor
- .update(&mut cx, |editor, cx| {
- let snapshot = editor.snapshot(cx);
- let mut recalculated_hunks = snapshot
- .diff_map
- .diff_hunks(&snapshot.buffer_snapshot)
- .filter(|hunk| hunk.buffer_id == buffer_id)
- .fuse()
- .peekable();
- let mut highlights_to_remove = Vec::with_capacity(editor.diff_map.hunks.len());
- let mut blocks_to_remove = HashSet::default();
- let mut hunks_to_reexpand = Vec::with_capacity(editor.diff_map.hunks.len());
- editor.diff_map.hunks.retain_mut(|expanded_hunk| {
- if expanded_hunk.hunk_range.start.buffer_id != Some(buffer_id) {
- return true;
- };
-
- let mut retain = false;
- if diff_base_buffer_unchanged {
- let expanded_hunk_display_range = expanded_hunk
- .hunk_range
- .start
- .to_display_point(&snapshot)
- .row()
- ..expanded_hunk
- .hunk_range
- .end
- .to_display_point(&snapshot)
- .row();
- while let Some(buffer_hunk) = recalculated_hunks.peek() {
- match diff_hunk_to_display(buffer_hunk, &snapshot) {
- DisplayDiffHunk::Folded { display_row } => {
- recalculated_hunks.next();
- if !expanded_hunk.folded
- && expanded_hunk_display_range
- .to_inclusive()
- .contains(&display_row)
- {
- retain = true;
- expanded_hunk.folded = true;
- highlights_to_remove
- .push(expanded_hunk.hunk_range.clone());
- for block in expanded_hunk.blocks.drain(..) {
- blocks_to_remove.insert(block);
- }
- break;
- } else {
- continue;
- }
- }
- DisplayDiffHunk::Unfolded {
- diff_base_byte_range,
- display_row_range,
- multi_buffer_range,
- status,
- } => {
- let hunk_display_range = display_row_range;
-
- if expanded_hunk_display_range.start
- > hunk_display_range.end
- {
- recalculated_hunks.next();
- if editor.diff_map.expand_all {
- hunks_to_reexpand.push(HoveredHunk {
- status,
- multi_buffer_range,
- diff_base_byte_range,
- });
- }
- continue;
- }
-
- if expanded_hunk_display_range.end
- < hunk_display_range.start
- {
- break;
- }
-
- if !expanded_hunk.folded
- && expanded_hunk_display_range == hunk_display_range
- && expanded_hunk.status == hunk_status(buffer_hunk)
- && expanded_hunk.diff_base_byte_range
- == buffer_hunk.diff_base_byte_range
- {
- recalculated_hunks.next();
- retain = true;
- } else {
- hunks_to_reexpand.push(HoveredHunk {
- status,
- multi_buffer_range,
- diff_base_byte_range,
- });
- }
- break;
- }
- }
- }
- }
- if !retain {
- blocks_to_remove.extend(expanded_hunk.blocks.drain(..));
- highlights_to_remove.push(expanded_hunk.hunk_range.clone());
- }
- retain
- });
-
- if editor.diff_map.expand_all {
- for hunk in recalculated_hunks {
- match diff_hunk_to_display(&hunk, &snapshot) {
- DisplayDiffHunk::Folded { .. } => {}
- DisplayDiffHunk::Unfolded {
- diff_base_byte_range,
- multi_buffer_range,
- status,
- ..
- } => {
- hunks_to_reexpand.push(HoveredHunk {
- status,
- multi_buffer_range,
- diff_base_byte_range,
- });
- }
- }
- }
- } else {
- drop(recalculated_hunks);
- }
-
- editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx);
- editor.remove_blocks(blocks_to_remove, None, cx);
-
- if let Some(diff_base_buffer) = &diff_base_buffer {
- for hunk in hunks_to_reexpand {
- editor.expand_diff_hunk(Some(diff_base_buffer.clone()), &hunk, cx);
- }
- }
- })
- .ok();
- });
-
- diff_map.hunk_update_tasks.insert(
- Some(buffer_id),
- cx.background_executor().spawn(new_sync_task),
- );
- }
-
- fn go_to_subsequent_hunk(&mut self, position: Anchor, cx: &mut ViewContext<Self>) {
- let snapshot = self.snapshot(cx);
- let position = position.to_point(&snapshot.buffer_snapshot);
- if let Some(hunk) = self.go_to_hunk_after_position(&snapshot, position, cx) {
- let multi_buffer_start = snapshot
- .buffer_snapshot
- .anchor_before(Point::new(hunk.row_range.start.0, 0));
- let multi_buffer_end = snapshot
- .buffer_snapshot
- .anchor_after(Point::new(hunk.row_range.end.0, 0));
- self.expand_diff_hunk(
- None,
- &HoveredHunk {
- multi_buffer_range: multi_buffer_start..multi_buffer_end,
- status: hunk_status(&hunk),
- diff_base_byte_range: hunk.diff_base_byte_range,
- },
- cx,
- );
- }
- }
-
- fn go_to_preceding_hunk(&mut self, position: Anchor, cx: &mut ViewContext<Self>) {
- let snapshot = self.snapshot(cx);
- let position = position.to_point(&snapshot.buffer_snapshot);
- let hunk = self.go_to_hunk_before_position(&snapshot, position, cx);
- if let Some(hunk) = hunk {
- let multi_buffer_start = snapshot
- .buffer_snapshot
- .anchor_before(Point::new(hunk.row_range.start.0, 0));
- let multi_buffer_end = snapshot
- .buffer_snapshot
- .anchor_after(Point::new(hunk.row_range.end.0, 0));
- self.expand_diff_hunk(
- None,
- &HoveredHunk {
- multi_buffer_range: multi_buffer_start..multi_buffer_end,
- status: hunk_status(&hunk),
- diff_base_byte_range: hunk.diff_base_byte_range,
- },
- cx,
- );
- }
- }
-}
-
-pub(crate) fn to_diff_hunk(
- hovered_hunk: &HoveredHunk,
- multi_buffer_snapshot: &MultiBufferSnapshot,
-) -> Option<MultiBufferDiffHunk> {
- let buffer_id = hovered_hunk
- .multi_buffer_range
- .start
- .buffer_id
- .or(hovered_hunk.multi_buffer_range.end.buffer_id)?;
- let buffer_range = hovered_hunk.multi_buffer_range.start.text_anchor
- ..hovered_hunk.multi_buffer_range.end.text_anchor;
- let point_range = hovered_hunk
- .multi_buffer_range
- .to_point(multi_buffer_snapshot);
- Some(MultiBufferDiffHunk {
- row_range: MultiBufferRow(point_range.start.row)..MultiBufferRow(point_range.end.row),
- buffer_id,
- buffer_range,
- diff_base_byte_range: hovered_hunk.diff_base_byte_range.clone(),
- })
-}
-
-fn added_hunk_color(cx: &AppContext) -> Hsla {
- let mut created_color = cx.theme().status().git().created;
- created_color.fade_out(0.7);
- created_color
-}
-
-fn deleted_hunk_color(cx: &AppContext) -> Hsla {
- let mut deleted_color = cx.theme().status().deleted;
- deleted_color.fade_out(0.7);
- deleted_color
-}
-
-fn editor_with_deleted_text(
- diff_base_buffer: Model<Buffer>,
- deleted_color: Hsla,
- hunk: &HoveredHunk,
- cx: &mut ViewContext<Editor>,
-) -> (u32, View<Editor>) {
- let parent_editor = cx.view().downgrade();
- let editor = cx.new_view(|cx| {
- let multi_buffer =
- cx.new_model(|_| MultiBuffer::without_headers(language::Capability::ReadOnly));
- multi_buffer.update(cx, |multi_buffer, cx| {
- multi_buffer.push_excerpts(
- diff_base_buffer,
- Some(ExcerptRange {
- context: hunk.diff_base_byte_range.clone(),
- primary: None,
- }),
- cx,
- );
- });
-
- let mut editor = Editor::for_multibuffer(multi_buffer, None, true, cx);
- editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
- editor.set_show_wrap_guides(false, cx);
- editor.set_show_gutter(false, cx);
- editor.set_show_line_numbers(false, cx);
- editor.set_show_scrollbars(false, cx);
- editor.set_show_runnables(false, cx);
- editor.set_show_git_diff_gutter(false, cx);
- editor.set_show_code_actions(false, cx);
- editor.scroll_manager.set_forbid_vertical_scroll(true);
- editor.set_read_only(true);
- editor.set_show_inline_completions(Some(false), cx);
-
- enum DeletedBlockRowHighlight {}
- editor.highlight_rows::<DeletedBlockRowHighlight>(
- Anchor::min()..Anchor::max(),
- deleted_color,
- false,
- cx,
- );
- editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
- editor
- ._subscriptions
- .extend([cx.on_blur(&editor.focus_handle, |editor, cx| {
- editor.change_selections(None, cx, |s| {
- s.try_cancel();
- });
- })]);
-
- editor
- .register_action::<RevertSelectedHunks>({
- let hunk = hunk.clone();
- let parent_editor = parent_editor.clone();
- move |_, cx| {
- parent_editor
- .update(cx, |editor, cx| editor.revert_hunk(hunk.clone(), cx))
- .ok();
- }
- })
- .detach();
- editor
- .register_action::<ToggleHunkDiff>({
- let hunk = hunk.clone();
- move |_, cx| {
- parent_editor
- .update(cx, |editor, cx| {
- editor.toggle_hovered_hunk(&hunk, cx);
- })
- .ok();
- }
- })
- .detach();
- editor
- });
-
- let editor_height = editor.update(cx, |editor, cx| editor.max_point(cx).row().0);
- (editor_height, editor)
-}
-
-impl DisplayDiffHunk {
- pub fn start_display_row(&self) -> DisplayRow {
- match self {
- &DisplayDiffHunk::Folded { display_row } => display_row,
- DisplayDiffHunk::Unfolded {
- display_row_range, ..
- } => display_row_range.start,
- }
- }
-
- pub fn contains_display_row(&self, display_row: DisplayRow) -> bool {
- let range = match self {
- &DisplayDiffHunk::Folded { display_row } => display_row..=display_row,
-
- DisplayDiffHunk::Unfolded {
- display_row_range, ..
- } => display_row_range.start..=display_row_range.end,
- };
-
- range.contains(&display_row)
- }
-}
-
-pub fn diff_hunk_to_display(
- hunk: &MultiBufferDiffHunk,
- snapshot: &DisplaySnapshot,
-) -> DisplayDiffHunk {
- let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
- let hunk_start_point_sub = Point::new(hunk.row_range.start.0.saturating_sub(1), 0);
- let hunk_end_point_sub = Point::new(
- hunk.row_range
- .end
- .0
- .saturating_sub(1)
- .max(hunk.row_range.start.0),
- 0,
- );
-
- let status = hunk_status(hunk);
- let is_removal = status == DiffHunkStatus::Removed;
-
- let folds_start = Point::new(hunk.row_range.start.0.saturating_sub(2), 0);
- let folds_end = Point::new(hunk.row_range.end.0 + 2, 0);
- let folds_range = folds_start..folds_end;
-
- let containing_fold = snapshot.folds_in_range(folds_range).find(|fold| {
- let fold_point_range = fold.range.to_point(&snapshot.buffer_snapshot);
- let fold_point_range = fold_point_range.start..=fold_point_range.end;
-
- let folded_start = fold_point_range.contains(&hunk_start_point);
- let folded_end = fold_point_range.contains(&hunk_end_point_sub);
- let folded_start_sub = fold_point_range.contains(&hunk_start_point_sub);
-
- (folded_start && folded_end) || (is_removal && folded_start_sub)
- });
-
- if let Some(fold) = containing_fold {
- let row = fold.range.start.to_display_point(snapshot).row();
- DisplayDiffHunk::Folded { display_row: row }
- } else {
- let start = hunk_start_point.to_display_point(snapshot).row();
-
- let hunk_end_row = hunk.row_range.end.max(hunk.row_range.start);
- let hunk_end_point = Point::new(hunk_end_row.0, 0);
-
- let multi_buffer_start = snapshot.buffer_snapshot.anchor_before(hunk_start_point);
- let multi_buffer_end = snapshot
- .buffer_snapshot
- .anchor_in_excerpt(multi_buffer_start.excerpt_id, hunk.buffer_range.end)
- .unwrap();
- let end = hunk_end_point.to_display_point(snapshot).row();
-
- DisplayDiffHunk::Unfolded {
- display_row_range: start..end,
- multi_buffer_range: multi_buffer_start..multi_buffer_end,
- status,
- diff_base_byte_range: hunk.diff_base_byte_range.clone(),
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::{editor_tests::init_test, hunk_status};
- use gpui::{Context, TestAppContext};
- use language::Capability::ReadWrite;
- use multi_buffer::{ExcerptRange, MultiBuffer, MultiBufferRow};
- use project::{FakeFs, Project};
- use unindent::Unindent as _;
-
- #[gpui::test]
- async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
- use git::diff::DiffHunkStatus;
- init_test(cx, |_| {});
-
- let fs = FakeFs::new(cx.background_executor.clone());
- let project = Project::test(fs, [], cx).await;
-
- // buffer has two modified hunks with two rows each
- let diff_base_1 = "
- 1.zero
- 1.one
- 1.two
- 1.three
- 1.four
- 1.five
- 1.six
- "
- .unindent();
-
- let text_1 = "
- 1.zero
- 1.ONE
- 1.TWO
- 1.three
- 1.FOUR
- 1.FIVE
- 1.six
- "
- .unindent();
-
- // buffer has a deletion hunk and an insertion hunk
- let diff_base_2 = "
- 2.zero
- 2.one
- 2.one-and-a-half
- 2.two
- 2.three
- 2.four
- 2.six
- "
- .unindent();
-
- let text_2 = "
- 2.zero
- 2.one
- 2.two
- 2.three
- 2.four
- 2.five
- 2.six
- "
- .unindent();
-
- let buffer_1 = project.update(cx, |project, cx| {
- project.create_local_buffer(text_1.as_str(), None, cx)
- });
- let buffer_2 = project.update(cx, |project, cx| {
- project.create_local_buffer(text_2.as_str(), None, cx)
- });
-
- let multibuffer = cx.new_model(|cx| {
- let mut multibuffer = MultiBuffer::new(ReadWrite);
- multibuffer.push_excerpts(
- buffer_1.clone(),
- [
- // excerpt ends in the middle of a modified hunk
- ExcerptRange {
- context: Point::new(0, 0)..Point::new(1, 5),
- primary: Default::default(),
- },
- // excerpt begins in the middle of a modified hunk
- ExcerptRange {
- context: Point::new(5, 0)..Point::new(6, 5),
- primary: Default::default(),
- },
- ],
- cx,
- );
- multibuffer.push_excerpts(
- buffer_2.clone(),
- [
- // excerpt ends at a deletion
- ExcerptRange {
- context: Point::new(0, 0)..Point::new(1, 5),
- primary: Default::default(),
- },
- // excerpt starts at a deletion
- ExcerptRange {
- context: Point::new(2, 0)..Point::new(2, 5),
- primary: Default::default(),
- },
- // excerpt fully contains a deletion hunk
- ExcerptRange {
- context: Point::new(1, 0)..Point::new(2, 5),
- primary: Default::default(),
- },
- // excerpt fully contains an insertion hunk
- ExcerptRange {
- context: Point::new(4, 0)..Point::new(6, 5),
- primary: Default::default(),
- },
- ],
- cx,
- );
- multibuffer
- });
-
- let editor = cx.add_window(|cx| Editor::for_multibuffer(multibuffer, None, false, cx));
- editor
- .update(cx, |editor, cx| {
- for (buffer, diff_base) in [
- (buffer_1.clone(), diff_base_1),
- (buffer_2.clone(), diff_base_2),
- ] {
- let change_set = cx.new_model(|cx| {
- BufferChangeSet::new_with_base_text(
- diff_base.to_string(),
- buffer.read(cx).text_snapshot(),
- cx,
- )
- });
- editor.diff_map.add_change_set(change_set, cx)
- }
- })
- .unwrap();
- cx.background_executor.run_until_parked();
-
- let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
-
- assert_eq!(
- snapshot.buffer_snapshot.text(),
- "
- 1.zero
- 1.ONE
- 1.FIVE
- 1.six
- 2.zero
- 2.one
- 2.two
- 2.one
- 2.two
- 2.four
- 2.five
- 2.six"
- .unindent()
- );
-
- let expected = [
- (
- DiffHunkStatus::Modified,
- MultiBufferRow(1)..MultiBufferRow(2),
- ),
- (
- DiffHunkStatus::Modified,
- MultiBufferRow(2)..MultiBufferRow(3),
- ),
- //TODO: Define better when and where removed hunks show up at range extremities
- (
- DiffHunkStatus::Removed,
- MultiBufferRow(6)..MultiBufferRow(6),
- ),
- (
- DiffHunkStatus::Removed,
- MultiBufferRow(8)..MultiBufferRow(8),
- ),
- (
- DiffHunkStatus::Added,
- MultiBufferRow(10)..MultiBufferRow(11),
- ),
- ];
-
- assert_eq!(
- snapshot
- .diff_map
- .diff_hunks_in_range(Point::zero()..Point::new(12, 0), &snapshot.buffer_snapshot)
- .map(|hunk| (hunk_status(&hunk), hunk.row_range))
- .collect::<Vec<_>>(),
- &expected,
- );
-
- assert_eq!(
- snapshot
- .diff_map
- .diff_hunks_in_range_rev(
- Point::zero()..Point::new(12, 0),
- &snapshot.buffer_snapshot
- )
- .map(|hunk| (hunk_status(&hunk), hunk.row_range))
- .collect::<Vec<_>>(),
- expected
- .iter()
- .rev()
- .cloned()
- .collect::<Vec<_>>()
- .as_slice(),
- );
- }
-}
@@ -2,17 +2,16 @@ use std::{ops::Range, time::Duration};
use collections::HashSet;
use gpui::{AppContext, Task};
-use language::{language_settings::language_settings, BufferRow};
-use multi_buffer::{MultiBufferIndentGuide, MultiBufferRow};
-use text::{BufferId, LineIndent, Point};
+use language::language_settings::language_settings;
+use multi_buffer::{IndentGuide, MultiBufferRow};
+use text::{LineIndent, Point};
use ui::ViewContext;
use util::ResultExt;
use crate::{DisplaySnapshot, Editor};
struct ActiveIndentedRange {
- buffer_id: BufferId,
- row_range: Range<BufferRow>,
+ row_range: Range<MultiBufferRow>,
indent: LineIndent,
}
@@ -36,7 +35,7 @@ impl Editor {
visible_buffer_range: Range<MultiBufferRow>,
snapshot: &DisplaySnapshot,
cx: &mut ViewContext<Editor>,
- ) -> Option<Vec<MultiBufferIndentGuide>> {
+ ) -> Option<Vec<IndentGuide>> {
let show_indent_guides = self.should_show_indent_guides().unwrap_or_else(|| {
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
language_settings(
@@ -66,7 +65,7 @@ impl Editor {
pub fn find_active_indent_guide_indices(
&mut self,
- indent_guides: &[MultiBufferIndentGuide],
+ indent_guides: &[IndentGuide],
snapshot: &DisplaySnapshot,
cx: &mut ViewContext<Editor>,
) -> Option<HashSet<usize>> {
@@ -134,9 +133,7 @@ impl Editor {
.iter()
.enumerate()
.filter(|(_, indent_guide)| {
- indent_guide.buffer_id == active_indent_range.buffer_id
- && indent_guide.indent_level()
- == active_indent_range.indent.len(indent_guide.tab_size)
+ indent_guide.indent_level() == active_indent_range.indent.len(indent_guide.tab_size)
});
let mut matches = HashSet::default();
@@ -158,7 +155,7 @@ pub fn indent_guides_in_range(
ignore_disabled_for_language: bool,
snapshot: &DisplaySnapshot,
cx: &AppContext,
-) -> Vec<MultiBufferIndentGuide> {
+) -> Vec<IndentGuide> {
let start_anchor = snapshot
.buffer_snapshot
.anchor_before(Point::new(visible_buffer_range.start.0, 0));
@@ -169,14 +166,12 @@ pub fn indent_guides_in_range(
snapshot
.buffer_snapshot
.indent_guides_in_range(start_anchor..end_anchor, ignore_disabled_for_language, cx)
- .into_iter()
.filter(|indent_guide| {
- if editor.buffer_folded(indent_guide.buffer_id, cx) {
+ if editor.is_buffer_folded(indent_guide.buffer_id, cx) {
return false;
}
- let start =
- MultiBufferRow(indent_guide.multibuffer_row_range.start.0.saturating_sub(1));
+ let start = MultiBufferRow(indent_guide.start_row.0.saturating_sub(1));
// Filter out indent guides that are inside a fold
// All indent guides that are starting "offscreen" have a start value of the first visible row minus one
// Therefore checking if a line is folded at first visible row minus one causes the other indent guides that are not related to the fold to disappear as well
@@ -193,24 +188,11 @@ async fn resolve_indented_range(
snapshot: DisplaySnapshot,
buffer_row: MultiBufferRow,
) -> Option<ActiveIndentedRange> {
- let (buffer_row, buffer_snapshot, buffer_id) =
- if let Some((_, buffer_id, snapshot)) = snapshot.buffer_snapshot.as_singleton() {
- (buffer_row.0, snapshot, buffer_id)
- } else {
- let (snapshot, point) = snapshot.buffer_snapshot.buffer_line_for_row(buffer_row)?;
-
- let buffer_id = snapshot.remote_id();
- (point.start.row, snapshot, buffer_id)
- };
-
- buffer_snapshot
+ snapshot
+ .buffer_snapshot
.enclosing_indent(buffer_row)
.await
- .map(|(row_range, indent)| ActiveIndentedRange {
- row_range,
- indent,
- buffer_id,
- })
+ .map(|(row_range, indent)| ActiveIndentedRange { row_range, indent })
}
fn should_recalculate_indented_range(
@@ -222,23 +204,23 @@ fn should_recalculate_indented_range(
if prev_row.0 == new_row.0 {
return false;
}
- if let Some((_, _, snapshot)) = snapshot.buffer_snapshot.as_singleton() {
- if !current_indent_range.row_range.contains(&new_row.0) {
+ if snapshot.buffer_snapshot.is_singleton() {
+ if !current_indent_range.row_range.contains(&new_row) {
return true;
}
- let old_line_indent = snapshot.line_indent_for_row(prev_row.0);
- let new_line_indent = snapshot.line_indent_for_row(new_row.0);
+ let old_line_indent = snapshot.buffer_snapshot.line_indent_for_row(prev_row);
+ let new_line_indent = snapshot.buffer_snapshot.line_indent_for_row(new_row);
if old_line_indent.is_line_empty()
|| new_line_indent.is_line_empty()
|| old_line_indent != new_line_indent
- || snapshot.max_point().row == new_row.0
+ || snapshot.buffer_snapshot.max_point().row == new_row.0
{
return true;
}
- let next_line_indent = snapshot.line_indent_for_row(new_row.0 + 1);
+ let next_line_indent = snapshot.buffer_snapshot.line_indent_for_row(new_row + 1);
next_line_indent.is_line_empty() || next_line_indent != old_line_indent
} else {
true
@@ -20,7 +20,6 @@ use language::{
SelectionGoal,
};
use lsp::DiagnosticSeverity;
-use multi_buffer::AnchorRangeExt;
use project::{
lsp_store::FormatTrigger, project_settings::ProjectSettings, search::SearchQuery, Project,
ProjectItem as _, ProjectPath,
@@ -528,6 +527,7 @@ fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor)
excerpt_id,
text_anchor: language::proto::deserialize_anchor(anchor.anchor?)?,
buffer_id: buffer.buffer_id_for_excerpt(excerpt_id),
+ diff_base_anchor: None,
})
}
@@ -1435,59 +1435,34 @@ impl SearchableItem for Editor {
cx.background_executor().spawn(async move {
let mut ranges = Vec::new();
- if let Some((_, _, excerpt_buffer)) = buffer.as_singleton() {
- let search_within_ranges = if search_within_ranges.is_empty() {
- vec![None]
- } else {
- search_within_ranges
- .into_iter()
- .map(|range| Some(range.to_offset(&buffer)))
- .collect::<Vec<_>>()
- };
-
- for range in search_within_ranges {
- let buffer = &buffer;
- ranges.extend(
- query
- .search(excerpt_buffer, range.clone())
- .await
- .into_iter()
- .map(|matched_range| {
- let offset = range.clone().map(|r| r.start).unwrap_or(0);
- buffer.anchor_after(matched_range.start + offset)
- ..buffer.anchor_before(matched_range.end + offset)
- }),
- );
- }
+ let search_within_ranges = if search_within_ranges.is_empty() {
+ vec![buffer.anchor_before(0)..buffer.anchor_after(buffer.len())]
} else {
- let search_within_ranges = if search_within_ranges.is_empty() {
- vec![buffer.anchor_before(0)..buffer.anchor_after(buffer.len())]
- } else {
- search_within_ranges
- };
-
- for (excerpt_id, search_buffer, search_range) in
- buffer.excerpts_in_ranges(search_within_ranges)
- {
- if !search_range.is_empty() {
- ranges.extend(
- query
- .search(search_buffer, Some(search_range.clone()))
- .await
- .into_iter()
- .map(|match_range| {
- let start = search_buffer
- .anchor_after(search_range.start + match_range.start);
- let end = search_buffer
- .anchor_before(search_range.start + match_range.end);
- buffer.anchor_in_excerpt(excerpt_id, start).unwrap()
- ..buffer.anchor_in_excerpt(excerpt_id, end).unwrap()
- }),
- );
- }
- }
+ search_within_ranges
};
+ for (search_buffer, search_range, excerpt_id) in
+ buffer.ranges_to_buffer_ranges(search_within_ranges.into_iter())
+ {
+ ranges.extend(
+ query
+ .search(search_buffer, Some(search_range.clone()))
+ .await
+ .into_iter()
+ .map(|match_range| {
+ let start =
+ search_buffer.anchor_after(search_range.start + match_range.start);
+ let end =
+ search_buffer.anchor_before(search_range.start + match_range.end);
+ Anchor::range_in_buffer(
+ excerpt_id,
+ search_buffer.remote_id(),
+ start..end,
+ )
+ }),
+ );
+ }
+
ranges
})
}
@@ -61,7 +61,7 @@ impl ProposedChangesEditor {
let mut this = Self {
editor: cx.new_view(|cx| {
let mut editor = Editor::for_multibuffer(multibuffer.clone(), project, true, cx);
- editor.set_expand_all_diff_hunks();
+ editor.set_expand_all_diff_hunks(cx);
editor.set_completion_provider(None);
editor.clear_code_action_providers();
editor.set_semantics_provider(
@@ -104,16 +104,10 @@ impl ProposedChangesEditor {
let buffer = buffer.read(cx);
let base_buffer = buffer.base_buffer()?;
let buffer = buffer.text_snapshot();
- let change_set = this.editor.update(cx, |editor, _| {
- Some(
- editor
- .diff_map
- .diff_bases
- .get(&buffer.remote_id())?
- .change_set
- .clone(),
- )
- })?;
+ let change_set = this
+ .multibuffer
+ .read(cx)
+ .change_set_for(buffer.remote_id())?;
Some(change_set.update(cx, |change_set, cx| {
change_set.set_base_text(
base_buffer.read(cx).text(),
@@ -193,7 +187,7 @@ impl ProposedChangesEditor {
} else {
branch_buffer = location.buffer.update(cx, |buffer, cx| buffer.branch(cx));
new_change_sets.push(cx.new_model(|cx| {
- let mut change_set = BufferChangeSet::new(branch_buffer.read(cx));
+ let mut change_set = BufferChangeSet::new(&branch_buffer, cx);
let _ = change_set.set_base_text(
location.buffer.read(cx).text(),
branch_buffer.read(cx).text_snapshot(),
@@ -223,9 +217,11 @@ impl ProposedChangesEditor {
self.buffer_entries = buffer_entries;
self.editor.update(cx, |editor, cx| {
editor.change_selections(None, cx, |selections| selections.refresh());
- for change_set in new_change_sets {
- editor.diff_map.add_change_set(change_set, cx)
- }
+ editor.buffer.update(cx, |buffer, cx| {
+ for change_set in new_change_sets {
+ buffer.add_change_set(change_set, cx)
+ }
+ })
});
}
@@ -323,8 +323,7 @@ impl SelectionsCollection {
self.all(cx).last().unwrap().clone()
}
- #[cfg(any(test, feature = "test-support"))]
- pub fn ranges<D: TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug>(
+ pub fn ranges<D: TextDimension + Ord + Sub<D, Output = D>>(
&self,
cx: &mut AppContext,
) -> Vec<Range<D>> {
@@ -332,9 +331,9 @@ impl SelectionsCollection {
.iter()
.map(|s| {
if s.reversed {
- s.end.clone()..s.start.clone()
+ s.end..s.start
} else {
- s.start.clone()..s.end.clone()
+ s.start..s.end
}
})
.collect()
@@ -921,7 +920,7 @@ pub(crate) fn resolve_selections<'a, D, I>(
map: &'a DisplaySnapshot,
) -> impl 'a + Iterator<Item = Selection<D>>
where
- D: TextDimension + Clone + Ord + Sub<D, Output = D>,
+ D: TextDimension + Ord + Sub<D, Output = D>,
I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
{
let (to_convert, selections) = resolve_selections_display(selections, map).tee();
@@ -5,6 +5,7 @@ use crate::actions::ShowSignatureHelp;
use crate::{Editor, EditorSettings, ToggleAutoSignatureHelp};
use gpui::{AppContext, ViewContext};
use language::markdown::parse_markdown;
+use language::BufferSnapshot;
use multi_buffer::{Anchor, ToOffset};
use settings::Settings;
use std::ops::Range;
@@ -94,13 +95,14 @@ impl Editor {
(a, b) if b <= buffer_snapshot.len() => a - 1..b,
(a, b) => a - 1..b - 1,
};
- let not_quote_like_brackets = |start: Range<usize>, end: Range<usize>| {
- let text = buffer_snapshot.text();
- let (text_start, text_end) = (text.get(start), text.get(end));
- QUOTE_PAIRS
- .into_iter()
- .all(|(start, end)| text_start != Some(start) && text_end != Some(end))
- };
+ let not_quote_like_brackets =
+ |buffer: &BufferSnapshot, start: Range<usize>, end: Range<usize>| {
+ let text_start = buffer.text_for_range(start).collect::<String>();
+ let text_end = buffer.text_for_range(end).collect::<String>();
+ QUOTE_PAIRS
+ .into_iter()
+ .all(|(start, end)| text_start != start && text_end != end)
+ };
let previous_position = old_cursor_position.to_offset(&buffer_snapshot);
let previous_brackets_range = bracket_range(previous_position);
@@ -15,7 +15,7 @@ fn task_context_with_editor(
};
let (selection, buffer, editor_snapshot) = {
let selection = editor.selections.newest_adjusted(cx);
- let Some((buffer, _, _)) = editor
+ let Some((buffer, _)) = editor
.buffer()
.read(cx)
.point_to_buffer_offset(selection.start, cx)
@@ -67,6 +67,13 @@ pub(crate) fn rust_lang() -> Arc<Language> {
("<" @open ">" @close)
("\"" @open "\"" @close)
(closure_parameters "|" @open "|" @close)"#})),
+ text_objects: Some(Cow::from(indoc! {r#"
+ (function_item
+ body: (_
+ "{"
+ (_)* @function.inside
+ "}" )) @function.around
+ "#})),
..Default::default()
})
.expect("Could not parse queries");
@@ -1,6 +1,6 @@
use crate::{
- display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DiffRowHighlight, DisplayPoint,
- Editor, MultiBuffer, RowExt,
+ display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
+ RowExt,
};
use collections::BTreeMap;
use futures::Future;
@@ -11,7 +11,7 @@ use gpui::{
};
use itertools::Itertools;
use language::{Buffer, BufferSnapshot, LanguageRegistry};
-use multi_buffer::{ExcerptRange, ToPoint};
+use multi_buffer::{ExcerptRange, MultiBufferRow};
use parking_lot::RwLock;
use project::{FakeFs, Project};
use std::{
@@ -333,85 +333,8 @@ impl EditorTestContext {
///
/// Diff hunks are indicated by lines starting with `+` and `-`.
#[track_caller]
- pub fn assert_state_with_diff(&mut self, expected_diff: String) {
- let has_diff_markers = expected_diff
- .lines()
- .any(|line| line.starts_with("+") || line.starts_with("-"));
- let expected_diff_text = expected_diff
- .split('\n')
- .map(|line| {
- let trimmed = line.trim();
- if trimmed.is_empty() {
- String::new()
- } else if has_diff_markers {
- line.to_string()
- } else {
- format!(" {line}")
- }
- })
- .join("\n");
-
- let actual_selections = self.editor_selections();
- let actual_marked_text =
- generate_marked_text(&self.buffer_text(), &actual_selections, true);
-
- // Read the actual diff from the editor's row highlights and block
- // decorations.
- let actual_diff = self.editor.update(&mut self.cx, |editor, cx| {
- let snapshot = editor.snapshot(cx);
- let insertions = editor
- .highlighted_rows::<DiffRowHighlight>()
- .map(|(range, _)| {
- let start = range.start.to_point(&snapshot.buffer_snapshot);
- let end = range.end.to_point(&snapshot.buffer_snapshot);
- start.row..end.row
- })
- .collect::<Vec<_>>();
- let deletions = editor
- .diff_map
- .hunks
- .iter()
- .filter_map(|hunk| {
- if hunk.blocks.is_empty() {
- return None;
- }
- let row = hunk
- .hunk_range
- .start
- .to_point(&snapshot.buffer_snapshot)
- .row;
- let (_, buffer, _) = editor
- .buffer()
- .read(cx)
- .excerpt_containing(hunk.hunk_range.start, cx)
- .expect("no excerpt for expanded buffer's hunk start");
- let buffer_id = buffer.read(cx).remote_id();
- let change_set = &editor
- .diff_map
- .diff_bases
- .get(&buffer_id)
- .expect("should have a diff base for expanded hunk")
- .change_set;
- let deleted_text = change_set
- .read(cx)
- .base_text
- .as_ref()
- .expect("no base text for expanded hunk")
- .read(cx)
- .as_rope()
- .slice(hunk.diff_base_byte_range.clone())
- .to_string();
- if let DiffHunkStatus::Modified | DiffHunkStatus::Removed = hunk.status {
- Some((row, deleted_text))
- } else {
- None
- }
- })
- .collect::<Vec<_>>();
- format_diff(actual_marked_text, deletions, insertions)
- });
-
- pretty_assertions::assert_eq!(actual_diff, expected_diff_text, "unexpected diff state");
+ pub fn assert_state_with_diff(&mut self, expected_diff_text: String) {
+ assert_state_with_diff(&self.editor, &mut self.cx, &expected_diff_text);
}
/// Make an assertion about the editor's text and the ranges and directions
@@ -504,44 +427,49 @@ impl EditorTestContext {
}
}
-fn format_diff(
- text: String,
- actual_deletions: Vec<(u32, String)>,
- actual_insertions: Vec<Range<u32>>,
-) -> String {
- let mut diff = String::new();
- for (row, line) in text.split('\n').enumerate() {
- let row = row as u32;
- if row > 0 {
- diff.push('\n');
- }
- if let Some(text) = actual_deletions
- .iter()
- .find_map(|(deletion_row, deleted_text)| {
- if *deletion_row == row {
- Some(deleted_text)
- } else {
- None
- }
- })
- {
- for line in text.lines() {
- diff.push('-');
- if !line.is_empty() {
- diff.push(' ');
- diff.push_str(line);
+#[track_caller]
+pub fn assert_state_with_diff(
+ editor: &View<Editor>,
+ cx: &mut VisualTestContext,
+ expected_diff_text: &str,
+) {
+ let (snapshot, selections) = editor.update(cx, |editor, cx| {
+ (
+ editor.snapshot(cx).buffer_snapshot.clone(),
+ editor.selections.ranges::<usize>(cx),
+ )
+ });
+
+ let actual_marked_text = generate_marked_text(&snapshot.text(), &selections, true);
+
+ // Read the actual diff.
+ let line_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
+ let has_diff = line_infos.iter().any(|info| info.diff_status.is_some());
+ let actual_diff = actual_marked_text
+ .split('\n')
+ .zip(line_infos)
+ .map(|(line, info)| {
+ let mut marker = match info.diff_status {
+ Some(DiffHunkStatus::Added) => "+ ",
+ Some(DiffHunkStatus::Removed) => "- ",
+ Some(DiffHunkStatus::Modified) => unreachable!(),
+ None => {
+ if has_diff {
+ " "
+ } else {
+ ""
+ }
}
- diff.push('\n');
+ };
+ if line.is_empty() {
+ marker = marker.trim();
}
- }
- let marker = if actual_insertions.iter().any(|range| range.contains(&row)) {
- "+ "
- } else {
- " "
- };
- diff.push_str(format!("{marker}{line}").trim_end());
- }
- diff
+ format!("{marker}{line}")
+ })
+ .collect::<Vec<_>>()
+ .join("\n");
+
+ pretty_assertions::assert_eq!(actual_diff, expected_diff_text, "unexpected diff state");
}
impl Deref for EditorTestContext {
@@ -9,7 +9,7 @@ use futures::future::join_all;
pub use open_path_prompt::OpenPathDelegate;
use collections::HashMap;
-use editor::{scroll::Autoscroll, Bias, Editor};
+use editor::Editor;
use file_finder_settings::{FileFinderSettings, FileFinderWidth};
use file_icons::FileIcons;
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
@@ -1162,13 +1162,7 @@ impl PickerDelegate for FileFinderDelegate {
active_editor
.downgrade()
.update(&mut cx, |editor, cx| {
- let snapshot = editor.snapshot(cx).display_snapshot;
- let point = snapshot
- .buffer_snapshot
- .clip_point(Point::new(row, col), Bias::Left);
- editor.change_selections(Some(Autoscroll::center()), cx, |s| {
- s.select_ranges([point..point])
- });
+ editor.go_to_singleton_buffer_point(Point::new(row, col), cx);
})
.log_err();
}
@@ -74,7 +74,7 @@ impl BufferDiff {
}
}
- pub async fn build(diff_base: &str, buffer: &text::BufferSnapshot) -> Self {
+ pub fn build(diff_base: &str, buffer: &text::BufferSnapshot) -> Self {
let mut tree = SumTree::new(buffer);
let buffer_text = buffer.as_rope().to_string();
@@ -119,32 +119,38 @@ impl BufferDiff {
!before_start && !after_end
});
- let anchor_iter = std::iter::from_fn(move || {
+ let anchor_iter = iter::from_fn(move || {
cursor.next(buffer);
cursor.item()
})
.flat_map(move |hunk| {
[
- (&hunk.buffer_range.start, hunk.diff_base_byte_range.start),
- (&hunk.buffer_range.end, hunk.diff_base_byte_range.end),
+ (
+ &hunk.buffer_range.start,
+ (hunk.buffer_range.start, hunk.diff_base_byte_range.start),
+ ),
+ (
+ &hunk.buffer_range.end,
+ (hunk.buffer_range.end, hunk.diff_base_byte_range.end),
+ ),
]
- .into_iter()
});
let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
iter::from_fn(move || {
- let (start_point, start_base) = summaries.next()?;
- let (mut end_point, end_base) = summaries.next()?;
+ let (start_point, (start_anchor, start_base)) = summaries.next()?;
+ let (mut end_point, (mut end_anchor, end_base)) = summaries.next()?;
if end_point.column > 0 {
end_point.row += 1;
end_point.column = 0;
+ end_anchor = buffer.anchor_before(end_point);
}
Some(DiffHunk {
row_range: start_point.row..end_point.row,
diff_base_byte_range: start_base..end_base,
- buffer_range: buffer.anchor_before(start_point)..buffer.anchor_after(end_point),
+ buffer_range: start_anchor..end_anchor,
})
})
}
@@ -162,7 +168,7 @@ impl BufferDiff {
!before_start && !after_end
});
- std::iter::from_fn(move || {
+ iter::from_fn(move || {
cursor.prev(buffer);
let hunk = cursor.item()?;
@@ -186,8 +192,8 @@ impl BufferDiff {
self.tree = SumTree::new(buffer);
}
- pub async fn update(&mut self, diff_base: &Rope, buffer: &text::BufferSnapshot) {
- *self = Self::build(&diff_base.to_string(), buffer).await;
+ pub fn update(&mut self, diff_base: &Rope, buffer: &text::BufferSnapshot) {
+ *self = Self::build(&diff_base.to_string(), buffer);
}
#[cfg(test)]
@@ -346,7 +352,7 @@ mod tests {
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let mut diff = BufferDiff::new(&buffer);
- smol::block_on(diff.update(&diff_base_rope, &buffer));
+ diff.update(&diff_base_rope, &buffer);
assert_hunks(
diff.hunks(&buffer),
&buffer,
@@ -355,7 +361,7 @@ mod tests {
);
buffer.edit([(0..0, "point five\n")]);
- smol::block_on(diff.update(&diff_base_rope, &buffer));
+ diff.update(&diff_base_rope, &buffer);
assert_hunks(
diff.hunks(&buffer),
&buffer,
@@ -407,7 +413,7 @@ mod tests {
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let mut diff = BufferDiff::new(&buffer);
- smol::block_on(diff.update(&diff_base_rope, &buffer));
+ diff.update(&diff_base_rope, &buffer);
assert_eq!(diff.hunks(&buffer).count(), 8);
assert_hunks(
@@ -16,6 +16,7 @@ doctest = false
anyhow.workspace = true
editor.workspace = true
gpui.workspace = true
+language.workspace = true
menu.workspace = true
schemars.workspace = true
serde.workspace = true
@@ -20,7 +20,7 @@ pub(crate) struct SelectionStats {
}
pub struct CursorPosition {
- position: Option<Point>,
+ position: Option<(Point, bool)>,
selected_count: SelectionStats,
context: Option<FocusHandle>,
workspace: WeakView<Workspace>,
@@ -97,8 +97,11 @@ impl CursorPosition {
}
}
}
- cursor_position.position =
- last_selection.map(|s| s.head().to_point(&buffer));
+ cursor_position.position = last_selection.and_then(|s| {
+ buffer
+ .point_to_buffer_point(s.head().to_point(&buffer))
+ .map(|(_, point, is_main_buffer)| (point, is_main_buffer))
+ });
cursor_position.context = Some(editor.focus_handle(cx));
}
}
@@ -163,9 +166,10 @@ impl CursorPosition {
impl Render for CursorPosition {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
- div().when_some(self.position, |el, position| {
+ div().when_some(self.position, |el, (position, is_main_buffer)| {
let mut text = format!(
- "{}{FILE_ROW_COLUMN_DELIMITER}{}",
+ "{}{}{FILE_ROW_COLUMN_DELIMITER}{}",
+ if is_main_buffer { "" } else { "(deleted) " },
position.row + 1,
position.column + 1
);
@@ -183,8 +187,12 @@ impl Render for CursorPosition {
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))
{
- workspace
- .toggle_modal(cx, |cx| crate::GoToLine::new(editor, cx))
+ if let Some((_, buffer, _)) = editor.read(cx).active_excerpt(cx)
+ {
+ workspace.toggle_modal(cx, |cx| {
+ crate::GoToLine::new(editor, buffer, cx)
+ })
+ }
}
});
}
@@ -1,13 +1,15 @@
pub mod cursor_position;
use cursor_position::LineIndicatorFormat;
-use editor::{scroll::Autoscroll, Editor};
+use editor::{scroll::Autoscroll, Anchor, Editor, MultiBuffer, ToPoint};
use gpui::{
div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle,
- FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
+ FocusableView, Model, Render, SharedString, Styled, Subscription, View, ViewContext,
+ VisualContext,
};
+use language::Buffer;
use settings::Settings;
-use text::{Bias, Point};
+use text::Point;
use theme::ActiveTheme;
use ui::prelude::*;
use util::paths::FILE_ROW_COLUMN_DELIMITER;
@@ -21,6 +23,7 @@ pub fn init(cx: &mut AppContext) {
pub struct GoToLine {
line_editor: View<Editor>,
active_editor: View<Editor>,
+ active_buffer: Model<Buffer>,
current_text: SharedString,
prev_scroll_position: Option<gpui::Point<f32>>,
_subscriptions: Vec<Subscription>,
@@ -42,22 +45,43 @@ impl GoToLine {
let handle = cx.view().downgrade();
editor
.register_action(move |_: &editor::actions::ToggleGoToLine, cx| {
- let Some(editor) = handle.upgrade() else {
+ let Some(editor_handle) = handle.upgrade() else {
return;
};
- let Some(workspace) = editor.read(cx).workspace() else {
+ let Some(workspace) = editor_handle.read(cx).workspace() else {
+ return;
+ };
+ let editor = editor_handle.read(cx);
+ let Some((_, buffer, _)) = editor.active_excerpt(cx) else {
return;
};
workspace.update(cx, |workspace, cx| {
- workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx));
+ workspace.toggle_modal(cx, move |cx| GoToLine::new(editor_handle, buffer, cx));
})
})
.detach();
}
- pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
- let cursor =
- active_editor.update(cx, |editor, cx| editor.selections.last::<Point>(cx).head());
+ pub fn new(
+ active_editor: View<Editor>,
+ active_buffer: Model<Buffer>,
+ cx: &mut ViewContext<Self>,
+ ) -> Self {
+ let (cursor, last_line, scroll_position) = active_editor.update(cx, |editor, cx| {
+ let cursor = editor.selections.last::<Point>(cx).head();
+ let snapshot = active_buffer.read(cx).snapshot();
+
+ let last_line = editor
+ .buffer()
+ .read(cx)
+ .excerpts_for_buffer(&active_buffer, cx)
+ .into_iter()
+ .map(move |(_, range)| text::ToPoint::to_point(&range.context.end, &snapshot).row)
+ .max()
+ .unwrap_or(0);
+
+ (cursor, last_line, editor.scroll_position(cx))
+ });
let line = cursor.row + 1;
let column = cursor.column + 1;
@@ -69,15 +93,17 @@ impl GoToLine {
});
let line_editor_change = cx.subscribe(&line_editor, Self::on_line_editor_event);
- let editor = active_editor.read(cx);
- let last_line = editor.buffer().read(cx).snapshot(cx).max_point().row;
- let scroll_position = active_editor.update(cx, |editor, cx| editor.scroll_position(cx));
-
- let current_text = format!("{} of {} (column {})", line, last_line + 1, column);
+ let current_text = format!(
+ "Current Line: {} of {} (column {})",
+ line,
+ last_line + 1,
+ column
+ );
Self {
line_editor,
active_editor,
+ active_buffer,
current_text: current_text.into(),
prev_scroll_position: Some(scroll_position),
_subscriptions: vec![line_editor_change, cx.on_release(Self::release)],
@@ -113,35 +139,40 @@ impl GoToLine {
}
fn highlight_current_line(&mut self, cx: &mut ViewContext<Self>) {
- if let Some(point) = self.point_from_query(cx) {
- self.active_editor.update(cx, |active_editor, cx| {
- let snapshot = active_editor.snapshot(cx).display_snapshot;
- let start = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
- let end = start + Point::new(1, 0);
- let start = snapshot.buffer_snapshot.anchor_before(start);
- let end = snapshot.buffer_snapshot.anchor_after(end);
- active_editor.clear_row_highlights::<GoToLineRowHighlights>();
- active_editor.highlight_rows::<GoToLineRowHighlights>(
- start..end,
- cx.theme().colors().editor_highlighted_line_background,
- true,
- cx,
- );
- active_editor.request_autoscroll(Autoscroll::center(), cx);
- });
- cx.notify();
- }
+ self.active_editor.update(cx, |editor, cx| {
+ editor.clear_row_highlights::<GoToLineRowHighlights>();
+ let multibuffer = editor.buffer().read(cx);
+ let snapshot = multibuffer.snapshot(cx);
+ let Some(start) = self.anchor_from_query(&multibuffer, cx) else {
+ return;
+ };
+ let start_point = start.to_point(&snapshot);
+ let end_point = start_point + Point::new(1, 0);
+ let end = snapshot.anchor_after(end_point);
+ editor.highlight_rows::<GoToLineRowHighlights>(
+ start..end,
+ cx.theme().colors().editor_highlighted_line_background,
+ true,
+ cx,
+ );
+ editor.request_autoscroll(Autoscroll::center(), cx);
+ });
+ cx.notify();
}
- fn point_from_query(&self, cx: &ViewContext<Self>) -> Option<Point> {
- let (row, column) = self.line_column_from_query(cx);
- Some(Point::new(
- row?.saturating_sub(1),
- column.unwrap_or(0).saturating_sub(1),
- ))
+ fn anchor_from_query(
+ &self,
+ multibuffer: &MultiBuffer,
+ cx: &ViewContext<Editor>,
+ ) -> Option<Anchor> {
+ let (Some(row), column) = self.line_column_from_query(cx) else {
+ return None;
+ };
+ let point = Point::new(row.saturating_sub(1), column.unwrap_or(0).saturating_sub(1));
+ multibuffer.buffer_point_to_anchor(&self.active_buffer, point, cx)
}
- fn line_column_from_query(&self, cx: &ViewContext<Self>) -> (Option<u32>, Option<u32>) {
+ fn line_column_from_query(&self, cx: &AppContext) -> (Option<u32>, Option<u32>) {
let input = self.line_editor.read(cx).text(cx);
let mut components = input
.splitn(2, FILE_ROW_COLUMN_DELIMITER)
@@ -157,18 +188,18 @@ impl GoToLine {
}
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
- if let Some(point) = self.point_from_query(cx) {
- self.active_editor.update(cx, |editor, cx| {
- let snapshot = editor.snapshot(cx).display_snapshot;
- let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
- editor.change_selections(Some(Autoscroll::center()), cx, |s| {
- s.select_ranges([point..point])
- });
- editor.focus(cx);
- cx.notify();
+ self.active_editor.update(cx, |editor, cx| {
+ let multibuffer = editor.buffer().read(cx);
+ let Some(start) = self.anchor_from_query(&multibuffer, cx) else {
+ return;
+ };
+ editor.change_selections(Some(Autoscroll::center()), cx, |s| {
+ s.select_anchor_ranges([start..start])
});
- self.prev_scroll_position.take();
- }
+ editor.focus(cx);
+ cx.notify()
+ });
+ self.prev_scroll_position.take();
cx.emit(DismissEvent);
}
@@ -205,7 +236,6 @@ impl Render for GoToLine {
.px_2()
.py_1()
.gap_1()
- .child(Label::new("Current Line:").color(Color::Muted))
.child(Label::new(help_text).color(Color::Muted)),
)
}
@@ -6,7 +6,7 @@ pub use crate::{
};
use crate::{
diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
- language_settings::{language_settings, IndentGuideSettings, LanguageSettings},
+ language_settings::{language_settings, LanguageSettings},
markdown::parse_markdown,
outline::OutlineItem,
syntax_map::{
@@ -144,7 +144,7 @@ struct BufferBranchState {
/// An immutable, cheaply cloneable representation of a fixed
/// state of a buffer.
pub struct BufferSnapshot {
- text: text::BufferSnapshot,
+ pub text: text::BufferSnapshot,
pub(crate) syntax: SyntaxSnapshot,
file: Option<Arc<dyn File>>,
diagnostics: SmallVec<[(LanguageServerId, DiagnosticSet); 2]>,
@@ -587,22 +587,6 @@ pub struct Runnable {
pub buffer: BufferId,
}
-#[derive(Clone, Debug, PartialEq)]
-pub struct IndentGuide {
- pub buffer_id: BufferId,
- pub start_row: BufferRow,
- pub end_row: BufferRow,
- pub depth: u32,
- pub tab_size: u32,
- pub settings: IndentGuideSettings,
-}
-
-impl IndentGuide {
- pub fn indent_level(&self) -> u32 {
- self.depth * self.tab_size
- }
-}
-
#[derive(Clone)]
pub struct EditPreview {
applied_edits_snapshot: text::BufferSnapshot,
@@ -937,6 +921,36 @@ impl Buffer {
}
}
+ pub fn build_snapshot(
+ text: Rope,
+ language: Option<Arc<Language>>,
+ language_registry: Option<Arc<LanguageRegistry>>,
+ cx: &mut AppContext,
+ ) -> impl Future<Output = BufferSnapshot> {
+ let entity_id = cx.reserve_model::<Self>().entity_id();
+ let buffer_id = entity_id.as_non_zero_u64().into();
+ async move {
+ let text =
+ TextBuffer::new_normalized(0, buffer_id, Default::default(), text).snapshot();
+ let mut syntax = SyntaxMap::new(&text).snapshot();
+ if let Some(language) = language.clone() {
+ let text = text.clone();
+ let language = language.clone();
+ let language_registry = language_registry.clone();
+ syntax.reparse(&text, language_registry, language);
+ }
+ BufferSnapshot {
+ text,
+ syntax,
+ file: None,
+ diagnostics: Default::default(),
+ remote_selections: Default::default(),
+ language,
+ non_text_state_update_count: 0,
+ }
+ }
+ }
+
/// Retrieve a snapshot of the buffer's current state. This is computationally
/// cheap, and allows reading from the buffer on a background thread.
pub fn snapshot(&self) -> BufferSnapshot {
@@ -2633,7 +2647,8 @@ impl Buffer {
last_end = Some(range.end);
let new_text_len = rng.gen_range(0..10);
- let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect();
+ let mut new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect();
+ new_text = new_text.to_uppercase();
edits.push((range, new_text));
}
@@ -3730,10 +3745,8 @@ impl BufferSnapshot {
pub fn runnable_ranges(
&self,
- range: Range<Anchor>,
+ offset_range: Range<usize>,
) -> impl Iterator<Item = RunnableRange> + '_ {
- let offset_range = range.start.to_offset(self)..range.end.to_offset(self);
-
let mut syntax_matches = self.syntax.matches(offset_range, self, |grammar| {
grammar.runnable_config.as_ref().map(|config| &config.query)
});
@@ -3833,245 +3846,6 @@ impl BufferSnapshot {
})
}
- pub fn indent_guides_in_range(
- &self,
- range: Range<Anchor>,
- ignore_disabled_for_language: bool,
- cx: &AppContext,
- ) -> Vec<IndentGuide> {
- let language_settings =
- language_settings(self.language().map(|l| l.name()), self.file.as_ref(), cx);
- let settings = language_settings.indent_guides;
- if !ignore_disabled_for_language && !settings.enabled {
- return Vec::new();
- }
- let tab_size = language_settings.tab_size.get() as u32;
-
- let start_row = range.start.to_point(self).row;
- let end_row = range.end.to_point(self).row;
- let row_range = start_row..end_row + 1;
-
- let mut row_indents = self.line_indents_in_row_range(row_range.clone());
-
- let mut result_vec = Vec::new();
- let mut indent_stack = SmallVec::<[IndentGuide; 8]>::new();
-
- while let Some((first_row, mut line_indent)) = row_indents.next() {
- let current_depth = indent_stack.len() as u32;
-
- // When encountering empty, continue until found useful line indent
- // then add to the indent stack with the depth found
- let mut found_indent = false;
- let mut last_row = first_row;
- if line_indent.is_line_empty() {
- let mut trailing_row = end_row;
- while !found_indent {
- let (target_row, new_line_indent) =
- if let Some(display_row) = row_indents.next() {
- display_row
- } else {
- // This means we reached the end of the given range and found empty lines at the end.
- // We need to traverse further until we find a non-empty line to know if we need to add
- // an indent guide for the last visible indent.
- trailing_row += 1;
-
- const TRAILING_ROW_SEARCH_LIMIT: u32 = 25;
- if trailing_row > self.max_point().row
- || trailing_row > end_row + TRAILING_ROW_SEARCH_LIMIT
- {
- break;
- }
- let new_line_indent = self.line_indent_for_row(trailing_row);
- (trailing_row, new_line_indent)
- };
-
- if new_line_indent.is_line_empty() {
- continue;
- }
- last_row = target_row.min(end_row);
- line_indent = new_line_indent;
- found_indent = true;
- break;
- }
- } else {
- found_indent = true
- }
-
- let depth = if found_indent {
- line_indent.len(tab_size) / tab_size
- + ((line_indent.len(tab_size) % tab_size) > 0) as u32
- } else {
- current_depth
- };
-
- match depth.cmp(¤t_depth) {
- Ordering::Less => {
- for _ in 0..(current_depth - depth) {
- let mut indent = indent_stack.pop().unwrap();
- if last_row != first_row {
- // In this case, we landed on an empty row, had to seek forward,
- // and discovered that the indent we where on is ending.
- // This means that the last display row must
- // be on line that ends this indent range, so we
- // should display the range up to the first non-empty line
- indent.end_row = first_row.saturating_sub(1);
- }
-
- result_vec.push(indent)
- }
- }
- Ordering::Greater => {
- for next_depth in current_depth..depth {
- indent_stack.push(IndentGuide {
- buffer_id: self.remote_id(),
- start_row: first_row,
- end_row: last_row,
- depth: next_depth,
- tab_size,
- settings,
- });
- }
- }
- _ => {}
- }
-
- for indent in indent_stack.iter_mut() {
- indent.end_row = last_row;
- }
- }
-
- result_vec.extend(indent_stack);
-
- result_vec
- }
-
- pub async fn enclosing_indent(
- &self,
- mut buffer_row: BufferRow,
- ) -> Option<(Range<BufferRow>, LineIndent)> {
- let max_row = self.max_point().row;
- if buffer_row >= max_row {
- return None;
- }
-
- let mut target_indent = self.line_indent_for_row(buffer_row);
-
- // If the current row is at the start of an indented block, we want to return this
- // block as the enclosing indent.
- if !target_indent.is_line_empty() && buffer_row < max_row {
- let next_line_indent = self.line_indent_for_row(buffer_row + 1);
- if !next_line_indent.is_line_empty()
- && target_indent.raw_len() < next_line_indent.raw_len()
- {
- target_indent = next_line_indent;
- buffer_row += 1;
- }
- }
-
- const SEARCH_ROW_LIMIT: u32 = 25000;
- const SEARCH_WHITESPACE_ROW_LIMIT: u32 = 2500;
- const YIELD_INTERVAL: u32 = 100;
-
- let mut accessed_row_counter = 0;
-
- // If there is a blank line at the current row, search for the next non indented lines
- if target_indent.is_line_empty() {
- let start = buffer_row.saturating_sub(SEARCH_WHITESPACE_ROW_LIMIT);
- let end = (max_row + 1).min(buffer_row + SEARCH_WHITESPACE_ROW_LIMIT);
-
- let mut non_empty_line_above = None;
- for (row, indent) in self
- .text
- .reversed_line_indents_in_row_range(start..buffer_row)
- {
- accessed_row_counter += 1;
- if accessed_row_counter == YIELD_INTERVAL {
- accessed_row_counter = 0;
- yield_now().await;
- }
- if !indent.is_line_empty() {
- non_empty_line_above = Some((row, indent));
- break;
- }
- }
-
- let mut non_empty_line_below = None;
- for (row, indent) in self.text.line_indents_in_row_range((buffer_row + 1)..end) {
- accessed_row_counter += 1;
- if accessed_row_counter == YIELD_INTERVAL {
- accessed_row_counter = 0;
- yield_now().await;
- }
- if !indent.is_line_empty() {
- non_empty_line_below = Some((row, indent));
- break;
- }
- }
-
- let (row, indent) = match (non_empty_line_above, non_empty_line_below) {
- (Some((above_row, above_indent)), Some((below_row, below_indent))) => {
- if above_indent.raw_len() >= below_indent.raw_len() {
- (above_row, above_indent)
- } else {
- (below_row, below_indent)
- }
- }
- (Some(above), None) => above,
- (None, Some(below)) => below,
- _ => return None,
- };
-
- target_indent = indent;
- buffer_row = row;
- }
-
- let start = buffer_row.saturating_sub(SEARCH_ROW_LIMIT);
- let end = (max_row + 1).min(buffer_row + SEARCH_ROW_LIMIT);
-
- let mut start_indent = None;
- for (row, indent) in self
- .text
- .reversed_line_indents_in_row_range(start..buffer_row)
- {
- accessed_row_counter += 1;
- if accessed_row_counter == YIELD_INTERVAL {
- accessed_row_counter = 0;
- yield_now().await;
- }
- if !indent.is_line_empty() && indent.raw_len() < target_indent.raw_len() {
- start_indent = Some((row, indent));
- break;
- }
- }
- let (start_row, start_indent_size) = start_indent?;
-
- let mut end_indent = (end, None);
- for (row, indent) in self.text.line_indents_in_row_range((buffer_row + 1)..end) {
- accessed_row_counter += 1;
- if accessed_row_counter == YIELD_INTERVAL {
- accessed_row_counter = 0;
- yield_now().await;
- }
- if !indent.is_line_empty() && indent.raw_len() < target_indent.raw_len() {
- end_indent = (row.saturating_sub(1), Some(indent));
- break;
- }
- }
- let (end_row, end_indent_size) = end_indent;
-
- let indent = if let Some(end_indent_size) = end_indent_size {
- if start_indent_size.raw_len() > end_indent_size.raw_len() {
- start_indent_size
- } else {
- end_indent_size
- }
- } else {
- start_indent_size
- };
-
- Some((start_row..end_row, indent))
- }
-
/// Returns selections for remote peers intersecting the given range.
#[allow(clippy::type_complexity)]
pub fn selections_in_range(
@@ -4395,6 +4169,10 @@ impl<'a> BufferChunks<'a> {
self.range.start
}
+ pub fn range(&self) -> Range<usize> {
+ self.range.clone()
+ }
+
fn update_diagnostic_depths(&mut self, endpoint: DiagnosticEndpoint) {
let depth = match endpoint.severity {
DiagnosticSeverity::ERROR => &mut self.error_depth,
@@ -21,7 +21,7 @@ use std::{
};
use syntax_map::TreeSitterOptions;
use text::network::Network;
-use text::{BufferId, LineEnding, LineIndent};
+use text::{BufferId, LineEnding};
use text::{Point, ToPoint};
use unindent::Unindent as _;
use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter};
@@ -2475,92 +2475,6 @@ fn test_serialization(cx: &mut gpui::AppContext) {
assert_eq!(buffer2.read(cx).text(), "abcDF");
}
-#[gpui::test]
-async fn test_find_matching_indent(cx: &mut TestAppContext) {
- cx.update(|cx| init_settings(cx, |_| {}));
-
- async fn enclosing_indent(
- text: impl Into<String>,
- buffer_row: u32,
- cx: &mut TestAppContext,
- ) -> Option<(Range<u32>, LineIndent)> {
- let buffer = cx.new_model(|cx| Buffer::local(text, cx));
- let snapshot = cx.read(|cx| buffer.read(cx).snapshot());
- snapshot.enclosing_indent(buffer_row).await
- }
-
- assert_eq!(
- enclosing_indent(
- "
- fn b() {
- if c {
- let d = 2;
- }
- }"
- .unindent(),
- 1,
- cx,
- )
- .await,
- Some((
- 1..2,
- LineIndent {
- tabs: 0,
- spaces: 4,
- line_blank: false,
- }
- ))
- );
-
- assert_eq!(
- enclosing_indent(
- "
- fn b() {
- if c {
- let d = 2;
- }
- }"
- .unindent(),
- 2,
- cx,
- )
- .await,
- Some((
- 1..2,
- LineIndent {
- tabs: 0,
- spaces: 4,
- line_blank: false,
- }
- ))
- );
-
- assert_eq!(
- enclosing_indent(
- "
- fn b() {
- if c {
- let d = 2;
-
- let e = 5;
- }
- }"
- .unindent(),
- 3,
- cx,
- )
- .await,
- Some((
- 1..4,
- LineIndent {
- tabs: 0,
- spaces: 4,
- line_blank: false,
- }
- ))
- );
-}
-
#[gpui::test]
fn test_branch_and_merge(cx: &mut TestAppContext) {
cx.update(|cx| init_settings(cx, |_| {}));
@@ -131,15 +131,15 @@ impl SyntaxTreeView {
let snapshot = editor_state
.editor
.update(cx, |editor, cx| editor.snapshot(cx));
- let (excerpt, buffer, range) = editor_state.editor.update(cx, |editor, cx| {
+ let (buffer, range, excerpt_id) = editor_state.editor.update(cx, |editor, cx| {
let selection_range = editor.selections.last::<usize>(cx).range();
let multi_buffer = editor.buffer().read(cx);
- let (excerpt, range) = snapshot
+ let (buffer, range, excerpt_id) = snapshot
.buffer_snapshot
.range_to_buffer_ranges(selection_range)
.pop()?;
- let buffer = multi_buffer.buffer(excerpt.buffer_id()).unwrap().clone();
- Some((excerpt, buffer, range))
+ let buffer = multi_buffer.buffer(buffer.remote_id()).unwrap().clone();
+ Some((buffer, range, excerpt_id))
})?;
// If the cursor has moved into a different excerpt, retrieve a new syntax layer
@@ -148,16 +148,16 @@ impl SyntaxTreeView {
.active_buffer
.get_or_insert_with(|| BufferState {
buffer: buffer.clone(),
- excerpt_id: excerpt.id(),
+ excerpt_id,
active_layer: None,
});
let mut prev_layer = None;
if did_reparse {
prev_layer = buffer_state.active_layer.take();
}
- if buffer_state.buffer != buffer || buffer_state.excerpt_id != excerpt.id() {
+ if buffer_state.buffer != buffer || buffer_state.excerpt_id != excerpt_id {
buffer_state.buffer = buffer.clone();
- buffer_state.excerpt_id = excerpt.id();
+ buffer_state.excerpt_id = excerpt_id;
buffer_state.active_layer = None;
}
@@ -27,12 +27,16 @@ collections.workspace = true
ctor.workspace = true
env_logger.workspace = true
futures.workspace = true
+git.workspace = true
gpui.workspace = true
itertools.workspace = true
language.workspace = true
log.workspace = true
parking_lot.workspace = true
+project.workspace = true
rand.workspace = true
+rope.workspace = true
+smol.workspace = true
settings.workspace = true
serde.workspace = true
smallvec.workspace = true
@@ -45,7 +49,10 @@ util.workspace = true
[dev-dependencies]
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
+project = { workspace = true, features = ["test-support"] }
rand.workspace = true
settings = { workspace = true, features = ["test-support"] }
text = { workspace = true, features = ["test-support"] }
util = { workspace = true, features = ["test-support"] }
+pretty_assertions.workspace = true
+indoc.workspace = true
@@ -12,14 +12,38 @@ pub struct Anchor {
pub buffer_id: Option<BufferId>,
pub excerpt_id: ExcerptId,
pub text_anchor: text::Anchor,
+ pub diff_base_anchor: Option<text::Anchor>,
}
impl Anchor {
+ pub fn in_buffer(
+ excerpt_id: ExcerptId,
+ buffer_id: BufferId,
+ text_anchor: text::Anchor,
+ ) -> Self {
+ Self {
+ buffer_id: Some(buffer_id),
+ excerpt_id,
+ text_anchor,
+ diff_base_anchor: None,
+ }
+ }
+
+ pub fn range_in_buffer(
+ excerpt_id: ExcerptId,
+ buffer_id: BufferId,
+ range: Range<text::Anchor>,
+ ) -> Range<Self> {
+ Self::in_buffer(excerpt_id, buffer_id, range.start)
+ ..Self::in_buffer(excerpt_id, buffer_id, range.end)
+ }
+
pub fn min() -> Self {
Self {
buffer_id: None,
excerpt_id: ExcerptId::min(),
text_anchor: text::Anchor::MIN,
+ diff_base_anchor: None,
}
}
@@ -28,22 +52,47 @@ impl Anchor {
buffer_id: None,
excerpt_id: ExcerptId::max(),
text_anchor: text::Anchor::MAX,
+ diff_base_anchor: None,
}
}
pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id, snapshot);
- if excerpt_id_cmp.is_eq() {
- if self.excerpt_id == ExcerptId::min() || self.excerpt_id == ExcerptId::max() {
- Ordering::Equal
- } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
- self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer)
- } else {
- Ordering::Equal
+ if excerpt_id_cmp.is_ne() {
+ return excerpt_id_cmp;
+ }
+ if self.excerpt_id == ExcerptId::min() || self.excerpt_id == ExcerptId::max() {
+ return Ordering::Equal;
+ }
+ if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
+ let text_cmp = self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer);
+ if text_cmp.is_ne() {
+ return text_cmp;
+ }
+ if self.diff_base_anchor.is_some() || other.diff_base_anchor.is_some() {
+ if let Some(diff_base) = snapshot.diffs.get(&excerpt.buffer_id) {
+ let self_anchor = self
+ .diff_base_anchor
+ .filter(|a| diff_base.base_text.can_resolve(a));
+ let other_anchor = other
+ .diff_base_anchor
+ .filter(|a| diff_base.base_text.can_resolve(a));
+ return match (self_anchor, other_anchor) {
+ (Some(a), Some(b)) => a.cmp(&b, &diff_base.base_text),
+ (Some(_), None) => match other.text_anchor.bias {
+ Bias::Left => Ordering::Greater,
+ Bias::Right => Ordering::Less,
+ },
+ (None, Some(_)) => match self.text_anchor.bias {
+ Bias::Left => Ordering::Less,
+ Bias::Right => Ordering::Greater,
+ },
+ (None, None) => Ordering::Equal,
+ };
+ }
}
- } else {
- excerpt_id_cmp
}
+ Ordering::Equal
}
pub fn bias(&self) -> Bias {
@@ -57,6 +106,14 @@ impl Anchor {
buffer_id: self.buffer_id,
excerpt_id: self.excerpt_id,
text_anchor: self.text_anchor.bias_left(&excerpt.buffer),
+ diff_base_anchor: self.diff_base_anchor.map(|a| {
+ if let Some(base) = snapshot.diffs.get(&excerpt.buffer_id) {
+ if a.buffer_id == Some(base.base_text.remote_id()) {
+ return a.bias_left(&base.base_text);
+ }
+ }
+ a
+ }),
};
}
}
@@ -70,6 +127,14 @@ impl Anchor {
buffer_id: self.buffer_id,
excerpt_id: self.excerpt_id,
text_anchor: self.text_anchor.bias_right(&excerpt.buffer),
+ diff_base_anchor: self.diff_base_anchor.map(|a| {
+ if let Some(base) = snapshot.diffs.get(&excerpt.buffer_id) {
+ if a.buffer_id == Some(base.base_text.remote_id()) {
+ return a.bias_right(&base.base_text);
+ }
+ }
+ a
+ }),
};
}
}
@@ -1,27 +1,34 @@
mod anchor;
#[cfg(test)]
mod multi_buffer_tests;
+mod position;
pub use anchor::{Anchor, AnchorRangeExt, Offset};
+pub use position::{TypedOffset, TypedPoint, TypedRow};
+
use anyhow::{anyhow, Result};
use clock::ReplicaId;
use collections::{BTreeMap, Bound, HashMap, HashSet};
use futures::{channel::mpsc, SinkExt};
+use git::diff::DiffHunkStatus;
use gpui::{AppContext, EntityId, EventEmitter, Model, ModelContext, Task};
use itertools::Itertools;
use language::{
- language_settings::{language_settings, LanguageSettings},
+ language_settings::{language_settings, IndentGuideSettings, LanguageSettings},
AutoindentMode, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability, CharClassifier,
- CharKind, Chunk, CursorShape, DiagnosticEntry, DiskState, File, IndentGuide, IndentSize,
- Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, PointUtf16,
- Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _,
- TransactionId, Unclipped,
+ CharKind, Chunk, CursorShape, DiagnosticEntry, DiskState, File, IndentSize, Language,
+ LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection,
+ TextDimension, TextObject, ToOffset as _, ToPoint as _, TransactionId, TreeSitterOptions,
+ Unclipped,
};
+use project::buffer_store::BufferChangeSet;
+use rope::DimensionPair;
use smallvec::SmallVec;
+use smol::future::yield_now;
use std::{
any::type_name,
borrow::Cow,
- cell::{Ref, RefCell},
+ cell::{Ref, RefCell, RefMut},
cmp, fmt,
future::Future,
io,
@@ -32,14 +39,13 @@ use std::{
sync::Arc,
time::{Duration, Instant},
};
-use sum_tree::{Bias, Cursor, SumTree};
+use sum_tree::{Bias, Cursor, SumTree, TreeMap};
use text::{
locator::Locator,
subscription::{Subscription, Topic},
- BufferId, Edit, TextSummary,
+ BufferId, Edit, LineIndent, TextSummary,
};
use theme::SyntaxTheme;
-
use util::post_inc;
#[cfg(any(test, feature = "test-support"))]
@@ -50,12 +56,6 @@ const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExcerptId(usize);
-impl From<ExcerptId> for EntityId {
- fn from(id: ExcerptId) -> Self {
- EntityId::from(id.0 as u64)
- }
-}
-
/// One or more [`Buffers`](Buffer) being edited in a single view.
///
/// See <https://zed.dev/features#multi-buffers>
@@ -65,6 +65,8 @@ pub struct MultiBuffer {
snapshot: RefCell<MultiBufferSnapshot>,
/// Contains the state of the buffers being edited
buffers: RefCell<HashMap<BufferId, BufferState>>,
+ diff_bases: HashMap<BufferId, ChangeSetState>,
+ all_diff_hunks_expanded: bool,
subscriptions: Topic,
/// If true, the multi-buffer only contains a single [`Buffer`] and a single [`Excerpt`]
singleton: bool,
@@ -119,11 +121,27 @@ pub struct MultiBufferDiffHunk {
pub buffer_id: BufferId,
/// The range of the underlying buffer that this hunk corresponds to.
pub buffer_range: Range<text::Anchor>,
+ /// The excerpt that contains the diff hunk.
+ pub excerpt_id: ExcerptId,
/// The range within the buffer's diff base that this hunk corresponds to.
pub diff_base_byte_range: Range<usize>,
}
+impl MultiBufferDiffHunk {
+ pub fn status(&self) -> DiffHunkStatus {
+ if self.buffer_range.start == self.buffer_range.end {
+ DiffHunkStatus::Removed
+ } else if self.diff_base_byte_range.is_empty() {
+ DiffHunkStatus::Added
+ } else {
+ DiffHunkStatus::Modified
+ }
+ }
+}
+
pub type MultiBufferPoint = Point;
+type ExcerptOffset = TypedOffset<Excerpt>;
+type ExcerptPoint = TypedPoint<Excerpt>;
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq, Hash, serde::Deserialize)]
#[serde(transparent)]
@@ -134,6 +152,14 @@ impl MultiBufferRow {
pub const MAX: Self = Self(u32::MAX);
}
+impl std::ops::Add<usize> for MultiBufferRow {
+ type Output = Self;
+
+ fn add(self, rhs: usize) -> Self::Output {
+ MultiBufferRow(self.0 + rhs as u32)
+ }
+}
+
#[derive(Clone)]
struct History {
next_transaction_id: TransactionId,
@@ -176,12 +202,19 @@ struct BufferState {
_subscriptions: [gpui::Subscription; 2],
}
+struct ChangeSetState {
+ change_set: Model<BufferChangeSet>,
+ _subscription: gpui::Subscription,
+}
+
/// The contents of a [`MultiBuffer`] at a single point in time.
#[derive(Clone, Default)]
pub struct MultiBufferSnapshot {
singleton: bool,
excerpts: SumTree<Excerpt>,
excerpt_ids: SumTree<ExcerptIdMapping>,
+ diffs: TreeMap<BufferId, DiffSnapshot>,
+ pub diff_transforms: SumTree<DiffTransform>,
trailing_excerpt_update_count: usize,
non_text_state_update_count: usize,
edit_count: usize,
@@ -191,13 +224,34 @@ pub struct MultiBufferSnapshot {
show_headers: bool,
}
+#[derive(Debug, Clone)]
+pub enum DiffTransform {
+ BufferContent {
+ summary: TextSummary,
+ inserted_hunk_anchor: Option<(ExcerptId, text::Anchor)>,
+ },
+ DeletedHunk {
+ summary: TextSummary,
+ buffer_id: BufferId,
+ hunk_anchor: (ExcerptId, text::Anchor),
+ base_text_byte_range: Range<usize>,
+ has_trailing_newline: bool,
+ },
+}
+
+#[derive(Clone)]
+struct DiffSnapshot {
+ diff: git::diff::BufferDiff,
+ base_text: language::BufferSnapshot,
+}
+
#[derive(Clone)]
pub struct ExcerptInfo {
pub id: ExcerptId,
pub buffer: BufferSnapshot,
pub buffer_id: BufferId,
pub range: ExcerptRange<text::Anchor>,
- pub text_summary: TextSummary,
+ pub end_row: MultiBufferRow,
}
impl std::fmt::Debug for ExcerptInfo {
@@ -230,6 +284,13 @@ impl ExcerptBoundary {
}
}
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+pub struct RowInfo {
+ pub buffer_row: Option<u32>,
+ pub multibuffer_row: Option<MultiBufferRow>,
+ pub diff_status: Option<git::diff::DiffHunkStatus>,
+}
+
/// A slice into a [`Buffer`] that is being edited in a [`MultiBuffer`].
#[derive(Clone)]
struct Excerpt {
@@ -257,8 +318,11 @@ struct Excerpt {
#[derive(Clone)]
pub struct MultiBufferExcerpt<'a> {
excerpt: &'a Excerpt,
- excerpt_offset: usize,
- excerpt_position: Point,
+ diff_transforms:
+ sum_tree::Cursor<'a, DiffTransform, (OutputDimension<usize>, ExcerptDimension<usize>)>,
+ offset: usize,
+ excerpt_offset: ExcerptDimension<usize>,
+ buffer_offset: usize,
}
#[derive(Clone, Debug)]
@@ -287,50 +351,92 @@ pub struct ExcerptSummary {
text: TextSummary,
}
+#[derive(Debug, Clone)]
+pub struct DiffTransformSummary {
+ input: TextSummary,
+ output: TextSummary,
+}
+
#[derive(Clone)]
pub struct MultiBufferRows<'a> {
- buffer_row_range: Range<u32>,
- excerpts: Cursor<'a, Excerpt, Point>,
+ point: Point,
+ is_empty: bool,
+ cursor: MultiBufferCursor<'a, Point>,
}
pub struct MultiBufferChunks<'a> {
+ excerpts: Cursor<'a, Excerpt, ExcerptOffset>,
+ diff_transforms: Cursor<'a, DiffTransform, (usize, ExcerptOffset)>,
+ diffs: &'a TreeMap<BufferId, DiffSnapshot>,
+ diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>,
+ buffer_chunk: Option<Chunk<'a>>,
range: Range<usize>,
- excerpts: Cursor<'a, Excerpt, usize>,
+ excerpt_offset_range: Range<ExcerptOffset>,
excerpt_chunks: Option<ExcerptChunks<'a>>,
language_aware: bool,
}
+pub struct ReversedMultiBufferChunks<'a> {
+ cursor: MultiBufferCursor<'a, usize>,
+ current_chunks: Option<rope::Chunks<'a>>,
+ start: usize,
+ offset: usize,
+}
+
pub struct MultiBufferBytes<'a> {
range: Range<usize>,
- excerpts: Cursor<'a, Excerpt, usize>,
- excerpt_bytes: Option<ExcerptBytes<'a>>,
+ cursor: MultiBufferCursor<'a, usize>,
+ excerpt_bytes: Option<text::Bytes<'a>>,
+ has_trailing_newline: bool,
chunk: &'a [u8],
}
pub struct ReversedMultiBufferBytes<'a> {
range: Range<usize>,
- excerpts: Cursor<'a, Excerpt, usize>,
- excerpt_bytes: Option<ExcerptBytes<'a>>,
+ chunks: ReversedMultiBufferChunks<'a>,
chunk: &'a [u8],
}
+#[derive(Clone)]
+struct MultiBufferCursor<'a, D: TextDimension> {
+ excerpts: Cursor<'a, Excerpt, ExcerptDimension<D>>,
+ diff_transforms: Cursor<'a, DiffTransform, (OutputDimension<D>, ExcerptDimension<D>)>,
+ diffs: &'a TreeMap<BufferId, DiffSnapshot>,
+ cached_region: Option<MultiBufferRegion<'a, D>>,
+}
+
+#[derive(Clone)]
+struct MultiBufferRegion<'a, D: TextDimension> {
+ buffer: &'a BufferSnapshot,
+ is_main_buffer: bool,
+ is_inserted_hunk: bool,
+ excerpt: &'a Excerpt,
+ buffer_range: Range<D>,
+ range: Range<D>,
+ has_trailing_newline: bool,
+}
+
struct ExcerptChunks<'a> {
excerpt_id: ExcerptId,
content_chunks: BufferChunks<'a>,
footer_height: usize,
}
-struct ExcerptBytes<'a> {
- content_bytes: text::Bytes<'a>,
- padding_height: usize,
- reversed: bool,
-}
-
+#[derive(Debug)]
struct BufferEdit {
range: Range<usize>,
new_text: Arc<str>,
is_insertion: bool,
original_indent_column: u32,
+ excerpt_id: ExcerptId,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+enum DiffChangeKind {
+ BufferEdited,
+ ExcerptsChanged,
+ DiffUpdated { base_changed: bool },
+ ExpandOrCollapseHunks { expand: bool },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -359,16 +465,18 @@ impl ExpandExcerptDirection {
}
#[derive(Clone, Debug, PartialEq)]
-pub struct MultiBufferIndentGuide {
- pub multibuffer_row_range: Range<MultiBufferRow>,
- pub buffer: IndentGuide,
+pub struct IndentGuide {
+ pub buffer_id: BufferId,
+ pub start_row: MultiBufferRow,
+ pub end_row: MultiBufferRow,
+ pub depth: u32,
+ pub tab_size: u32,
+ pub settings: IndentGuideSettings,
}
-impl std::ops::Deref for MultiBufferIndentGuide {
- type Target = IndentGuide;
-
- fn deref(&self) -> &Self::Target {
- &self.buffer
+impl IndentGuide {
+ pub fn indent_level(&self) -> u32 {
+ self.depth * self.tab_size
}
}
@@ -380,6 +488,8 @@ impl MultiBuffer {
..MultiBufferSnapshot::default()
}),
buffers: RefCell::default(),
+ diff_bases: HashMap::default(),
+ all_diff_hunks_expanded: false,
subscriptions: Topic::default(),
singleton: false,
capability,
@@ -398,6 +508,8 @@ impl MultiBuffer {
Self {
snapshot: Default::default(),
buffers: Default::default(),
+ diff_bases: HashMap::default(),
+ all_diff_hunks_expanded: false,
subscriptions: Default::default(),
singleton: false,
capability,
@@ -429,9 +541,22 @@ impl MultiBuffer {
},
);
}
+ let mut diff_bases = HashMap::default();
+ for (buffer_id, change_set_state) in self.diff_bases.iter() {
+ diff_bases.insert(
+ *buffer_id,
+ ChangeSetState {
+ _subscription: new_cx
+ .observe(&change_set_state.change_set, Self::buffer_diff_changed),
+ change_set: change_set_state.change_set.clone(),
+ },
+ );
+ }
Self {
snapshot: RefCell::new(self.snapshot.borrow().clone()),
buffers: RefCell::new(buffers),
+ diff_bases,
+ all_diff_hunks_expanded: self.all_diff_hunks_expanded,
subscriptions: Default::default(),
singleton: self.singleton,
capability: self.capability,
@@ -566,16 +691,6 @@ impl MultiBuffer {
return;
}
- if let Some(buffer) = this.as_singleton() {
- buffer.update(cx, |buffer, cx| {
- buffer.edit(edits, autoindent_mode, cx);
- });
- cx.emit(Event::ExcerptsEdited {
- ids: this.excerpt_ids(),
- });
- return;
- }
-
let original_indent_columns = match &mut autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns,
@@ -588,7 +703,7 @@ impl MultiBuffer {
drop(snapshot);
for (buffer_id, mut edits) in buffer_edits {
- edits.sort_unstable_by_key(|edit| edit.range.start);
+ edits.sort_by_key(|edit| edit.range.start);
this.buffers.borrow()[&buffer_id]
.buffer
.update(cx, |buffer, cx| {
@@ -599,20 +714,26 @@ impl MultiBuffer {
let empty_str: Arc<str> = Arc::default();
while let Some(BufferEdit {
mut range,
- new_text,
+ mut new_text,
mut is_insertion,
original_indent_column,
+ excerpt_id,
}) = edits.next()
{
while let Some(BufferEdit {
range: next_range,
is_insertion: next_is_insertion,
+ new_text: next_new_text,
+ excerpt_id: next_excerpt_id,
..
}) = edits.peek()
{
if range.end >= next_range.start {
range.end = cmp::max(next_range.end, range.end);
is_insertion |= *next_is_insertion;
+ if excerpt_id == *next_excerpt_id {
+ new_text = format!("{new_text}{next_new_text}").into();
+ }
edits.next();
} else {
break;
@@ -671,96 +792,113 @@ impl MultiBuffer {
) -> (HashMap<BufferId, Vec<BufferEdit>>, Vec<ExcerptId>) {
let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default();
let mut edited_excerpt_ids = Vec::new();
- let mut cursor = snapshot.excerpts.cursor::<usize>(&());
+ let mut cursor = snapshot.cursor::<usize>();
for (ix, (range, new_text)) in edits.into_iter().enumerate() {
let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0);
- cursor.seek(&range.start, Bias::Right, &());
- if cursor.item().is_none() && range.start == *cursor.start() {
- cursor.prev(&());
+
+ cursor.seek_forward(&range.start);
+ let mut start_region = cursor.region().expect("start offset out of bounds");
+ if !start_region.is_main_buffer {
+ cursor.next();
+ if let Some(region) = cursor.region() {
+ start_region = region;
+ } else {
+ continue;
+ }
}
- let start_excerpt = cursor.item().expect("start offset out of bounds");
- let start_overshoot = range.start - cursor.start();
- let buffer_start = start_excerpt
- .range
- .context
- .start
- .to_offset(&start_excerpt.buffer)
- + start_overshoot;
- edited_excerpt_ids.push(start_excerpt.id);
- cursor.seek(&range.end, Bias::Right, &());
- if cursor.item().is_none() && range.end == *cursor.start() {
- cursor.prev(&());
+ if range.end < start_region.range.start {
+ continue;
}
- let end_excerpt = cursor.item().expect("end offset out of bounds");
- let end_overshoot = range.end - cursor.start();
- let buffer_end = end_excerpt
- .range
- .context
- .start
- .to_offset(&end_excerpt.buffer)
- + end_overshoot;
- if start_excerpt.id == end_excerpt.id {
- buffer_edits
- .entry(start_excerpt.buffer_id)
- .or_default()
- .push(BufferEdit {
- range: buffer_start..buffer_end,
- new_text,
- is_insertion: true,
- original_indent_column,
- });
- } else {
- edited_excerpt_ids.push(end_excerpt.id);
- let start_excerpt_range = buffer_start
- ..start_excerpt
- .range
- .context
- .end
- .to_offset(&start_excerpt.buffer);
- let end_excerpt_range = end_excerpt
- .range
- .context
- .start
- .to_offset(&end_excerpt.buffer)
- ..buffer_end;
- buffer_edits
- .entry(start_excerpt.buffer_id)
- .or_default()
- .push(BufferEdit {
- range: start_excerpt_range,
- new_text: new_text.clone(),
- is_insertion: true,
- original_indent_column,
- });
- buffer_edits
- .entry(end_excerpt.buffer_id)
- .or_default()
- .push(BufferEdit {
- range: end_excerpt_range,
- new_text: new_text.clone(),
- is_insertion: false,
- original_indent_column,
- });
+ if range.end > start_region.range.end {
+ cursor.seek_forward(&range.end);
+ }
+ let mut end_region = cursor.region().expect("end offset out of bounds");
+ if !end_region.is_main_buffer {
+ cursor.prev();
+ if let Some(region) = cursor.region() {
+ end_region = region;
+ } else {
+ continue;
+ }
+ }
- cursor.seek(&range.start, Bias::Right, &());
- cursor.next(&());
- while let Some(excerpt) = cursor.item() {
- if excerpt.id == end_excerpt.id {
- break;
- }
+ if range.start > end_region.range.end {
+ continue;
+ }
+
+ let start_overshoot = range.start.saturating_sub(start_region.range.start);
+ let end_overshoot = range.end.saturating_sub(end_region.range.start);
+ let buffer_start = (start_region.buffer_range.start + start_overshoot)
+ .min(start_region.buffer_range.end);
+ let buffer_end =
+ (end_region.buffer_range.start + end_overshoot).min(end_region.buffer_range.end);
+
+ if start_region.excerpt.id == end_region.excerpt.id {
+ if start_region.is_main_buffer {
+ edited_excerpt_ids.push(start_region.excerpt.id);
buffer_edits
- .entry(excerpt.buffer_id)
+ .entry(start_region.buffer.remote_id())
.or_default()
.push(BufferEdit {
- range: excerpt.range.context.to_offset(&excerpt.buffer),
+ range: buffer_start..buffer_end,
+ new_text,
+ is_insertion: true,
+ original_indent_column,
+ excerpt_id: start_region.excerpt.id,
+ });
+ }
+ } else {
+ let start_excerpt_range = buffer_start..start_region.buffer_range.end;
+ let end_excerpt_range = end_region.buffer_range.start..buffer_end;
+ if start_region.is_main_buffer {
+ edited_excerpt_ids.push(start_region.excerpt.id);
+ buffer_edits
+ .entry(start_region.buffer.remote_id())
+ .or_default()
+ .push(BufferEdit {
+ range: start_excerpt_range,
+ new_text: new_text.clone(),
+ is_insertion: true,
+ original_indent_column,
+ excerpt_id: start_region.excerpt.id,
+ });
+ }
+ if end_region.is_main_buffer {
+ edited_excerpt_ids.push(end_region.excerpt.id);
+ buffer_edits
+ .entry(end_region.buffer.remote_id())
+ .or_default()
+ .push(BufferEdit {
+ range: end_excerpt_range,
new_text: new_text.clone(),
is_insertion: false,
original_indent_column,
+ excerpt_id: end_region.excerpt.id,
});
- edited_excerpt_ids.push(excerpt.id);
- cursor.next(&());
+ }
+
+ cursor.seek(&range.start);
+ cursor.next_excerpt();
+ while let Some(region) = cursor.region() {
+ if region.excerpt.id == end_region.excerpt.id {
+ break;
+ }
+ if region.is_main_buffer {
+ edited_excerpt_ids.push(region.excerpt.id);
+ buffer_edits
+ .entry(region.buffer.remote_id())
+ .or_default()
+ .push(BufferEdit {
+ range: region.buffer_range,
+ new_text: new_text.clone(),
+ is_insertion: false,
+ original_indent_column,
+ excerpt_id: region.excerpt.id,
+ });
+ }
+ cursor.next_excerpt();
}
}
}
@@ -797,16 +935,6 @@ impl MultiBuffer {
return;
}
- if let Some(buffer) = this.as_singleton() {
- buffer.update(cx, |buffer, cx| {
- buffer.autoindent_ranges(edits.into_iter().map(|e| e.0), cx);
- });
- cx.emit(Event::ExcerptsEdited {
- ids: this.excerpt_ids(),
- });
- return;
- }
-
let (buffer_edits, edited_excerpt_ids) =
this.convert_edits_to_buffer_edits(edits, &snapshot, &[]);
drop(snapshot);
@@ -849,20 +977,13 @@ impl MultiBuffer {
cx: &mut ModelContext<Self>,
) -> Point {
let multibuffer_point = position.to_point(&self.read(cx));
- if let Some(buffer) = self.as_singleton() {
- buffer.update(cx, |buffer, cx| {
- buffer.insert_empty_line(multibuffer_point, space_above, space_below, cx)
- })
- } else {
- let (buffer, buffer_point, _) =
- self.point_to_buffer_point(multibuffer_point, cx).unwrap();
- self.start_transaction(cx);
- let empty_line_start = buffer.update(cx, |buffer, cx| {
- buffer.insert_empty_line(buffer_point, space_above, space_below, cx)
- });
- self.end_transaction(cx);
- multibuffer_point + (empty_line_start - buffer_point)
- }
+ let (buffer, buffer_point, _) = self.point_to_buffer_point(multibuffer_point, cx).unwrap();
+ self.start_transaction(cx);
+ let empty_line_start = buffer.update(cx, |buffer, cx| {
+ buffer.insert_empty_line(buffer_point, space_above, space_below, cx)
+ });
+ self.end_transaction(cx);
+ multibuffer_point + (empty_line_start - buffer_point)
}
pub fn start_transaction(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
@@ -922,13 +1043,6 @@ impl MultiBuffer {
where
D: TextDimension + Ord + Sub<D, Output = D>,
{
- if let Some(buffer) = self.as_singleton() {
- return buffer
- .read(cx)
- .edited_ranges_for_transaction_id(transaction_id)
- .collect::<Vec<_>>();
- }
-
let Some(transaction) = self.history.transaction(transaction_id) else {
return Vec::new();
};
@@ -952,14 +1066,14 @@ impl MultiBuffer {
let excerpt_buffer_start =
excerpt.range.context.start.summary::<D>(buffer);
let excerpt_buffer_end = excerpt.range.context.end.summary::<D>(buffer);
- let excerpt_range = excerpt_buffer_start.clone()..excerpt_buffer_end;
+ let excerpt_range = excerpt_buffer_start..excerpt_buffer_end;
if excerpt_range.contains(&range.start)
&& excerpt_range.contains(&range.end)
{
let excerpt_start = D::from_text_summary(&cursor.start().text);
- let mut start = excerpt_start.clone();
- start.add_assign(&(range.start - excerpt_buffer_start.clone()));
+ let mut start = excerpt_start;
+ start.add_assign(&(range.start - excerpt_buffer_start));
let mut end = excerpt_start;
end.add_assign(&(range.end - excerpt_buffer_start));
@@ -972,7 +1086,7 @@ impl MultiBuffer {
}
}
- ranges.sort_by_key(|range| range.start.clone());
+ ranges.sort_by_key(|range| range.start);
ranges
}
@@ -1258,11 +1372,13 @@ impl MultiBuffer {
buffer_id: Some(buffer_id),
excerpt_id,
text_anchor: buffer_snapshot.anchor_after(range.start),
+ diff_base_anchor: None,
};
let end = Anchor {
buffer_id: Some(buffer_id),
excerpt_id,
text_anchor: buffer_snapshot.anchor_after(range.end),
+ diff_base_anchor: None,
};
start..end
}))
@@ -1339,11 +1455,13 @@ impl MultiBuffer {
buffer_id: Some(buffer_id),
excerpt_id,
text_anchor: range.start,
+ diff_base_anchor: None,
};
let end = Anchor {
buffer_id: Some(buffer_id),
excerpt_id,
text_anchor: range.end,
+ diff_base_anchor: None,
};
multi_buffer_ranges.push(start..end);
}
@@ -1425,7 +1543,7 @@ impl MultiBuffer {
let mut new_excerpts = cursor.slice(&prev_locator, Bias::Right, &());
prev_locator = cursor.start().unwrap_or(Locator::min_ref()).clone();
- let edit_start = new_excerpts.summary().text.len;
+ let edit_start = ExcerptOffset::new(new_excerpts.summary().text.len);
new_excerpts.update_last(
|excerpt| {
excerpt.has_trailing_newline = true;
@@ -1471,7 +1589,7 @@ impl MultiBuffer {
new_excerpt_ids.push(ExcerptIdMapping { id, locator }, &());
}
- let edit_end = new_excerpts.summary().text.len;
+ let edit_end = ExcerptOffset::new(new_excerpts.summary().text.len);
let suffix = cursor.suffix(&());
let changed_trailing_excerpt = suffix.is_empty();
@@ -1483,10 +1601,14 @@ impl MultiBuffer {
snapshot.trailing_excerpt_update_count += 1;
}
- self.subscriptions.publish_mut([Edit {
- old: edit_start..edit_start,
- new: edit_start..edit_end,
- }]);
+ self.sync_diff_transforms(
+ snapshot,
+ vec![Edit {
+ old: edit_start..edit_start,
+ new: edit_start..edit_end,
+ }],
+ DiffChangeKind::ExcerptsChanged,
+ );
cx.emit(Event::Edited {
singleton_buffer_edited: false,
edited_buffer: None,
@@ -1504,17 +1626,22 @@ impl MultiBuffer {
let ids = self.excerpt_ids();
self.buffers.borrow_mut().clear();
let mut snapshot = self.snapshot.borrow_mut();
- let prev_len = snapshot.len();
+ let start = ExcerptOffset::new(0);
+ let prev_len = ExcerptOffset::new(snapshot.excerpts.summary().text.len);
snapshot.excerpts = Default::default();
snapshot.trailing_excerpt_update_count += 1;
snapshot.is_dirty = false;
snapshot.has_deleted_file = false;
snapshot.has_conflict = false;
- self.subscriptions.publish_mut([Edit {
- old: 0..prev_len,
- new: 0..0,
- }]);
+ self.sync_diff_transforms(
+ snapshot,
+ vec![Edit {
+ old: start..prev_len,
+ new: start..start,
+ }],
+ DiffChangeKind::ExcerptsChanged,
+ );
cx.emit(Event::Edited {
singleton_buffer_edited: false,
edited_buffer: None,
@@ -1556,24 +1683,39 @@ impl MultiBuffer {
) -> Vec<Range<Point>> {
let snapshot = self.read(cx);
let buffers = self.buffers.borrow();
- let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, Point)>(&());
- buffers
+ let mut excerpts = snapshot
+ .excerpts
+ .cursor::<(Option<&Locator>, ExcerptDimension<Point>)>(&());
+ let mut diff_transforms = snapshot
+ .diff_transforms
+ .cursor::<(ExcerptDimension<Point>, OutputDimension<Point>)>(&());
+ diff_transforms.next(&());
+ let locators = buffers
.get(&buffer_id)
.into_iter()
- .flat_map(|state| &state.excerpts)
- .filter_map(move |locator| {
- cursor.seek_forward(&Some(locator), Bias::Left, &());
- cursor.item().and_then(|excerpt| {
- if excerpt.locator == *locator {
- let excerpt_start = cursor.start().1;
- let excerpt_end = excerpt_start + excerpt.text_summary.lines;
- Some(excerpt_start..excerpt_end)
- } else {
- None
- }
- })
- })
- .collect()
+ .flat_map(|state| &state.excerpts);
+ let mut result = Vec::new();
+ for locator in locators {
+ excerpts.seek_forward(&Some(locator), Bias::Left, &());
+ if let Some(excerpt) = excerpts.item() {
+ if excerpt.locator == *locator {
+ let excerpt_start = excerpts.start().1.clone();
+ let excerpt_end =
+ ExcerptDimension(excerpt_start.0 + excerpt.text_summary.lines);
+
+ diff_transforms.seek_forward(&excerpt_start, Bias::Left, &());
+ let overshoot = excerpt_start.0 - diff_transforms.start().0 .0;
+ let start = diff_transforms.start().1 .0 + overshoot;
+
+ diff_transforms.seek_forward(&excerpt_end, Bias::Right, &());
+ let overshoot = excerpt_end.0 - diff_transforms.start().0 .0;
+ let end = diff_transforms.start().1 .0 + overshoot;
+
+ result.push(start..end)
+ }
+ }
+ }
+ result
}
pub fn excerpt_buffer_ids(&self) -> Vec<BufferId> {
@@ -1600,12 +1742,12 @@ impl MultiBuffer {
cx: &AppContext,
) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
let snapshot = self.read(cx);
- let position = position.to_offset(&snapshot);
+ let offset = position.to_offset(&snapshot);
- let mut cursor = snapshot.excerpts.cursor::<usize>(&());
- cursor.seek(&position, Bias::Right, &());
+ let mut cursor = snapshot.cursor::<usize>();
+ cursor.seek(&offset);
cursor
- .item()
+ .excerpt()
.or_else(|| snapshot.excerpts.last())
.map(|excerpt| {
(
@@ -1626,22 +1768,17 @@ impl MultiBuffer {
&self,
point: T,
cx: &AppContext,
- ) -> Option<(Model<Buffer>, usize, ExcerptId)> {
+ ) -> Option<(Model<Buffer>, usize)> {
let snapshot = self.read(cx);
- let offset = point.to_offset(&snapshot);
- let mut cursor = snapshot.excerpts.cursor::<usize>(&());
- cursor.seek(&offset, Bias::Right, &());
- if cursor.item().is_none() {
- cursor.prev(&());
- }
-
- cursor.item().map(|excerpt| {
- let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
- let buffer_point = excerpt_start + offset - *cursor.start();
- let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone();
-
- (buffer, buffer_point, excerpt.id)
- })
+ let (buffer, offset) = snapshot.point_to_buffer_offset(point)?;
+ Some((
+ self.buffers
+ .borrow()
+ .get(&buffer.remote_id())?
+ .buffer
+ .clone(),
+ offset,
+ ))
}
// If point is at the end of the buffer, the last excerpt is returned
@@ -1652,18 +1789,49 @@ impl MultiBuffer {
) -> Option<(Model<Buffer>, Point, ExcerptId)> {
let snapshot = self.read(cx);
let point = point.to_point(&snapshot);
- let mut cursor = snapshot.excerpts.cursor::<Point>(&());
- cursor.seek(&point, Bias::Right, &());
- if cursor.item().is_none() {
- cursor.prev(&());
- }
+ let mut cursor = snapshot.cursor::<Point>();
+ cursor.seek(&point);
+
+ cursor.region().and_then(|region| {
+ if !region.is_main_buffer {
+ return None;
+ }
+
+ let overshoot = point - region.range.start;
+ let buffer_point = region.buffer_range.start + overshoot;
+ let buffer = self.buffers.borrow()[®ion.buffer.remote_id()]
+ .buffer
+ .clone();
+ Some((buffer, buffer_point, region.excerpt.id))
+ })
+ }
- cursor.item().map(|excerpt| {
- let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer);
- let buffer_point = excerpt_start + point - *cursor.start();
- let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone();
+ pub fn buffer_point_to_anchor(
+ &self,
+ buffer: &Model<Buffer>,
+ point: Point,
+ cx: &AppContext,
+ ) -> Option<Anchor> {
+ let mut found = None;
+ let snapshot = buffer.read(cx).snapshot();
+ for (excerpt_id, range) in self.excerpts_for_buffer(buffer, cx) {
+ let start = range.context.start.to_point(&snapshot);
+ let end = range.context.end.to_point(&snapshot);
+ if start <= point && point < end {
+ found = Some((snapshot.clip_point(point, Bias::Left), excerpt_id));
+ break;
+ }
+ if point < start {
+ found = Some((start, excerpt_id));
+ }
+ if point > end {
+ found = Some((end, excerpt_id));
+ }
+ }
- (buffer, buffer_point, excerpt.id)
+ found.map(|(point, excerpt_id)| {
+ let text_anchor = snapshot.anchor_after(point);
+ Anchor::in_buffer(excerpt_id, snapshot.remote_id(), text_anchor)
})
}
@@ -1681,7 +1849,9 @@ impl MultiBuffer {
let mut buffers = self.buffers.borrow_mut();
let mut snapshot = self.snapshot.borrow_mut();
let mut new_excerpts = SumTree::default();
- let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>(&());
+ let mut cursor = snapshot
+ .excerpts
+ .cursor::<(Option<&Locator>, ExcerptOffset)>(&());
let mut edits = Vec::new();
let mut excerpt_ids = ids.iter().copied().peekable();
@@ -1723,14 +1893,14 @@ impl MultiBuffer {
// When removing the last excerpt, remove the trailing newline from
// the previous excerpt.
- if cursor.item().is_none() && old_start > 0 {
- old_start -= 1;
+ if cursor.item().is_none() && old_start.value > 0 {
+ old_start.value -= 1;
new_excerpts.update_last(|e| e.has_trailing_newline = false, &());
}
// Push an edit for the removal of this run of excerpts.
let old_end = cursor.start().1;
- let new_start = new_excerpts.summary().text.len;
+ let new_start = ExcerptOffset::new(new_excerpts.summary().text.len);
edits.push(Edit {
old: old_start..old_end,
new: new_start..new_start,
@@ -1747,7 +1917,7 @@ impl MultiBuffer {
snapshot.trailing_excerpt_update_count += 1;
}
- self.subscriptions.publish_mut(edits);
+ self.sync_diff_transforms(snapshot, edits, DiffChangeKind::ExcerptsChanged);
cx.emit(Event::Edited {
singleton_buffer_edited: false,
edited_buffer: None,
@@ -1,5 +1,7 @@
use super::*;
+use git::diff::DiffHunkStatus;
use gpui::{AppContext, Context, TestAppContext};
+use indoc::indoc;
use language::{Buffer, Rope};
use parking_lot::RwLock;
use rand::prelude::*;
@@ -14,6 +16,22 @@ fn init_logger() {
}
}
+#[gpui::test]
+fn test_empty_singleton(cx: &mut AppContext) {
+ let buffer = cx.new_model(|cx| Buffer::local("", cx));
+ let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
+ let snapshot = multibuffer.read(cx).snapshot(cx);
+ assert_eq!(snapshot.text(), "");
+ assert_eq!(
+ snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
+ [RowInfo {
+ buffer_row: Some(0),
+ multibuffer_row: Some(MultiBufferRow(0)),
+ diff_status: None
+ }]
+ );
+}
+
#[gpui::test]
fn test_singleton(cx: &mut AppContext) {
let buffer = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
@@ -23,22 +41,30 @@ fn test_singleton(cx: &mut AppContext) {
assert_eq!(snapshot.text(), buffer.read(cx).text());
assert_eq!(
- snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
+ snapshot
+ .row_infos(MultiBufferRow(0))
+ .map(|info| info.buffer_row)
+ .collect::<Vec<_>>(),
(0..buffer.read(cx).row_count())
.map(Some)
.collect::<Vec<_>>()
);
+ assert_consistent_line_numbers(&snapshot);
buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx));
let snapshot = multibuffer.read(cx).snapshot(cx);
assert_eq!(snapshot.text(), buffer.read(cx).text());
assert_eq!(
- snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
+ snapshot
+ .row_infos(MultiBufferRow(0))
+ .map(|info| info.buffer_row)
+ .collect::<Vec<_>>(),
(0..buffer.read(cx).row_count())
.map(Some)
.collect::<Vec<_>>()
);
+ assert_consistent_line_numbers(&snapshot);
}
#[gpui::test]
@@ -154,28 +180,41 @@ fn test_excerpt_boundaries_and_clipping(cx: &mut AppContext) {
let snapshot = multibuffer.read(cx).snapshot(cx);
assert_eq!(
snapshot.text(),
- concat!(
- "bbbb\n", // Preserve newlines
- "ccccc\n", //
- "ddd\n", //
- "eeee\n", //
- "jj" //
- )
+ indoc!(
+ "
+ bbbb
+ ccccc
+ ddd
+ eeee
+ jj"
+ ),
);
assert_eq!(
- snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
+ snapshot
+ .row_infos(MultiBufferRow(0))
+ .map(|info| info.buffer_row)
+ .collect::<Vec<_>>(),
[Some(1), Some(2), Some(3), Some(4), Some(3)]
);
assert_eq!(
- snapshot.buffer_rows(MultiBufferRow(2)).collect::<Vec<_>>(),
+ snapshot
+ .row_infos(MultiBufferRow(2))
+ .map(|info| info.buffer_row)
+ .collect::<Vec<_>>(),
[Some(3), Some(4), Some(3)]
);
assert_eq!(
- snapshot.buffer_rows(MultiBufferRow(4)).collect::<Vec<_>>(),
+ snapshot
+ .row_infos(MultiBufferRow(4))
+ .map(|info| info.buffer_row)
+ .collect::<Vec<_>>(),
[Some(3)]
);
assert_eq!(
- snapshot.buffer_rows(MultiBufferRow(5)).collect::<Vec<_>>(),
+ snapshot
+ .row_infos(MultiBufferRow(5))
+ .map(|info| info.buffer_row)
+ .collect::<Vec<_>>(),
[]
);
@@ -314,6 +353,312 @@ fn test_excerpt_boundaries_and_clipping(cx: &mut AppContext) {
}
}
+#[gpui::test]
+fn test_diff_boundary_anchors(cx: &mut AppContext) {
+ let base_text = "one\ntwo\nthree\n";
+ let text = "one\nthree\n";
+ let buffer = cx.new_model(|cx| Buffer::local(text, cx));
+ let snapshot = buffer.read(cx).snapshot();
+ let change_set = cx.new_model(|cx| {
+ let mut change_set = BufferChangeSet::new(&buffer, cx);
+ change_set.recalculate_diff_sync(base_text.into(), snapshot.text, true, cx);
+ change_set
+ });
+ let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.add_change_set(change_set, cx)
+ });
+
+ let (before, after) = multibuffer.update(cx, |multibuffer, cx| {
+ let before = multibuffer.snapshot(cx).anchor_before(Point::new(1, 0));
+ let after = multibuffer.snapshot(cx).anchor_after(Point::new(1, 0));
+ multibuffer.set_all_diff_hunks_expanded(cx);
+ (before, after)
+ });
+ cx.background_executor().run_until_parked();
+
+ let snapshot = multibuffer.read(cx).snapshot(cx);
+ let actual_text = snapshot.text();
+ let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
+ let actual_diff = format_diff(&actual_text, &actual_row_infos, &Default::default());
+ pretty_assertions::assert_eq!(
+ actual_diff,
+ indoc! {
+ " one
+ - two
+ three
+ "
+ },
+ );
+
+ multibuffer.update(cx, |multibuffer, cx| {
+ let snapshot = multibuffer.snapshot(cx);
+ assert_eq!(before.to_point(&snapshot), Point::new(1, 0));
+ assert_eq!(after.to_point(&snapshot), Point::new(2, 0));
+ assert_eq!(
+ vec![Point::new(1, 0), Point::new(2, 0),],
+ snapshot.summaries_for_anchors::<Point, _>(&[before, after]),
+ )
+ })
+}
+
+#[gpui::test]
+fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
+ let base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n";
+ let text = "one\nfour\nseven\n";
+ let buffer = cx.new_model(|cx| Buffer::local(text, cx));
+ let change_set = cx.new_model(|cx| {
+ let mut change_set = BufferChangeSet::new(&buffer, cx);
+ let snapshot = buffer.read(cx).snapshot();
+ change_set.recalculate_diff_sync(base_text.into(), snapshot.text, true, cx);
+ change_set
+ });
+ let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
+ let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
+ (multibuffer.snapshot(cx), multibuffer.subscribe())
+ });
+
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.add_change_set(change_set, cx);
+ multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
+ });
+
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc! {
+ " one
+ - two
+ - three
+ four
+ - five
+ - six
+ seven
+ - eight
+ "
+ },
+ );
+
+ assert_eq!(
+ snapshot
+ .diff_hunks_in_range(Point::new(1, 0)..Point::MAX)
+ .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
+ .collect::<Vec<_>>(),
+ vec![1..3, 4..6, 7..8]
+ );
+
+ assert_eq!(
+ snapshot
+ .diff_hunk_before(Point::new(1, 1))
+ .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
+ None,
+ );
+ assert_eq!(
+ snapshot
+ .diff_hunk_before(Point::new(7, 0))
+ .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
+ Some(4..6)
+ );
+ assert_eq!(
+ snapshot
+ .diff_hunk_before(Point::new(4, 0))
+ .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
+ Some(1..3)
+ );
+
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
+ });
+
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc! {
+ "
+ one
+ four
+ seven
+ "
+ },
+ );
+
+ assert_eq!(
+ snapshot
+ .diff_hunk_before(Point::new(2, 0))
+ .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
+ Some(1..1),
+ );
+ assert_eq!(
+ snapshot
+ .diff_hunk_before(Point::new(4, 0))
+ .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
+ Some(2..2)
+ );
+}
+
+#[gpui::test]
+fn test_editing_text_in_diff_hunks(cx: &mut TestAppContext) {
+ let base_text = "one\ntwo\nfour\nfive\nsix\nseven\n";
+ let text = "one\ntwo\nTHREE\nfour\nfive\nseven\n";
+ let buffer = cx.new_model(|cx| Buffer::local(text, cx));
+ let change_set = cx.new_model(|cx| {
+ let mut change_set = BufferChangeSet::new(&buffer, cx);
+ let snapshot = buffer.read(cx).snapshot();
+ change_set.recalculate_diff_sync(base_text.into(), snapshot.text, true, cx);
+ change_set
+ });
+ let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
+
+ let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.add_change_set(change_set.clone(), cx);
+ (multibuffer.snapshot(cx), multibuffer.subscribe())
+ });
+
+ cx.executor().run_until_parked();
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.set_all_diff_hunks_expanded(cx);
+ });
+
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc! {
+ "
+ one
+ two
+ + THREE
+ four
+ five
+ - six
+ seven
+ "
+ },
+ );
+
+ // Insert a newline within an insertion hunk
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.edit([(Point::new(2, 0)..Point::new(2, 0), "__\n__")], None, cx);
+ });
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc! {
+ "
+ one
+ two
+ + __
+ + __THREE
+ four
+ five
+ - six
+ seven
+ "
+ },
+ );
+
+ // Delete the newline before a deleted hunk.
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.edit([(Point::new(5, 4)..Point::new(6, 0), "")], None, cx);
+ });
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc! {
+ "
+ one
+ two
+ + __
+ + __THREE
+ four
+ fiveseven
+ "
+ },
+ );
+
+ multibuffer.update(cx, |multibuffer, cx| multibuffer.undo(cx));
+ change_set.update(cx, |change_set, cx| {
+ change_set.recalculate_diff_sync(
+ base_text.into(),
+ buffer.read(cx).text_snapshot(),
+ true,
+ cx,
+ );
+ });
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc! {
+ "
+ one
+ two
+ + __
+ + __THREE
+ four
+ five
+ - six
+ seven
+ "
+ },
+ );
+
+ // Cannot (yet) insert at the beginning of a deleted hunk.
+ // (because it would put the newline in the wrong place)
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.edit([(Point::new(6, 0)..Point::new(6, 0), "\n")], None, cx);
+ });
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc! {
+ "
+ one
+ two
+ + __
+ + __THREE
+ four
+ five
+ - six
+ seven
+ "
+ },
+ );
+
+ // Replace a range that ends in a deleted hunk.
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.edit([(Point::new(5, 2)..Point::new(6, 2), "fty-")], None, cx);
+ });
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc! {
+ "
+ one
+ two
+ + __
+ + __THREE
+ four
+ fifty-seven
+ "
+ },
+ );
+}
+
#[gpui::test]
fn test_excerpt_events(cx: &mut AppContext) {
let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text(10, 3, 'a'), cx));
@@ -633,11 +978,17 @@ fn test_empty_multibuffer(cx: &mut AppContext) {
let snapshot = multibuffer.read(cx).snapshot(cx);
assert_eq!(snapshot.text(), "");
assert_eq!(
- snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
+ snapshot
+ .row_infos(MultiBufferRow(0))
+ .map(|info| info.buffer_row)
+ .collect::<Vec<_>>(),
&[Some(0)]
);
assert_eq!(
- snapshot.buffer_rows(MultiBufferRow(1)).collect::<Vec<_>>(),
+ snapshot
+ .row_infos(MultiBufferRow(1))
+ .map(|info| info.buffer_row)
+ .collect::<Vec<_>>(),
&[]
);
}
@@ -851,493 +1202,393 @@ fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut AppContext) {
);
}
-#[gpui::test(iterations = 100)]
-fn test_random_multibuffer(cx: &mut AppContext, mut rng: StdRng) {
- let operations = env::var("OPERATIONS")
- .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
- .unwrap_or(10);
+#[gpui::test]
+fn test_basic_diff_hunks(cx: &mut TestAppContext) {
+ let text = indoc!(
+ "
+ ZERO
+ one
+ TWO
+ three
+ six
+ "
+ );
+ let base_text = indoc!(
+ "
+ one
+ two
+ three
+ four
+ five
+ six
+ "
+ );
- let mut buffers: Vec<Model<Buffer>> = Vec::new();
- let multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite));
- let mut excerpt_ids = Vec::<ExcerptId>::new();
- let mut expected_excerpts = Vec::<(Model<Buffer>, Range<text::Anchor>)>::new();
- let mut anchors = Vec::new();
- let mut old_versions = Vec::new();
+ let buffer = cx.new_model(|cx| Buffer::local(text, cx));
+ let change_set =
+ cx.new_model(|cx| BufferChangeSet::new_with_base_text(base_text.to_string(), &buffer, cx));
+ cx.run_until_parked();
- for _ in 0..operations {
- match rng.gen_range(0..100) {
- 0..=14 if !buffers.is_empty() => {
- let buffer = buffers.choose(&mut rng).unwrap();
- buffer.update(cx, |buf, cx| buf.randomly_edit(&mut rng, 5, cx));
- }
- 15..=19 if !expected_excerpts.is_empty() => {
- multibuffer.update(cx, |multibuffer, cx| {
- let ids = multibuffer.excerpt_ids();
- let mut excerpts = HashSet::default();
- for _ in 0..rng.gen_range(0..ids.len()) {
- excerpts.extend(ids.choose(&mut rng).copied());
- }
+ let multibuffer = cx.new_model(|cx| {
+ let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
+ multibuffer.add_change_set(change_set.clone(), cx);
+ multibuffer
+ });
- let line_count = rng.gen_range(0..5);
+ let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
+ (multibuffer.snapshot(cx), multibuffer.subscribe())
+ });
+ assert_eq!(
+ snapshot.text(),
+ indoc!(
+ "
+ ZERO
+ one
+ TWO
+ three
+ six
+ "
+ ),
+ );
- let excerpt_ixs = excerpts
- .iter()
- .map(|id| excerpt_ids.iter().position(|i| i == id).unwrap())
- .collect::<Vec<_>>();
- log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines");
- multibuffer.expand_excerpts(
- excerpts.iter().cloned(),
- line_count,
- ExpandExcerptDirection::UpAndDown,
- cx,
- );
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
+ });
- if line_count > 0 {
- for id in excerpts {
- let excerpt_ix = excerpt_ids.iter().position(|&i| i == id).unwrap();
- let (buffer, range) = &mut expected_excerpts[excerpt_ix];
- let snapshot = buffer.read(cx).snapshot();
- let mut point_range = range.to_point(&snapshot);
- point_range.start =
- Point::new(point_range.start.row.saturating_sub(line_count), 0);
- point_range.end = snapshot.clip_point(
- Point::new(point_range.end.row + line_count, 0),
- Bias::Left,
- );
- point_range.end.column = snapshot.line_len(point_range.end.row);
- *range = snapshot.anchor_before(point_range.start)
- ..snapshot.anchor_after(point_range.end);
- }
- }
- });
- }
- 20..=29 if !expected_excerpts.is_empty() => {
- let mut ids_to_remove = vec![];
- for _ in 0..rng.gen_range(1..=3) {
- if expected_excerpts.is_empty() {
- break;
- }
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc!(
+ "
+ + ZERO
+ one
+ - two
+ + TWO
+ three
+ - four
+ - five
+ six
+ "
+ ),
+ );
- let ix = rng.gen_range(0..expected_excerpts.len());
- ids_to_remove.push(excerpt_ids.remove(ix));
- let (buffer, range) = expected_excerpts.remove(ix);
- let buffer = buffer.read(cx);
- log::info!(
- "Removing excerpt {}: {:?}",
- ix,
- buffer
- .text_for_range(range.to_offset(buffer))
- .collect::<String>(),
- );
- }
- let snapshot = multibuffer.read(cx).read(cx);
- ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
- drop(snapshot);
- multibuffer.update(cx, |multibuffer, cx| {
- multibuffer.remove_excerpts(ids_to_remove, cx)
- });
- }
- 30..=39 if !expected_excerpts.is_empty() => {
- let multibuffer = multibuffer.read(cx).read(cx);
- let offset =
- multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left);
- let bias = if rng.gen() { Bias::Left } else { Bias::Right };
- log::info!("Creating anchor at {} with bias {:?}", offset, bias);
- anchors.push(multibuffer.anchor_at(offset, bias));
- anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
- }
- 40..=44 if !anchors.is_empty() => {
- let multibuffer = multibuffer.read(cx).read(cx);
- let prev_len = anchors.len();
- anchors = multibuffer
- .refresh_anchors(&anchors)
- .into_iter()
- .map(|a| a.1)
- .collect();
+ assert_eq!(
+ snapshot
+ .row_infos(MultiBufferRow(0))
+ .map(|info| (info.buffer_row, info.diff_status))
+ .collect::<Vec<_>>(),
+ vec![
+ (Some(0), Some(DiffHunkStatus::Added)),
+ (Some(1), None),
+ (Some(1), Some(DiffHunkStatus::Removed)),
+ (Some(2), Some(DiffHunkStatus::Added)),
+ (Some(3), None),
+ (Some(3), Some(DiffHunkStatus::Removed)),
+ (Some(4), Some(DiffHunkStatus::Removed)),
+ (Some(4), None),
+ (Some(5), None)
+ ]
+ );
- // Ensure the newly-refreshed anchors point to a valid excerpt and don't
- // overshoot its boundaries.
- assert_eq!(anchors.len(), prev_len);
- for anchor in &anchors {
- if anchor.excerpt_id == ExcerptId::min()
- || anchor.excerpt_id == ExcerptId::max()
- {
- continue;
- }
+ assert_chunks_in_ranges(&snapshot);
+ assert_consistent_line_numbers(&snapshot);
+ assert_position_translation(&snapshot);
+ assert_line_indents(&snapshot);
- let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap();
- assert_eq!(excerpt.id, anchor.excerpt_id);
- assert!(excerpt.contains(anchor));
- }
- }
- _ => {
- let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
- let base_text = util::RandomCharIter::new(&mut rng)
- .take(25)
- .collect::<String>();
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
+ });
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc!(
+ "
+ ZERO
+ one
+ TWO
+ three
+ six
+ "
+ ),
+ );
- buffers.push(cx.new_model(|cx| Buffer::local(base_text, cx)));
- buffers.last().unwrap()
- } else {
- buffers.choose(&mut rng).unwrap()
- };
+ assert_chunks_in_ranges(&snapshot);
+ assert_consistent_line_numbers(&snapshot);
+ assert_position_translation(&snapshot);
+ assert_line_indents(&snapshot);
- let buffer = buffer_handle.read(cx);
- let end_ix = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right);
- let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
- let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
- let prev_excerpt_ix = rng.gen_range(0..=expected_excerpts.len());
- let prev_excerpt_id = excerpt_ids
- .get(prev_excerpt_ix)
- .cloned()
- .unwrap_or_else(ExcerptId::max);
- let excerpt_ix = (prev_excerpt_ix + 1).min(expected_excerpts.len());
+ // Expand the first diff hunk
+ multibuffer.update(cx, |multibuffer, cx| {
+ let position = multibuffer.read(cx).anchor_before(Point::new(2, 2));
+ multibuffer.expand_diff_hunks(vec![position..position], cx)
+ });
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc!(
+ "
+ ZERO
+ one
+ - two
+ + TWO
+ three
+ six
+ "
+ ),
+ );
- log::info!(
- "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}",
- excerpt_ix,
- expected_excerpts.len(),
- buffer_handle.read(cx).remote_id(),
- buffer.text(),
- start_ix..end_ix,
- &buffer.text()[start_ix..end_ix]
- );
+ // Expand the second diff hunk
+ multibuffer.update(cx, |multibuffer, cx| {
+ let start = multibuffer.read(cx).anchor_before(Point::new(4, 0));
+ let end = multibuffer.read(cx).anchor_before(Point::new(5, 0));
+ multibuffer.expand_diff_hunks(vec![start..end], cx)
+ });
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc!(
+ "
+ ZERO
+ one
+ - two
+ + TWO
+ three
+ - four
+ - five
+ six
+ "
+ ),
+ );
- let excerpt_id = multibuffer.update(cx, |multibuffer, cx| {
- multibuffer
- .insert_excerpts_after(
- prev_excerpt_id,
- buffer_handle.clone(),
- [ExcerptRange {
- context: start_ix..end_ix,
- primary: None,
- }],
- cx,
- )
- .pop()
- .unwrap()
- });
-
- excerpt_ids.insert(excerpt_ix, excerpt_id);
- expected_excerpts.insert(excerpt_ix, (buffer_handle.clone(), anchor_range));
- }
- }
-
- if rng.gen_bool(0.3) {
- multibuffer.update(cx, |multibuffer, cx| {
- old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
- })
- }
-
- let snapshot = multibuffer.read(cx).snapshot(cx);
-
- let mut excerpt_starts = Vec::new();
- let mut expected_text = String::new();
- let mut expected_buffer_rows = Vec::new();
- for (buffer, range) in &expected_excerpts {
- let buffer = buffer.read(cx);
- let buffer_range = range.to_offset(buffer);
-
- excerpt_starts.push(TextSummary::from(expected_text.as_str()));
- expected_text.extend(buffer.text_for_range(buffer_range.clone()));
- expected_text.push('\n');
-
- let buffer_row_range = buffer.offset_to_point(buffer_range.start).row
- ..=buffer.offset_to_point(buffer_range.end).row;
- for row in buffer_row_range {
- expected_buffer_rows.push(Some(row));
- }
- }
- // Remove final trailing newline.
- if !expected_excerpts.is_empty() {
- expected_text.pop();
- }
-
- // Always report one buffer row
- if expected_buffer_rows.is_empty() {
- expected_buffer_rows.push(Some(0));
- }
+ assert_chunks_in_ranges(&snapshot);
+ assert_consistent_line_numbers(&snapshot);
+ assert_position_translation(&snapshot);
+ assert_line_indents(&snapshot);
- assert_eq!(snapshot.text(), expected_text);
- log::info!("MultiBuffer text: {:?}", expected_text);
-
- assert_eq!(
- snapshot.buffer_rows(MultiBufferRow(0)).collect::<Vec<_>>(),
- expected_buffer_rows,
- );
-
- for _ in 0..5 {
- let start_row = rng.gen_range(0..=expected_buffer_rows.len());
- assert_eq!(
- snapshot
- .buffer_rows(MultiBufferRow(start_row as u32))
- .collect::<Vec<_>>(),
- &expected_buffer_rows[start_row..],
- "buffer_rows({})",
- start_row
- );
- }
-
- assert_eq!(
- snapshot.widest_line_number(),
- expected_buffer_rows.into_iter().flatten().max().unwrap() + 1
+ // Edit the buffer before the first hunk
+ buffer.update(cx, |buffer, cx| {
+ buffer.edit_via_marked_text(
+ indoc!(
+ "
+ ZERO
+ oneΒ« hundred
+ thousandΒ»
+ TWO
+ three
+ six
+ "
+ ),
+ None,
+ cx,
);
+ });
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc!(
+ "
+ ZERO
+ one hundred
+ thousand
+ - two
+ + TWO
+ three
+ - four
+ - five
+ six
+ "
+ ),
+ );
- let mut excerpt_starts = excerpt_starts.into_iter();
- for (buffer, range) in &expected_excerpts {
- let buffer = buffer.read(cx);
- let buffer_id = buffer.remote_id();
- let buffer_range = range.to_offset(buffer);
- let buffer_start_point = buffer.offset_to_point(buffer_range.start);
- let buffer_start_point_utf16 =
- buffer.text_summary_for_range::<PointUtf16, _>(0..buffer_range.start);
-
- let excerpt_start = excerpt_starts.next().unwrap();
- let mut offset = excerpt_start.len;
- let mut buffer_offset = buffer_range.start;
- let mut point = excerpt_start.lines;
- let mut buffer_point = buffer_start_point;
- let mut point_utf16 = excerpt_start.lines_utf16();
- let mut buffer_point_utf16 = buffer_start_point_utf16;
- for ch in buffer
- .snapshot()
- .chunks(buffer_range.clone(), false)
- .flat_map(|c| c.text.chars())
- {
- for _ in 0..ch.len_utf8() {
- let left_offset = snapshot.clip_offset(offset, Bias::Left);
- let right_offset = snapshot.clip_offset(offset, Bias::Right);
- let buffer_left_offset = buffer.clip_offset(buffer_offset, Bias::Left);
- let buffer_right_offset = buffer.clip_offset(buffer_offset, Bias::Right);
- assert_eq!(
- left_offset,
- excerpt_start.len + (buffer_left_offset - buffer_range.start),
- "clip_offset({:?}, Left). buffer: {:?}, buffer offset: {:?}",
- offset,
- buffer_id,
- buffer_offset,
- );
- assert_eq!(
- right_offset,
- excerpt_start.len + (buffer_right_offset - buffer_range.start),
- "clip_offset({:?}, Right). buffer: {:?}, buffer offset: {:?}",
- offset,
- buffer_id,
- buffer_offset,
- );
-
- let left_point = snapshot.clip_point(point, Bias::Left);
- let right_point = snapshot.clip_point(point, Bias::Right);
- let buffer_left_point = buffer.clip_point(buffer_point, Bias::Left);
- let buffer_right_point = buffer.clip_point(buffer_point, Bias::Right);
- assert_eq!(
- left_point,
- excerpt_start.lines + (buffer_left_point - buffer_start_point),
- "clip_point({:?}, Left). buffer: {:?}, buffer point: {:?}",
- point,
- buffer_id,
- buffer_point,
- );
- assert_eq!(
- right_point,
- excerpt_start.lines + (buffer_right_point - buffer_start_point),
- "clip_point({:?}, Right). buffer: {:?}, buffer point: {:?}",
- point,
- buffer_id,
- buffer_point,
- );
-
- assert_eq!(
- snapshot.point_to_offset(left_point),
- left_offset,
- "point_to_offset({:?})",
- left_point,
- );
- assert_eq!(
- snapshot.offset_to_point(left_offset),
- left_point,
- "offset_to_point({:?})",
- left_offset,
- );
-
- offset += 1;
- buffer_offset += 1;
- if ch == '\n' {
- point += Point::new(1, 0);
- buffer_point += Point::new(1, 0);
- } else {
- point += Point::new(0, 1);
- buffer_point += Point::new(0, 1);
- }
- }
-
- for _ in 0..ch.len_utf16() {
- let left_point_utf16 =
- snapshot.clip_point_utf16(Unclipped(point_utf16), Bias::Left);
- let right_point_utf16 =
- snapshot.clip_point_utf16(Unclipped(point_utf16), Bias::Right);
- let buffer_left_point_utf16 =
- buffer.clip_point_utf16(Unclipped(buffer_point_utf16), Bias::Left);
- let buffer_right_point_utf16 =
- buffer.clip_point_utf16(Unclipped(buffer_point_utf16), Bias::Right);
- assert_eq!(
- left_point_utf16,
- excerpt_start.lines_utf16()
- + (buffer_left_point_utf16 - buffer_start_point_utf16),
- "clip_point_utf16({:?}, Left). buffer: {:?}, buffer point_utf16: {:?}",
- point_utf16,
- buffer_id,
- buffer_point_utf16,
- );
- assert_eq!(
- right_point_utf16,
- excerpt_start.lines_utf16()
- + (buffer_right_point_utf16 - buffer_start_point_utf16),
- "clip_point_utf16({:?}, Right). buffer: {:?}, buffer point_utf16: {:?}",
- point_utf16,
- buffer_id,
- buffer_point_utf16,
- );
-
- if ch == '\n' {
- point_utf16 += PointUtf16::new(1, 0);
- buffer_point_utf16 += PointUtf16::new(1, 0);
- } else {
- point_utf16 += PointUtf16::new(0, 1);
- buffer_point_utf16 += PointUtf16::new(0, 1);
- }
- }
- }
- }
-
- for (row, line) in expected_text.split('\n').enumerate() {
- assert_eq!(
- snapshot.line_len(MultiBufferRow(row as u32)),
- line.len() as u32,
- "line_len({}).",
- row
- );
- }
+ assert_chunks_in_ranges(&snapshot);
+ assert_consistent_line_numbers(&snapshot);
+ assert_position_translation(&snapshot);
+ assert_line_indents(&snapshot);
- let text_rope = Rope::from(expected_text.as_str());
- for _ in 0..10 {
- let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
- let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
+ // Recalculate the diff, changing the first diff hunk.
+ let _ = change_set.update(cx, |change_set, cx| {
+ change_set.recalculate_diff(buffer.read(cx).text_snapshot(), cx)
+ });
+ cx.run_until_parked();
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc!(
+ "
+ ZERO
+ one hundred
+ thousand
+ TWO
+ three
+ - four
+ - five
+ six
+ "
+ ),
+ );
- let text_for_range = snapshot
- .text_for_range(start_ix..end_ix)
- .collect::<String>();
- assert_eq!(
- text_for_range,
- &expected_text[start_ix..end_ix],
- "incorrect text for range {:?}",
- start_ix..end_ix
- );
+ assert_eq!(
+ snapshot
+ .diff_hunks_in_range(0..snapshot.len())
+ .map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0)
+ .collect::<Vec<_>>(),
+ &[0..4, 5..7]
+ );
+}
- let snapshot = multibuffer.read(cx).snapshot(cx);
- let excerpted_buffer_ranges = snapshot.range_to_buffer_ranges(start_ix..end_ix);
- let excerpted_buffers_text = excerpted_buffer_ranges
- .iter()
- .map(|(excerpt, buffer_range)| {
- excerpt
- .buffer()
- .text_for_range(buffer_range.clone())
- .collect::<String>()
- })
- .collect::<Vec<_>>()
- .join("\n");
- assert_eq!(excerpted_buffers_text, text_for_range);
- if !expected_excerpts.is_empty() {
- assert!(!excerpted_buffer_ranges.is_empty());
- }
+#[gpui::test]
+fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
+ let text = indoc!(
+ "
+ one
+ TWO
+ THREE
+ four
+ FIVE
+ six
+ "
+ );
+ let base_text = indoc!(
+ "
+ one
+ four
+ six
+ "
+ );
- let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
- assert_eq!(
- snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
- expected_summary,
- "incorrect summary for range {:?}",
- start_ix..end_ix
- );
- }
+ let buffer = cx.new_model(|cx| Buffer::local(text, cx));
+ let change_set =
+ cx.new_model(|cx| BufferChangeSet::new_with_base_text(base_text.to_string(), &buffer, cx));
+ cx.run_until_parked();
- // Anchor resolution
- let summaries = snapshot.summaries_for_anchors::<usize, _>(&anchors);
- assert_eq!(anchors.len(), summaries.len());
- for (anchor, resolved_offset) in anchors.iter().zip(summaries) {
- assert!(resolved_offset <= snapshot.len());
- assert_eq!(
- snapshot.summary_for_anchor::<usize>(anchor),
- resolved_offset
- );
- }
+ let multibuffer = cx.new_model(|cx| {
+ let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
+ multibuffer.add_change_set(change_set.clone(), cx);
+ multibuffer
+ });
- for _ in 0..10 {
- let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
- assert_eq!(
- snapshot.reversed_chars_at(end_ix).collect::<String>(),
- expected_text[..end_ix].chars().rev().collect::<String>(),
- );
- }
+ let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
+ (multibuffer.snapshot(cx), multibuffer.subscribe())
+ });
- for _ in 0..10 {
- let end_ix = rng.gen_range(0..=text_rope.len());
- let start_ix = rng.gen_range(0..=end_ix);
- assert_eq!(
- snapshot
- .bytes_in_range(start_ix..end_ix)
- .flatten()
- .copied()
- .collect::<Vec<_>>(),
- expected_text.as_bytes()[start_ix..end_ix].to_vec(),
- "bytes_in_range({:?})",
- start_ix..end_ix,
- );
- }
- }
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
+ });
- let snapshot = multibuffer.read(cx).snapshot(cx);
- for (old_snapshot, subscription) in old_versions {
- let edits = subscription.consume().into_inner();
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc!(
+ "
+ one
+ + TWO
+ + THREE
+ four
+ + FIVE
+ six
+ "
+ ),
+ );
- log::info!(
- "applying subscription edits to old text: {:?}: {:?}",
- old_snapshot.text(),
- edits,
+ // Regression test: expanding diff hunks that are already expanded should not change anything.
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.expand_diff_hunks(
+ vec![
+ snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_before(Point::new(2, 0)),
+ ],
+ cx,
);
+ });
- let mut text = old_snapshot.text();
- for edit in edits {
- let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
- text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
- }
- assert_eq!(text.to_string(), snapshot.text());
- }
+ assert_new_snapshot(
+ &multibuffer,
+ &mut snapshot,
+ &mut subscription,
+ cx,
+ indoc!(
+ "
+ one
+ + TWO
+ + THREE
+ four
+ + FIVE
+ six
+ "
+ ),
+ );
}
#[gpui::test]
-fn test_history(cx: &mut AppContext) {
- let test_settings = SettingsStore::test(cx);
- cx.set_global(test_settings);
- let group_interval: Duration = Duration::from_millis(1);
- let buffer_1 = cx.new_model(|cx| {
- let mut buf = Buffer::local("1234", cx);
- buf.set_group_interval(group_interval);
- buf
- });
- let buffer_2 = cx.new_model(|cx| {
- let mut buf = Buffer::local("5678", cx);
- buf.set_group_interval(group_interval);
- buf
+fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
+ let base_text_1 = indoc!(
+ "
+ one
+ two
+ three
+ four
+ five
+ six
+ "
+ );
+ let text_1 = indoc!(
+ "
+ ZERO
+ one
+ TWO
+ three
+ six
+ "
+ );
+ let base_text_2 = indoc!(
+ "
+ seven
+ eight
+ nine
+ ten
+ eleven
+ twelve
+ "
+ );
+ let text_2 = indoc!(
+ "
+ eight
+ nine
+ eleven
+ THIRTEEN
+ FOURTEEN
+ "
+ );
+
+ let buffer_1 = cx.new_model(|cx| Buffer::local(text_1, cx));
+ let buffer_2 = cx.new_model(|cx| Buffer::local(text_2, cx));
+ let change_set_1 = cx.new_model(|cx| {
+ BufferChangeSet::new_with_base_text(base_text_1.to_string(), &buffer_1, cx)
});
- let multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite));
- multibuffer.update(cx, |this, _| {
- this.history.group_interval = group_interval;
+ let change_set_2 = cx.new_model(|cx| {
+ BufferChangeSet::new_with_base_text(base_text_2.to_string(), &buffer_2, cx)
});
- multibuffer.update(cx, |multibuffer, cx| {
+ cx.run_until_parked();
+
+ let multibuffer = cx.new_model(|cx| {
+ let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
multibuffer.push_excerpts(
buffer_1.clone(),
[ExcerptRange {
- context: 0..buffer_1.read(cx).len(),
+ context: text::Anchor::MIN..text::Anchor::MAX,
primary: None,
}],
cx,
@@ -0,0 +1,264 @@
+use std::{
+ fmt::{Debug, Display},
+ marker::PhantomData,
+ ops::{Add, AddAssign, Sub, SubAssign},
+};
+use text::Point;
+
+#[repr(transparent)]
+pub struct TypedOffset<T> {
+ pub value: usize,
+ _marker: PhantomData<T>,
+}
+
+#[repr(transparent)]
+pub struct TypedPoint<T> {
+ pub value: Point,
+ _marker: PhantomData<T>,
+}
+
+#[repr(transparent)]
+pub struct TypedRow<T> {
+ pub value: u32,
+ _marker: PhantomData<T>,
+}
+
+impl<T> TypedOffset<T> {
+ pub fn new(offset: usize) -> Self {
+ Self {
+ value: offset,
+ _marker: PhantomData,
+ }
+ }
+
+ pub fn saturating_sub(self, n: TypedOffset<T>) -> Self {
+ Self {
+ value: self.value.saturating_sub(n.value),
+ _marker: PhantomData,
+ }
+ }
+
+ pub fn zero() -> Self {
+ Self::new(0)
+ }
+
+ pub fn is_zero(&self) -> bool {
+ self.value == 0
+ }
+}
+
+impl<T> TypedPoint<T> {
+ pub fn new(row: u32, column: u32) -> Self {
+ Self {
+ value: Point::new(row, column),
+ _marker: PhantomData,
+ }
+ }
+
+ pub fn wrap(point: Point) -> Self {
+ Self {
+ value: point,
+ _marker: PhantomData,
+ }
+ }
+
+ pub fn row(&self) -> u32 {
+ self.value.row
+ }
+
+ pub fn column(&self) -> u32 {
+ self.value.column
+ }
+
+ pub fn zero() -> Self {
+ Self::wrap(Point::zero())
+ }
+
+ pub fn is_zero(&self) -> bool {
+ self.value.is_zero()
+ }
+}
+
+impl<T> TypedRow<T> {
+ pub fn new(row: u32) -> Self {
+ Self {
+ value: row,
+ _marker: PhantomData,
+ }
+ }
+}
+
+impl<T> Copy for TypedOffset<T> {}
+impl<T> Copy for TypedPoint<T> {}
+impl<T> Copy for TypedRow<T> {}
+
+impl<T> Clone for TypedOffset<T> {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+impl<T> Clone for TypedPoint<T> {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+impl<T> Clone for TypedRow<T> {
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+
+impl<T> Default for TypedOffset<T> {
+ fn default() -> Self {
+ Self::new(0)
+ }
+}
+impl<T> Default for TypedPoint<T> {
+ fn default() -> Self {
+ Self::wrap(Point::default())
+ }
+}
+impl<T> Default for TypedRow<T> {
+ fn default() -> Self {
+ Self::new(0)
+ }
+}
+
+impl<T> PartialOrd for TypedOffset<T> {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.value.cmp(&other.value))
+ }
+}
+impl<T> PartialOrd for TypedPoint<T> {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.value.cmp(&other.value))
+ }
+}
+impl<T> PartialOrd for TypedRow<T> {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.value.cmp(&other.value))
+ }
+}
+
+impl<T> Ord for TypedOffset<T> {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.value.cmp(&other.value)
+ }
+}
+impl<T> Ord for TypedPoint<T> {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.value.cmp(&other.value)
+ }
+}
+impl<T> Ord for TypedRow<T> {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.value.cmp(&other.value)
+ }
+}
+
+impl<T> PartialEq for TypedOffset<T> {
+ fn eq(&self, other: &Self) -> bool {
+ self.value == other.value
+ }
+}
+impl<T> PartialEq for TypedPoint<T> {
+ fn eq(&self, other: &Self) -> bool {
+ self.value == other.value
+ }
+}
+impl<T> PartialEq for TypedRow<T> {
+ fn eq(&self, other: &Self) -> bool {
+ self.value == other.value
+ }
+}
+
+impl<T> Eq for TypedOffset<T> {}
+impl<T> Eq for TypedPoint<T> {}
+impl<T> Eq for TypedRow<T> {}
+
+impl<T> Debug for TypedOffset<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}Offset({})", type_name::<T>(), self.value)
+ }
+}
+impl<T> Debug for TypedPoint<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}Point({}, {})",
+ type_name::<T>(),
+ self.value.row,
+ self.value.column
+ )
+ }
+}
+impl<T> Debug for TypedRow<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}Row({})", type_name::<T>(), self.value)
+ }
+}
+
+impl<T> Display for TypedOffset<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ Display::fmt(&self.value, f)
+ }
+}
+impl<T> Display for TypedRow<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ Display::fmt(&self.value, f)
+ }
+}
+
+fn type_name<T>() -> &'static str {
+ std::any::type_name::<T>().split("::").last().unwrap()
+}
+
+impl<T> Add<TypedOffset<T>> for TypedOffset<T> {
+ type Output = Self;
+
+ fn add(self, other: Self) -> Self {
+ TypedOffset::new(self.value + other.value)
+ }
+}
+impl<T> Add<TypedPoint<T>> for TypedPoint<T> {
+ type Output = Self;
+
+ fn add(self, other: Self) -> Self {
+ TypedPoint::wrap(self.value + other.value)
+ }
+}
+
+impl<T> Sub<TypedOffset<T>> for TypedOffset<T> {
+ type Output = Self;
+ fn sub(self, other: Self) -> Self {
+ TypedOffset::new(self.value - other.value)
+ }
+}
+impl<T> Sub<TypedPoint<T>> for TypedPoint<T> {
+ type Output = Self;
+ fn sub(self, other: Self) -> Self {
+ TypedPoint::wrap(self.value - other.value)
+ }
+}
+
+impl<T> AddAssign<TypedOffset<T>> for TypedOffset<T> {
+ fn add_assign(&mut self, other: Self) {
+ self.value += other.value;
+ }
+}
+impl<T> AddAssign<TypedPoint<T>> for TypedPoint<T> {
+ fn add_assign(&mut self, other: Self) {
+ self.value += other.value;
+ }
+}
+
+impl<T> SubAssign<Self> for TypedOffset<T> {
+ fn sub_assign(&mut self, other: Self) {
+ self.value -= other.value;
+ }
+}
+impl<T> SubAssign<Self> for TypedRow<T> {
+ fn sub_assign(&mut self, other: Self) {
+ self.value -= other.value;
+ }
+}
@@ -1042,7 +1042,7 @@ impl OutlinePanel {
.show_excerpt_controls();
let expand_excerpt_control_height = 1.0;
if let Some(buffer_id) = scroll_to_buffer {
- let current_folded = active_editor.read(cx).buffer_folded(buffer_id, cx);
+ let current_folded = active_editor.read(cx).is_buffer_folded(buffer_id, cx);
if current_folded {
if show_excerpt_controls {
let previous_buffer_id = self
@@ -1059,7 +1059,9 @@ impl OutlinePanel {
.skip_while(|id| *id != buffer_id)
.nth(1);
if let Some(previous_buffer_id) = previous_buffer_id {
- if !active_editor.read(cx).buffer_folded(previous_buffer_id, cx)
+ if !active_editor
+ .read(cx)
+ .is_buffer_folded(previous_buffer_id, cx)
{
offset.y += expand_excerpt_control_height;
}
@@ -1418,7 +1420,7 @@ impl OutlinePanel {
};
active_editor.update(cx, |editor, cx| {
- buffers_to_unfold.retain(|buffer_id| editor.buffer_folded(*buffer_id, cx));
+ buffers_to_unfold.retain(|buffer_id| editor.is_buffer_folded(*buffer_id, cx));
});
self.select_entry(selected_entry, true, cx);
if buffers_to_unfold.is_empty() {
@@ -1504,7 +1506,7 @@ impl OutlinePanel {
if collapsed {
active_editor.update(cx, |editor, cx| {
- buffers_to_fold.retain(|buffer_id| !editor.buffer_folded(*buffer_id, cx));
+ buffers_to_fold.retain(|buffer_id| !editor.is_buffer_folded(*buffer_id, cx));
});
self.select_entry(selected_entry, true, cx);
if buffers_to_fold.is_empty() {
@@ -1569,7 +1571,7 @@ impl OutlinePanel {
self.collapsed_entries
.retain(|entry| !expanded_entries.contains(entry));
active_editor.update(cx, |editor, cx| {
- buffers_to_unfold.retain(|buffer_id| editor.buffer_folded(*buffer_id, cx));
+ buffers_to_unfold.retain(|buffer_id| editor.is_buffer_folded(*buffer_id, cx));
});
if buffers_to_unfold.is_empty() {
self.update_cached_entries(None, cx);
@@ -1617,7 +1619,7 @@ impl OutlinePanel {
self.collapsed_entries.extend(new_entries);
active_editor.update(cx, |editor, cx| {
- buffers_to_fold.retain(|buffer_id| !editor.buffer_folded(*buffer_id, cx));
+ buffers_to_fold.retain(|buffer_id| !editor.is_buffer_folded(*buffer_id, cx));
});
if buffers_to_fold.is_empty() {
self.update_cached_entries(None, cx);
@@ -1707,7 +1709,7 @@ impl OutlinePanel {
active_editor.update(cx, |editor, cx| {
buffers_to_toggle.retain(|buffer_id| {
- let folded = editor.buffer_folded(*buffer_id, cx);
+ let folded = editor.is_buffer_folded(*buffer_id, cx);
if fold {
!folded
} else {
@@ -2471,7 +2473,7 @@ impl OutlinePanel {
let worktree = file.map(|file| file.worktree.read(cx).snapshot());
let is_new = new_entries.contains(&excerpt_id)
|| !outline_panel.excerpts.contains_key(&buffer_id);
- let is_folded = active_editor.read(cx).buffer_folded(buffer_id, cx);
+ let is_folded = active_editor.read(cx).is_buffer_folded(buffer_id, cx);
buffer_excerpts
.entry(buffer_id)
.or_insert_with(|| (is_new, is_folded, Vec::new(), entry_id, worktree))
@@ -2875,7 +2877,7 @@ impl OutlinePanel {
.excerpt_containing(selection, cx)?;
let buffer_id = buffer.read(cx).remote_id();
- if editor.read(cx).buffer_folded(buffer_id, cx) {
+ if editor.read(cx).is_buffer_folded(buffer_id, cx) {
return self
.fs_entries
.iter()
@@ -3593,7 +3595,7 @@ impl OutlinePanel {
None
};
if let Some((buffer_id, entry_excerpts)) = excerpts_to_consider {
- if !active_editor.read(cx).buffer_folded(buffer_id, cx) {
+ if !active_editor.read(cx).is_buffer_folded(buffer_id, cx) {
outline_panel.add_excerpt_entries(
&mut generation_state,
buffer_id,
@@ -4004,12 +4006,12 @@ impl OutlinePanel {
.filter(|(match_range, _)| {
let editor = active_editor.read(cx);
if let Some(buffer_id) = match_range.start.buffer_id {
- if editor.buffer_folded(buffer_id, cx) {
+ if editor.is_buffer_folded(buffer_id, cx) {
return false;
}
}
if let Some(buffer_id) = match_range.start.buffer_id {
- if editor.buffer_folded(buffer_id, cx) {
+ if editor.is_buffer_folded(buffer_id, cx) {
return false;
}
}
@@ -4883,7 +4885,7 @@ fn subscribe_for_editor_events(
}
})
.map(|buffer_id| {
- if editor.read(cx).buffer_folded(*buffer_id, cx) {
+ if editor.read(cx).is_buffer_folded(*buffer_id, cx) {
latest_folded_buffer_id = Some(*buffer_id);
false
} else {
@@ -21,7 +21,7 @@ use language::{
deserialize_line_ending, deserialize_version, serialize_line_ending, serialize_version,
split_operations,
},
- Buffer, BufferEvent, Capability, DiskState, File as _, Language, Operation,
+ Buffer, BufferEvent, Capability, DiskState, File as _, Language, LanguageRegistry, Operation,
};
use rpc::{proto, AnyProtoClient, ErrorExt as _, TypedEnvelope};
use serde::Deserialize;
@@ -60,14 +60,14 @@ struct SharedBuffer {
lsp_handle: Option<OpenLspBufferHandle>,
}
-#[derive(Debug)]
pub struct BufferChangeSet {
pub buffer_id: BufferId,
- pub base_text: Option<Model<Buffer>>,
+ pub base_text: Option<language::BufferSnapshot>,
+ pub language: Option<Arc<Language>>,
pub diff_to_buffer: git::diff::BufferDiff,
pub recalculate_diff_task: Option<Task<Result<()>>>,
pub diff_updated_futures: Vec<oneshot::Sender<()>>,
- pub base_text_version: usize,
+ pub language_registry: Option<Arc<LanguageRegistry>>,
}
enum BufferStoreState {
@@ -1080,9 +1080,9 @@ impl BufferStore {
Ok(text) => text,
};
- let change_set = buffer.update(&mut cx, |buffer, cx| {
- cx.new_model(|_| BufferChangeSet::new(buffer))
- })?;
+ let change_set = cx
+ .new_model(|cx| BufferChangeSet::new(&buffer, cx))
+ .unwrap();
if let Some(text) = text {
change_set
@@ -1976,11 +1976,8 @@ impl BufferStore {
shared.unstaged_changes = Some(change_set.clone());
}
})?;
- let staged_text = change_set.read_with(&cx, |change_set, cx| {
- change_set
- .base_text
- .as_ref()
- .map(|buffer| buffer.read(cx).text())
+ let staged_text = change_set.read_with(&cx, |change_set, _| {
+ change_set.base_text.as_ref().map(|buffer| buffer.text())
})?;
Ok(proto::GetStagedTextResponse { staged_text })
}
@@ -2225,25 +2222,51 @@ impl BufferStore {
}
impl BufferChangeSet {
- pub fn new(buffer: &text::BufferSnapshot) -> Self {
+ pub fn new(buffer: &Model<Buffer>, cx: &mut ModelContext<Self>) -> Self {
+ cx.subscribe(buffer, |this, buffer, event, cx| match event {
+ BufferEvent::LanguageChanged => {
+ this.language = buffer.read(cx).language().cloned();
+ if let Some(base_text) = &this.base_text {
+ let snapshot = language::Buffer::build_snapshot(
+ base_text.as_rope().clone(),
+ this.language.clone(),
+ this.language_registry.clone(),
+ cx,
+ );
+ this.recalculate_diff_task = Some(cx.spawn(|this, mut cx| async move {
+ let base_text = cx.background_executor().spawn(snapshot).await;
+ this.update(&mut cx, |this, cx| {
+ this.base_text = Some(base_text);
+ cx.notify();
+ })
+ }));
+ }
+ }
+ _ => {}
+ })
+ .detach();
+
+ let buffer = buffer.read(cx);
+
Self {
buffer_id: buffer.remote_id(),
base_text: None,
diff_to_buffer: git::diff::BufferDiff::new(buffer),
recalculate_diff_task: None,
diff_updated_futures: Vec::new(),
- base_text_version: 0,
+ language: buffer.language().cloned(),
+ language_registry: buffer.language_registry(),
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn new_with_base_text(
base_text: String,
- buffer: text::BufferSnapshot,
+ buffer: &Model<Buffer>,
cx: &mut ModelContext<Self>,
) -> Self {
- let mut this = Self::new(&buffer);
- let _ = this.set_base_text(base_text, buffer, cx);
+ let mut this = Self::new(&buffer, cx);
+ let _ = this.set_base_text(base_text, buffer.read(cx).text_snapshot(), cx);
this
}
@@ -2266,8 +2289,8 @@ impl BufferChangeSet {
}
#[cfg(any(test, feature = "test-support"))]
- pub fn base_text_string(&self, cx: &AppContext) -> Option<String> {
- self.base_text.as_ref().map(|buffer| buffer.read(cx).text())
+ pub fn base_text_string(&self) -> Option<String> {
+ self.base_text.as_ref().map(|buffer| buffer.text())
}
pub fn set_base_text(
@@ -2289,7 +2312,6 @@ impl BufferChangeSet {
self.base_text = None;
self.diff_to_buffer = BufferDiff::new(&buffer_snapshot);
self.recalculate_diff_task.take();
- self.base_text_version += 1;
cx.notify();
}
}
@@ -2300,7 +2322,7 @@ impl BufferChangeSet {
cx: &mut ModelContext<Self>,
) -> oneshot::Receiver<()> {
if let Some(base_text) = self.base_text.clone() {
- self.recalculate_diff_internal(base_text.read(cx).text(), buffer_snapshot, false, cx)
+ self.recalculate_diff_internal(base_text.text(), buffer_snapshot, false, cx)
} else {
oneshot::channel().1
}
@@ -2316,19 +2338,30 @@ impl BufferChangeSet {
let (tx, rx) = oneshot::channel();
self.diff_updated_futures.push(tx);
self.recalculate_diff_task = Some(cx.spawn(|this, mut cx| async move {
- let (base_text, diff) = cx
+ let new_base_text = if base_text_changed {
+ let base_text_rope: Rope = base_text.as_str().into();
+ let snapshot = this.update(&mut cx, |this, cx| {
+ language::Buffer::build_snapshot(
+ base_text_rope,
+ this.language.clone(),
+ this.language_registry.clone(),
+ cx,
+ )
+ })?;
+ Some(cx.background_executor().spawn(snapshot).await)
+ } else {
+ None
+ };
+ let diff = cx
.background_executor()
- .spawn(async move {
- let diff = BufferDiff::build(&base_text, &buffer_snapshot).await;
- (base_text, diff)
+ .spawn({
+ let buffer_snapshot = buffer_snapshot.clone();
+ async move { BufferDiff::build(&base_text, &buffer_snapshot) }
})
.await;
this.update(&mut cx, |this, cx| {
- if base_text_changed {
- this.base_text_version += 1;
- this.base_text = Some(cx.new_model(|cx| {
- Buffer::local_normalized(Rope::from(base_text), LineEnding::default(), cx)
- }));
+ if let Some(new_base_text) = new_base_text {
+ this.base_text = Some(new_base_text)
}
this.diff_to_buffer = diff;
this.recalculate_diff_task.take();
@@ -2341,6 +2374,33 @@ impl BufferChangeSet {
}));
rx
}
+
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn recalculate_diff_sync(
+ &mut self,
+ mut base_text: String,
+ buffer_snapshot: text::BufferSnapshot,
+ base_text_changed: bool,
+ cx: &mut ModelContext<Self>,
+ ) {
+ LineEnding::normalize(&mut base_text);
+ let diff = BufferDiff::build(&base_text, &buffer_snapshot);
+ if base_text_changed {
+ self.base_text = Some(
+ cx.background_executor()
+ .clone()
+ .block(Buffer::build_snapshot(
+ base_text.into(),
+ self.language.clone(),
+ self.language_registry.clone(),
+ cx,
+ )),
+ );
+ }
+ self.diff_to_buffer = diff;
+ self.recalculate_diff_task.take();
+ cx.notify();
+ }
}
impl OpenBuffer {
@@ -1851,14 +1851,11 @@ impl LocalLspStore {
let edits_since_save = std::cell::LazyCell::new(|| {
let saved_version = buffer.read(cx).saved_version();
- Patch::new(
- snapshot
- .edits_since::<Unclipped<PointUtf16>>(saved_version)
- .collect(),
- )
+ Patch::new(snapshot.edits_since::<PointUtf16>(saved_version).collect())
});
let mut sanitized_diagnostics = Vec::new();
+
for entry in diagnostics {
let start;
let end;
@@ -1866,8 +1863,8 @@ impl LocalLspStore {
// Some diagnostics are based on files on disk instead of buffers'
// current contents. Adjust these diagnostics' ranges to reflect
// any unsaved edits.
- start = (*edits_since_save).old_to_new(entry.range.start);
- end = (*edits_since_save).old_to_new(entry.range.end);
+ start = Unclipped((*edits_since_save).old_to_new(entry.range.start.0));
+ end = Unclipped((*edits_since_save).old_to_new(entry.range.end.0));
} else {
start = entry.range.start;
end = entry.range.end;
@@ -5651,7 +5651,7 @@ async fn test_unstaged_changes_for_buffer(cx: &mut gpui::TestAppContext) {
assert_hunks(
unstaged_changes.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot),
&snapshot,
- &unstaged_changes.base_text.as_ref().unwrap().read(cx).text(),
+ &unstaged_changes.base_text.as_ref().unwrap().text(),
&[
(0..1, "", "// print goodbye\n"),
(
@@ -5681,7 +5681,7 @@ async fn test_unstaged_changes_for_buffer(cx: &mut gpui::TestAppContext) {
assert_hunks(
unstaged_changes.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot),
&snapshot,
- &unstaged_changes.base_text.as_ref().unwrap().read(cx).text(),
+ &unstaged_changes.base_text.as_ref().unwrap().text(),
&[(2..3, "", " println!(\"goodbye world\");\n")],
);
});
@@ -12,7 +12,7 @@ use language::{
use rpc::{proto, AnyProtoClient, TypedEnvelope};
use settings::{watch_config_file, SettingsLocation};
use task::{TaskContext, TaskVariables, VariableName};
-use text::BufferId;
+use text::{BufferId, OffsetRangeExt};
use util::ResultExt;
use crate::{
@@ -125,12 +125,10 @@ impl TaskStore {
.filter_map(|(k, v)| Some((k.parse().log_err()?, v))),
);
- for range in location
- .buffer
- .read(cx)
- .snapshot()
- .runnable_ranges(location.range.clone())
- {
+ let snapshot = location.buffer.read(cx).snapshot();
+ let range = location.range.to_offset(&snapshot);
+
+ for range in snapshot.runnable_ranges(range) {
for (capture_name, value) in range.extra_captures {
variables.insert(VariableName::Custom(capture_name.into()), value);
}
@@ -86,9 +86,9 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
.await
.unwrap();
- change_set.update(cx, |change_set, cx| {
+ change_set.update(cx, |change_set, _| {
assert_eq!(
- change_set.base_text_string(cx).unwrap(),
+ change_set.base_text_string().unwrap(),
"fn one() -> usize { 0 }"
);
});
@@ -150,9 +150,9 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
&[(Path::new("src/lib2.rs"), "fn one() -> usize { 100 }".into())],
);
cx.executor().run_until_parked();
- change_set.update(cx, |change_set, cx| {
+ change_set.update(cx, |change_set, _| {
assert_eq!(
- change_set.base_text_string(cx).unwrap(),
+ change_set.base_text_string().unwrap(),
"fn one() -> usize { 100 }"
);
});
@@ -4,16 +4,17 @@ mod point;
mod point_utf16;
mod unclipped;
-use chunk::{Chunk, ChunkSlice};
+use chunk::Chunk;
use rayon::iter::{IntoParallelIterator, ParallelIterator as _};
use smallvec::SmallVec;
use std::{
cmp, fmt, io, mem,
- ops::{AddAssign, Range},
+ ops::{self, AddAssign, Range},
str,
};
use sum_tree::{Bias, Dimension, SumTree};
+pub use chunk::ChunkSlice;
pub use offset_utf16::OffsetUtf16;
pub use point::Point;
pub use point_utf16::PointUtf16;
@@ -221,7 +222,7 @@ impl Rope {
}
pub fn summary(&self) -> TextSummary {
- self.chunks.summary().text.clone()
+ self.chunks.summary().text
}
pub fn len(&self) -> usize {
@@ -962,7 +963,7 @@ impl sum_tree::Summary for ChunkSummary {
}
/// Summary of a string of text.
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct TextSummary {
/// Length in UTF-8
pub len: usize,
@@ -989,6 +990,27 @@ impl TextSummary {
column: self.last_line_len_utf16,
}
}
+
+ pub fn newline() -> Self {
+ Self {
+ len: 1,
+ len_utf16: OffsetUtf16(1),
+ first_line_chars: 0,
+ last_line_chars: 0,
+ last_line_len_utf16: 0,
+ lines: Point::new(1, 0),
+ longest_row: 0,
+ longest_row_chars: 0,
+ }
+ }
+
+ pub fn add_newline(&mut self) {
+ self.len += 1;
+ self.len_utf16 += OffsetUtf16(self.len_utf16.0 + 1);
+ self.last_line_chars = 0;
+ self.last_line_len_utf16 = 0;
+ self.lines += Point::new(1, 0);
+ }
}
impl<'a> From<&'a str> for TextSummary {
@@ -1048,7 +1070,7 @@ impl sum_tree::Summary for TextSummary {
}
}
-impl std::ops::Add<Self> for TextSummary {
+impl ops::Add<Self> for TextSummary {
type Output = Self;
fn add(mut self, rhs: Self) -> Self::Output {
@@ -1057,7 +1079,7 @@ impl std::ops::Add<Self> for TextSummary {
}
}
-impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
+impl<'a> ops::AddAssign<&'a Self> for TextSummary {
fn add_assign(&mut self, other: &'a Self) {
let joined_chars = self.last_line_chars + other.first_line_chars;
if joined_chars > self.longest_row_chars {
@@ -1087,13 +1109,15 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
}
}
-impl std::ops::AddAssign<Self> for TextSummary {
+impl ops::AddAssign<Self> for TextSummary {
fn add_assign(&mut self, other: Self) {
*self += &other;
}
}
-pub trait TextDimension: 'static + for<'a> Dimension<'a, ChunkSummary> {
+pub trait TextDimension:
+ 'static + Clone + Copy + Default + for<'a> Dimension<'a, ChunkSummary> + std::fmt::Debug
+{
fn from_text_summary(summary: &TextSummary) -> Self;
fn from_chunk(chunk: ChunkSlice) -> Self;
fn add_assign(&mut self, other: &Self);
@@ -1129,7 +1153,7 @@ impl<'a> sum_tree::Dimension<'a, ChunkSummary> for TextSummary {
impl TextDimension for TextSummary {
fn from_text_summary(summary: &TextSummary) -> Self {
- summary.clone()
+ *summary
}
fn from_chunk(chunk: ChunkSlice) -> Self {
@@ -1240,6 +1264,118 @@ impl TextDimension for PointUtf16 {
}
}
+/// A pair of text dimensions in which only the first dimension is used for comparison,
+/// but both dimensions are updated during addition and subtraction.
+#[derive(Clone, Copy, Debug)]
+pub struct DimensionPair<K, V> {
+ pub key: K,
+ pub value: Option<V>,
+}
+
+impl<K: Default, V: Default> Default for DimensionPair<K, V> {
+ fn default() -> Self {
+ Self {
+ key: Default::default(),
+ value: Some(Default::default()),
+ }
+ }
+}
+
+impl<K, V> cmp::Ord for DimensionPair<K, V>
+where
+ K: cmp::Ord,
+{
+ fn cmp(&self, other: &Self) -> cmp::Ordering {
+ self.key.cmp(&other.key)
+ }
+}
+
+impl<K, V> cmp::PartialOrd for DimensionPair<K, V>
+where
+ K: cmp::PartialOrd,
+{
+ fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
+ self.key.partial_cmp(&other.key)
+ }
+}
+
+impl<K, V> cmp::PartialEq for DimensionPair<K, V>
+where
+ K: cmp::PartialEq,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.key.eq(&other.key)
+ }
+}
+
+impl<K, V> ops::Sub for DimensionPair<K, V>
+where
+ K: ops::Sub<K, Output = K>,
+ V: ops::Sub<V, Output = V>,
+{
+ type Output = Self;
+
+ fn sub(self, rhs: Self) -> Self::Output {
+ Self {
+ key: self.key - rhs.key,
+ value: self.value.zip(rhs.value).map(|(a, b)| a - b),
+ }
+ }
+}
+
+impl<K, V> cmp::Eq for DimensionPair<K, V> where K: cmp::Eq {}
+
+impl<'a, K, V> sum_tree::Dimension<'a, ChunkSummary> for DimensionPair<K, V>
+where
+ K: sum_tree::Dimension<'a, ChunkSummary>,
+ V: sum_tree::Dimension<'a, ChunkSummary>,
+{
+ fn zero(_cx: &()) -> Self {
+ Self {
+ key: K::zero(_cx),
+ value: Some(V::zero(_cx)),
+ }
+ }
+
+ fn add_summary(&mut self, summary: &'a ChunkSummary, _cx: &()) {
+ self.key.add_summary(summary, _cx);
+ if let Some(value) = &mut self.value {
+ value.add_summary(summary, _cx);
+ }
+ }
+}
+
+impl<K, V> TextDimension for DimensionPair<K, V>
+where
+ K: TextDimension,
+ V: TextDimension,
+{
+ fn add_assign(&mut self, other: &Self) {
+ self.key.add_assign(&other.key);
+ if let Some(value) = &mut self.value {
+ if let Some(other_value) = other.value.as_ref() {
+ value.add_assign(other_value);
+ } else {
+ self.value.take();
+ }
+ }
+ }
+
+ fn from_chunk(chunk: ChunkSlice) -> Self {
+ Self {
+ key: K::from_chunk(chunk),
+ value: Some(V::from_chunk(chunk)),
+ }
+ }
+
+ fn from_text_summary(summary: &TextSummary) -> Self {
+ Self {
+ key: K::from_text_summary(summary),
+ value: Some(V::from_text_summary(summary)),
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -1,4 +1,4 @@
-use crate::{chunk::ChunkSlice, ChunkSummary, TextDimension, TextSummary};
+use crate::ChunkSummary;
use std::ops::{Add, AddAssign, Sub, SubAssign};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -22,20 +22,6 @@ impl<'a, T: sum_tree::Dimension<'a, ChunkSummary>> sum_tree::Dimension<'a, Chunk
}
}
-impl<T: TextDimension> TextDimension for Unclipped<T> {
- fn from_text_summary(summary: &TextSummary) -> Self {
- Unclipped(T::from_text_summary(summary))
- }
-
- fn from_chunk(chunk: ChunkSlice) -> Self {
- Unclipped(T::from_chunk(chunk))
- }
-
- fn add_assign(&mut self, other: &Self) {
- TextDimension::add_assign(&mut self.0, &other.0);
- }
-}
-
impl<T: Add<T, Output = T>> Add<Unclipped<T>> for Unclipped<T> {
type Output = Unclipped<T>;
@@ -115,14 +115,29 @@ impl<'a, T: Summary, D1: Dimension<'a, T>, D2: Dimension<'a, T>> Dimension<'a, T
}
}
-impl<'a, S: Summary, D1: SeekTarget<'a, S, D1> + Dimension<'a, S>, D2: Dimension<'a, S>>
- SeekTarget<'a, S, (D1, D2)> for D1
+impl<'a, S, D1, D2> SeekTarget<'a, S, (D1, D2)> for D1
+where
+ S: Summary,
+ D1: SeekTarget<'a, S, D1> + Dimension<'a, S>,
+ D2: Dimension<'a, S>,
{
fn cmp(&self, cursor_location: &(D1, D2), cx: &S::Context) -> Ordering {
self.cmp(&cursor_location.0, cx)
}
}
+impl<'a, S, D1, D2, D3> SeekTarget<'a, S, ((D1, D2), D3)> for D1
+where
+ S: Summary,
+ D1: SeekTarget<'a, S, D1> + Dimension<'a, S>,
+ D2: Dimension<'a, S>,
+ D3: Dimension<'a, S>,
+{
+ fn cmp(&self, cursor_location: &((D1, D2), D3), cx: &S::Context) -> Ordering {
+ self.cmp(&cursor_location.0 .0, cx)
+ }
+}
+
struct End<D>(PhantomData<D>);
impl<D> End<D> {
@@ -5,11 +5,7 @@ pub mod terminal_scrollbar;
pub mod terminal_tab_tooltip;
use collections::HashSet;
-use editor::{
- actions::SelectAll,
- scroll::{Autoscroll, ScrollbarAutoHide},
- Editor, EditorSettings,
-};
+use editor::{actions::SelectAll, scroll::ScrollbarAutoHide, Editor, EditorSettings};
use futures::{stream::FuturesUnordered, StreamExt};
use gpui::{
anchored, deferred, div, impl_actions, AnyElement, AppContext, DismissEvent, EventEmitter,
@@ -17,7 +13,6 @@ use gpui::{
MouseDownEvent, Pixels, Render, ScrollWheelEvent, Stateful, Styled, Subscription, Task, View,
VisualContext, WeakModel, WeakView,
};
-use language::Bias;
use persistence::TERMINAL_DB;
use project::{search::SearchQuery, terminals::TerminalKind, Fs, Metadata, Project};
use schemars::JsonSchema;
@@ -885,19 +880,13 @@ fn subscribe_for_terminal_events(
active_editor
.downgrade()
.update(&mut cx, |editor, cx| {
- let snapshot = editor.snapshot(cx).display_snapshot;
- let point = snapshot.buffer_snapshot.clip_point(
+ editor.go_to_singleton_buffer_point(
language::Point::new(
row.saturating_sub(1),
col.saturating_sub(1),
),
- Bias::Left,
- );
- editor.change_selections(
- Some(Autoscroll::center()),
cx,
- |s| s.select_ranges([point..point]),
- );
+ )
})
.log_err();
}
@@ -42,6 +42,7 @@ where
self.0
}
+ #[must_use]
pub fn compose(&self, new_edits_iter: impl IntoIterator<Item = Edit<T>>) -> Self {
let mut old_edits_iter = self.0.iter().cloned().peekable();
let mut new_edits_iter = new_edits_iter.into_iter().peekable();
@@ -1507,9 +1507,9 @@ impl Buffer {
let mut rope_cursor = self.visible_text.cursor(0);
disjoint_ranges.map(move |range| {
position.add_assign(&rope_cursor.summary(range.start));
- let start = position.clone();
+ let start = position;
position.add_assign(&rope_cursor.summary(range.end));
- let end = position.clone();
+ let end = position;
start..end
})
}
@@ -2029,11 +2029,11 @@ impl BufferSnapshot {
row_range: Range<u32>,
) -> impl Iterator<Item = (u32, LineIndent)> + '_ {
let start = Point::new(row_range.start, 0).to_offset(self);
- let end = Point::new(row_range.end - 1, self.line_len(row_range.end - 1)).to_offset(self);
+ let end = Point::new(row_range.end, self.line_len(row_range.end)).to_offset(self);
let mut chunks = self.as_rope().chunks_in_range(start..end);
let mut row = row_range.start;
- let mut done = start == end;
+ let mut done = false;
std::iter::from_fn(move || {
if done {
None
@@ -2071,7 +2071,7 @@ impl BufferSnapshot {
}
let mut row = end_point.row;
- let mut done = start == end;
+ let mut done = false;
std::iter::from_fn(move || {
if done {
None
@@ -2168,7 +2168,7 @@ impl BufferSnapshot {
}
position.add_assign(&text_cursor.summary(fragment_offset));
- (position.clone(), payload)
+ (position, payload)
})
}
@@ -2176,10 +2176,14 @@ impl BufferSnapshot {
where
D: TextDimension,
{
+ self.text_summary_for_range(0..self.offset_for_anchor(anchor))
+ }
+
+ pub fn offset_for_anchor(&self, anchor: &Anchor) -> usize {
if *anchor == Anchor::MIN {
- D::zero(&())
+ 0
} else if *anchor == Anchor::MAX {
- D::from_text_summary(&self.visible_text.summary())
+ self.visible_text.len()
} else {
let anchor_key = InsertionFragmentKey {
timestamp: anchor.timestamp,
@@ -2217,7 +2221,7 @@ impl BufferSnapshot {
if fragment.visible {
fragment_offset += anchor.offset - insertion.split_offset;
}
- self.text_summary_for_range(0..fragment_offset)
+ fragment_offset
}
}
@@ -2580,16 +2584,16 @@ impl<'a, D: TextDimension + Ord, F: FnMut(&FragmentSummary) -> bool> Iterator fo
}
let fragment_summary = self.visible_cursor.summary(visible_end);
- let mut new_end = self.new_end.clone();
+ let mut new_end = self.new_end;
new_end.add_assign(&fragment_summary);
if let Some((edit, range)) = pending_edit.as_mut() {
- edit.new.end = new_end.clone();
+ edit.new.end = new_end;
range.end = end_anchor;
} else {
pending_edit = Some((
Edit {
- old: self.old_end.clone()..self.old_end.clone(),
- new: self.new_end.clone()..new_end.clone(),
+ old: self.old_end..self.old_end,
+ new: self.new_end..new_end,
},
start_anchor..end_anchor,
));
@@ -2609,16 +2613,16 @@ impl<'a, D: TextDimension + Ord, F: FnMut(&FragmentSummary) -> bool> Iterator fo
self.deleted_cursor.seek_forward(cursor.start().deleted);
}
let fragment_summary = self.deleted_cursor.summary(deleted_end);
- let mut old_end = self.old_end.clone();
+ let mut old_end = self.old_end;
old_end.add_assign(&fragment_summary);
if let Some((edit, range)) = pending_edit.as_mut() {
- edit.old.end = old_end.clone();
+ edit.old.end = old_end;
range.end = end_anchor;
} else {
pending_edit = Some((
Edit {
- old: self.old_end.clone()..old_end.clone(),
- new: self.new_end.clone()..self.new_end.clone(),
+ old: self.old_end..old_end,
+ new: self.new_end..self.new_end,
},
start_anchor..end_anchor,
));
@@ -138,22 +138,27 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
Vim::action(editor, cx, |vim, action: &GoToLine, cx| {
vim.switch_mode(Mode::Normal, false, cx);
let result = vim.update_editor(cx, |vim, editor, cx| {
- action.range.head().buffer_row(vim, editor, cx)
+ let snapshot = editor.snapshot(cx);
+ let buffer_row = action.range.head().buffer_row(vim, editor, cx)?;
+ let current = editor.selections.newest::<Point>(cx);
+ let target = snapshot
+ .buffer_snapshot
+ .clip_point(Point::new(buffer_row.0, current.head().column), Bias::Left);
+ editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+ s.select_ranges([target..target]);
+ });
+
+ anyhow::Ok(())
});
- let buffer_row = match result {
- None => return,
- Some(e @ Err(_)) => {
- let Some(workspace) = vim.workspace(cx) else {
- return;
- };
- workspace.update(cx, |workspace, cx| {
- e.notify_err(workspace, cx);
- });
+ if let Some(e @ Err(_)) = result {
+ let Some(workspace) = vim.workspace(cx) else {
return;
- }
- Some(Ok(result)) => result,
- };
- vim.move_cursor(Motion::StartOfDocument, Some(buffer_row.0 as usize + 1), cx);
+ };
+ workspace.update(cx, |workspace, cx| {
+ e.notify_err(workspace, cx);
+ });
+ return;
+ }
});
Vim::action(editor, cx, |vim, action: &YankCommand, cx| {
@@ -462,7 +467,22 @@ impl Position {
) -> Result<MultiBufferRow> {
let snapshot = editor.snapshot(cx);
let target = match self {
- Position::Line { row, offset } => row.saturating_add_signed(offset.saturating_sub(1)),
+ Position::Line { row, offset } => {
+ if let Some(anchor) = editor.active_excerpt(cx).and_then(|(_, buffer, _)| {
+ editor.buffer().read(cx).buffer_point_to_anchor(
+ &buffer,
+ Point::new(row.saturating_sub(1), 0),
+ cx,
+ )
+ }) {
+ anchor
+ .to_point(&snapshot.buffer_snapshot)
+ .row
+ .saturating_add_signed(*offset)
+ } else {
+ row.saturating_add_signed(offset.saturating_sub(1))
+ }
+ }
Position::Mark { name, offset } => {
let Some(mark) = vim.marks.get(&name.to_string()).and_then(|vec| vec.last()) else {
return Err(anyhow!("mark {} not set", name));
@@ -697,7 +717,8 @@ fn generate_commands(_: &AppContext) -> Vec<VimCommand> {
VimCommand::new(("foldc", "lose"), editor::actions::Fold)
.bang(editor::actions::FoldRecursive)
.range(act_on_range),
- VimCommand::new(("dif", "fupdate"), editor::actions::ToggleHunkDiff).range(act_on_range),
+ VimCommand::new(("dif", "fupdate"), editor::actions::ToggleSelectedDiffHunks)
+ .range(act_on_range),
VimCommand::new(("rev", "ert"), editor::actions::RevertSelectedHunks).range(act_on_range),
VimCommand::new(("d", "elete"), VisualDeleteLine).range(select_range),
VimCommand::new(("y", "ank"), gpui::NoAction).range(|_, range| {
@@ -4,7 +4,7 @@ use editor::{
self, find_boundary, find_preceding_boundary_display_point, FindRange, TextLayoutDetails,
},
scroll::Autoscroll,
- Anchor, Bias, DisplayPoint, Editor, RowExt, ToOffset,
+ Anchor, Bias, DisplayPoint, Editor, RowExt, ToOffset, ToPoint,
};
use gpui::{actions, impl_actions, px, ViewContext};
use language::{CharKind, Point, Selection, SelectionGoal};
@@ -847,7 +847,10 @@ impl Motion {
SelectionGoal::None,
),
CurrentLine => (next_line_end(map, point, times), SelectionGoal::None),
- StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None),
+ StartOfDocument => (
+ start_of_document(map, point, maybe_times),
+ SelectionGoal::None,
+ ),
EndOfDocument => (
end_of_document(map, point, maybe_times),
SelectionGoal::None,
@@ -1956,25 +1959,96 @@ fn start_of_next_sentence(map: &DisplaySnapshot, end_of_sentence: usize) -> Opti
Some(map.buffer_snapshot.len())
}
-fn start_of_document(map: &DisplaySnapshot, point: DisplayPoint, line: usize) -> DisplayPoint {
- let mut new_point = Point::new((line - 1) as u32, 0).to_display_point(map);
- *new_point.column_mut() = point.column();
- map.clip_point(new_point, Bias::Left)
+fn go_to_line(map: &DisplaySnapshot, display_point: DisplayPoint, line: usize) -> DisplayPoint {
+ let point = map.display_point_to_point(display_point, Bias::Left);
+ let Some(mut excerpt) = map.buffer_snapshot.excerpt_containing(point..point) else {
+ return display_point;
+ };
+ let offset = excerpt.buffer().point_to_offset(
+ excerpt
+ .buffer()
+ .clip_point(Point::new((line - 1) as u32, point.column), Bias::Left),
+ );
+ let buffer_range = excerpt.buffer_range();
+ if offset >= buffer_range.start && offset <= buffer_range.end {
+ let point = map
+ .buffer_snapshot
+ .offset_to_point(excerpt.map_offset_from_buffer(offset));
+ return map.clip_point(map.point_to_display_point(point, Bias::Left), Bias::Left);
+ }
+ let mut last_position = None;
+ for (excerpt, buffer, range) in map.buffer_snapshot.excerpts() {
+ let excerpt_range = language::ToOffset::to_offset(&range.context.start, &buffer)
+ ..language::ToOffset::to_offset(&range.context.end, &buffer);
+ if offset >= excerpt_range.start && offset <= excerpt_range.end {
+ let text_anchor = buffer.anchor_after(offset);
+ let anchor = Anchor::in_buffer(excerpt, buffer.remote_id(), text_anchor);
+ return anchor.to_display_point(map);
+ } else if offset <= excerpt_range.start {
+ let anchor = Anchor::in_buffer(excerpt, buffer.remote_id(), range.context.start);
+ return anchor.to_display_point(map);
+ } else {
+ last_position = Some(Anchor::in_buffer(
+ excerpt,
+ buffer.remote_id(),
+ range.context.end,
+ ));
+ }
+ }
+
+ let mut last_point = last_position.unwrap().to_point(&map.buffer_snapshot);
+ last_point.column = point.column;
+
+ map.clip_point(
+ map.point_to_display_point(
+ map.buffer_snapshot.clip_point(point, Bias::Left),
+ Bias::Left,
+ ),
+ Bias::Left,
+ )
+}
+
+fn start_of_document(
+ map: &DisplaySnapshot,
+ display_point: DisplayPoint,
+ maybe_times: Option<usize>,
+) -> DisplayPoint {
+ if let Some(times) = maybe_times {
+ return go_to_line(map, display_point, times);
+ }
+
+ let point = map.display_point_to_point(display_point, Bias::Left);
+ let mut first_point = Point::zero();
+ first_point.column = point.column;
+
+ map.clip_point(
+ map.point_to_display_point(
+ map.buffer_snapshot.clip_point(first_point, Bias::Left),
+ Bias::Left,
+ ),
+ Bias::Left,
+ )
}
fn end_of_document(
map: &DisplaySnapshot,
- point: DisplayPoint,
- line: Option<usize>,
+ display_point: DisplayPoint,
+ maybe_times: Option<usize>,
) -> DisplayPoint {
- let new_row = if let Some(line) = line {
- (line - 1) as u32
- } else {
- map.buffer_snapshot.max_row().0
+ if let Some(times) = maybe_times {
+ return go_to_line(map, display_point, times);
};
+ let point = map.display_point_to_point(display_point, Bias::Left);
+ let mut last_point = map.buffer_snapshot.max_point();
+ last_point.column = point.column;
- let new_point = Point::new(new_row, point.column());
- map.clip_point(new_point.to_display_point(map), Bias::Left)
+ map.clip_point(
+ map.point_to_display_point(
+ map.buffer_snapshot.clip_point(last_point, Bias::Left),
+ Bias::Left,
+ ),
+ Bias::Left,
+ )
}
fn matching_tag(map: &DisplaySnapshot, head: DisplayPoint) -> Option<DisplayPoint> {
@@ -2545,7 +2619,7 @@ fn section_motion(
direction: Direction,
is_start: bool,
) -> DisplayPoint {
- if let Some((_, _, buffer)) = map.buffer_snapshot.as_singleton() {
+ if map.buffer_snapshot.as_singleton().is_some() {
for _ in 0..times {
let offset = map
.display_point_to_point(display_point, Bias::Left)
@@ -2553,13 +2627,14 @@ fn section_motion(
let range = if direction == Direction::Prev {
0..offset
} else {
- offset..buffer.len()
+ offset..map.buffer_snapshot.len()
};
// we set a max start depth here because we want a section to only be "top level"
// similar to vim's default of '{' in the first column.
// (and without it, ]] at the start of editor.rs is -very- slow)
- let mut possibilities = buffer
+ let mut possibilities = map
+ .buffer_snapshot
.text_object_ranges(range, language::TreeSitterOptions::max_start_depth(3))
.filter(|(_, object)| {
matches!(
@@ -2591,7 +2666,7 @@ fn section_motion(
let offset = if direction == Direction::Prev {
possibilities.max().unwrap_or(0)
} else {
- possibilities.min().unwrap_or(buffer.len())
+ possibilities.min().unwrap_or(map.buffer_snapshot.len())
};
let new_point = map.clip_point(offset.to_display_point(&map), Bias::Left);
@@ -494,7 +494,7 @@ pub fn surrounding_html_tag(
let snapshot = &map.buffer_snapshot;
let offset = head.to_offset(map, Bias::Left);
- let excerpt = snapshot.excerpt_containing(offset..offset)?;
+ let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
let buffer = excerpt.buffer();
let offset = excerpt.map_offset_to_buffer(offset);
@@ -664,7 +664,7 @@ fn text_object(
let snapshot = &map.buffer_snapshot;
let offset = relative_to.to_offset(map, Bias::Left);
- let excerpt = snapshot.excerpt_containing(offset..offset)?;
+ let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
let buffer = excerpt.buffer();
let offset = excerpt.map_offset_to_buffer(offset);
@@ -710,7 +710,7 @@ fn argument(
let offset = relative_to.to_offset(map, Bias::Left);
// The `argument` vim text object uses the syntax tree, so we operate at the buffer level and map back to the display level
- let excerpt = snapshot.excerpt_containing(offset..offset)?;
+ let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
let buffer = excerpt.buffer();
fn comma_delimited_range_at(
@@ -102,6 +102,8 @@ use crate::persistence::{
SerializedAxis,
};
+pub const SERIALIZATION_THROTTLE_TIME: Duration = Duration::from_millis(200);
+
static ZED_WINDOW_SIZE: LazyLock<Option<Size<Pixels>>> = LazyLock::new(|| {
env::var("ZED_WINDOW_SIZE")
.ok()
@@ -4344,7 +4346,6 @@ impl Workspace {
cx: &mut AsyncWindowContext,
) -> Result<()> {
const CHUNK_SIZE: usize = 200;
- const THROTTLE_TIME: Duration = Duration::from_millis(200);
let mut serializable_items = items_rx.ready_chunks(CHUNK_SIZE);
@@ -4369,7 +4370,9 @@ impl Workspace {
}
}
- cx.background_executor().timer(THROTTLE_TIME).await;
+ cx.background_executor()
+ .timer(SERIALIZATION_THROTTLE_TIME)
+ .await;
}
Ok(())
@@ -1469,7 +1469,7 @@ mod tests {
use workspace::{
item::{Item, ItemHandle},
open_new, open_paths, pane, NewFile, OpenVisible, SaveIntent, SplitDirection,
- WorkspaceHandle,
+ WorkspaceHandle, SERIALIZATION_THROTTLE_TIME,
};
#[gpui::test]
@@ -2866,7 +2866,9 @@ mod tests {
})
.unwrap();
- cx.run_until_parked();
+ cx.background_executor
+ .advance_clock(SERIALIZATION_THROTTLE_TIME);
+ cx.update(|_| {});
editor_1.assert_released();
editor_2.assert_released();
buffer.assert_released();
@@ -6,7 +6,6 @@ use cli::{ipc::IpcSender, CliRequest, CliResponse};
use client::parse_zed_link;
use collections::HashMap;
use db::kvp::KEY_VALUE_STORE;
-use editor::scroll::Autoscroll;
use editor::Editor;
use fs::Fs;
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
@@ -14,7 +13,7 @@ use futures::channel::{mpsc, oneshot};
use futures::future::join_all;
use futures::{FutureExt, SinkExt, StreamExt};
use gpui::{AppContext, AsyncAppContext, Global, WindowHandle};
-use language::{Bias, Point};
+use language::Point;
use recent_projects::{open_ssh_project, SshSettings};
use remote::SshConnectionOptions;
use settings::Settings;
@@ -236,11 +235,7 @@ pub async fn open_paths_with_positions(
workspace
.update(cx, |_, cx| {
active_editor.update(cx, |editor, cx| {
- let snapshot = editor.snapshot(cx).display_snapshot;
- let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
- editor.change_selections(Some(Autoscroll::center()), cx, |s| {
- s.select_ranges([point..point])
- });
+ editor.go_to_singleton_buffer_point(point, cx);
});
})
.log_err();