Add staged status information to diff hunks (#24475)

Release Notes:

- Render unstaged hunks in the project diff editor with a slashed
background

---------

Co-authored-by: maxbrunsfeld <max@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
Cole Miller 2025-02-10 21:43:25 -05:00 committed by GitHub
parent a9de9e3cb4
commit 8f75fe25e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 1132 additions and 753 deletions

48
Cargo.lock generated
View file

@ -2024,6 +2024,24 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "buffer_diff"
version = "0.1.0"
dependencies = [
"anyhow",
"futures 0.3.31",
"git2",
"gpui",
"language",
"pretty_assertions",
"rope",
"serde_json",
"sum_tree",
"text",
"unindent",
"util",
]
[[package]] [[package]]
name = "built" name = "built"
version = "0.7.5" version = "0.7.5"
@ -2742,6 +2760,7 @@ dependencies = [
"axum", "axum",
"axum-extra", "axum-extra",
"base64 0.22.1", "base64 0.22.1",
"buffer_diff",
"call", "call",
"channel", "channel",
"chrono", "chrono",
@ -2753,7 +2772,6 @@ dependencies = [
"ctor", "ctor",
"dashmap 6.1.0", "dashmap 6.1.0",
"derive_more", "derive_more",
"diff 0.1.0",
"editor", "editor",
"env_logger 0.11.6", "env_logger 0.11.6",
"envy", "envy",
@ -3860,24 +3878,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "diff"
version = "0.1.0"
dependencies = [
"futures 0.3.31",
"git2",
"gpui",
"language",
"log",
"pretty_assertions",
"rope",
"serde_json",
"sum_tree",
"text",
"unindent",
"util",
]
[[package]] [[package]]
name = "diff" name = "diff"
version = "0.1.13" version = "0.1.13"
@ -4041,6 +4041,7 @@ dependencies = [
"aho-corasick", "aho-corasick",
"anyhow", "anyhow",
"assets", "assets",
"buffer_diff",
"chrono", "chrono",
"client", "client",
"clock", "clock",
@ -4048,7 +4049,6 @@ dependencies = [
"convert_case 0.7.1", "convert_case 0.7.1",
"ctor", "ctor",
"db", "db",
"diff 0.1.0",
"emojis", "emojis",
"env_logger 0.11.6", "env_logger 0.11.6",
"file_icons", "file_icons",
@ -5347,9 +5347,9 @@ name = "git_ui"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"buffer_diff",
"collections", "collections",
"db", "db",
"diff 0.1.0",
"editor", "editor",
"feature_flags", "feature_flags",
"futures 0.3.31", "futures 0.3.31",
@ -7980,10 +7980,10 @@ name = "multi_buffer"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"buffer_diff",
"clock", "clock",
"collections", "collections",
"ctor", "ctor",
"diff 0.1.0",
"env_logger 0.11.6", "env_logger 0.11.6",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
@ -9995,7 +9995,7 @@ version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [ dependencies = [
"diff 0.1.13", "diff",
"yansi", "yansi",
] ]
@ -10088,10 +10088,10 @@ dependencies = [
"aho-corasick", "aho-corasick",
"anyhow", "anyhow",
"async-trait", "async-trait",
"buffer_diff",
"client", "client",
"clock", "clock",
"collections", "collections",
"diff 0.1.0",
"env_logger 0.11.6", "env_logger 0.11.6",
"fancy-regex 0.14.0", "fancy-regex 0.14.0",
"fs", "fs",

View file

@ -34,7 +34,7 @@ members = [
"crates/db", "crates/db",
"crates/deepseek", "crates/deepseek",
"crates/diagnostics", "crates/diagnostics",
"crates/diff", "crates/buffer_diff",
"crates/docs_preprocessor", "crates/docs_preprocessor",
"crates/editor", "crates/editor",
"crates/evals", "crates/evals",
@ -235,7 +235,7 @@ copilot = { path = "crates/copilot" }
db = { path = "crates/db" } db = { path = "crates/db" }
deepseek = { path = "crates/deepseek" } deepseek = { path = "crates/deepseek" }
diagnostics = { path = "crates/diagnostics" } diagnostics = { path = "crates/diagnostics" }
diff = { path = "crates/diff" } buffer_diff = { path = "crates/buffer_diff" }
editor = { path = "crates/editor" } editor = { path = "crates/editor" }
extension = { path = "crates/extension" } extension = { path = "crates/extension" }
extension_host = { path = "crates/extension_host" } extension_host = { path = "crates/extension_host" }

View file

@ -1,5 +1,5 @@
[package] [package]
name = "diff" name = "buffer_diff"
version = "0.1.0" version = "0.1.0"
edition.workspace = true edition.workspace = true
publish.workspace = true publish.workspace = true
@ -9,17 +9,17 @@ license = "GPL-3.0-or-later"
workspace = true workspace = true
[lib] [lib]
path = "src/diff.rs" path = "src/buffer_diff.rs"
[features] [features]
test-support = [] test-support = []
[dependencies] [dependencies]
anyhow.workspace = true
futures.workspace = true futures.workspace = true
git2.workspace = true git2.workspace = true
gpui.workspace = true gpui.workspace = true
language.workspace = true language.workspace = true
log.workspace = true
rope.workspace = true rope.workspace = true
sum_tree.workspace = true sum_tree.workspace = true
text.workspace = true text.workspace = true
@ -29,4 +29,5 @@ util.workspace = true
pretty_assertions.workspace = true pretty_assertions.workspace = true
serde_json.workspace = true serde_json.workspace = true
text = { workspace = true, features = ["test-support"] } text = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }
unindent.workspace = true unindent.workspace = true

View file

@ -33,7 +33,7 @@ clock.workspace = true
collections.workspace = true collections.workspace = true
dashmap.workspace = true dashmap.workspace = true
derive_more.workspace = true derive_more.workspace = true
diff.workspace = true buffer_diff.workspace = true
envy = "0.4.2" envy = "0.4.2"
futures.workspace = true futures.workspace = true
google_ai.workspace = true google_ai.workspace = true

View file

@ -8,6 +8,7 @@ use crate::{
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use assistant_context_editor::ContextStore; use assistant_context_editor::ContextStore;
use assistant_slash_command::SlashCommandWorkingSet; use assistant_slash_command::SlashCommandWorkingSet;
use buffer_diff::{assert_hunks, DiffHunkSecondaryStatus, DiffHunkStatus};
use call::{room, ActiveCall, ParticipantLocation, Room}; use call::{room, ActiveCall, ParticipantLocation, Room};
use client::{User, RECEIVE_TIMEOUT}; use client::{User, RECEIVE_TIMEOUT};
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
@ -2613,11 +2614,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(), diff.base_text_string().as_deref(),
Some(staged_text.as_str()) Some(staged_text.as_str())
); );
diff::assert_hunks( assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&diff.base_text_string().unwrap(), &diff.base_text_string().unwrap(),
&[(1..2, "", "two\n")], &[(1..2, "", "two\n", DiffHunkStatus::added())],
); );
}); });
@ -2641,11 +2642,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(), diff.base_text_string().as_deref(),
Some(staged_text.as_str()) Some(staged_text.as_str())
); );
diff::assert_hunks( assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&diff.base_text_string().unwrap(), &diff.base_text_string().unwrap(),
&[(1..2, "", "two\n")], &[(1..2, "", "two\n", DiffHunkStatus::added())],
); );
}); });
@ -2663,11 +2664,16 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(), diff.base_text_string().as_deref(),
Some(committed_text.as_str()) Some(committed_text.as_str())
); );
diff::assert_hunks( assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&diff.base_text_string().unwrap(), &diff.base_text_string().unwrap(),
&[(1..2, "TWO\n", "two\n")], &[(
1..2,
"TWO\n",
"two\n",
DiffHunkStatus::Modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
)],
); );
}); });
@ -2689,11 +2695,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(), diff.base_text_string().as_deref(),
Some(new_staged_text.as_str()) Some(new_staged_text.as_str())
); );
diff::assert_hunks( assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&diff.base_text_string().unwrap(), &diff.base_text_string().unwrap(),
&[(2..3, "", "three\n")], &[(2..3, "", "three\n", DiffHunkStatus::added())],
); );
}); });
@ -2703,11 +2709,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(), diff.base_text_string().as_deref(),
Some(new_staged_text.as_str()) Some(new_staged_text.as_str())
); );
diff::assert_hunks( assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&diff.base_text_string().unwrap(), &diff.base_text_string().unwrap(),
&[(2..3, "", "three\n")], &[(2..3, "", "three\n", DiffHunkStatus::added())],
); );
}); });
@ -2717,11 +2723,16 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(), diff.base_text_string().as_deref(),
Some(new_committed_text.as_str()) Some(new_committed_text.as_str())
); );
diff::assert_hunks( assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&diff.base_text_string().unwrap(), &diff.base_text_string().unwrap(),
&[(1..2, "TWO_HUNDRED\n", "two\n")], &[(
1..2,
"TWO_HUNDRED\n",
"two\n",
DiffHunkStatus::Modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
)],
); );
}); });
@ -2763,11 +2774,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(), diff.base_text_string().as_deref(),
Some(staged_text.as_str()) Some(staged_text.as_str())
); );
diff::assert_hunks( assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&diff.base_text_string().unwrap(), &diff.base_text_string().unwrap(),
&[(1..2, "", "two\n")], &[(1..2, "", "two\n", DiffHunkStatus::added())],
); );
}); });
@ -2790,11 +2801,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(), diff.base_text_string().as_deref(),
Some(staged_text.as_str()) Some(staged_text.as_str())
); );
diff::assert_hunks( assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&staged_text, &staged_text,
&[(1..2, "", "two\n")], &[(1..2, "", "two\n", DiffHunkStatus::added())],
); );
}); });
@ -2812,11 +2823,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(), diff.base_text_string().as_deref(),
Some(new_staged_text.as_str()) Some(new_staged_text.as_str())
); );
diff::assert_hunks( assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&new_staged_text, &new_staged_text,
&[(2..3, "", "three\n")], &[(2..3, "", "three\n", DiffHunkStatus::added())],
); );
}); });
@ -2826,11 +2837,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(), diff.base_text_string().as_deref(),
Some(new_staged_text.as_str()) Some(new_staged_text.as_str())
); );
diff::assert_hunks( assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer), diff.hunks_in_row_range(0..4, buffer, cx),
buffer, buffer,
&new_staged_text, &new_staged_text,
&[(2..3, "", "three\n")], &[(2..3, "", "three\n", DiffHunkStatus::added())],
); );
}); });
} }

View file

@ -38,7 +38,7 @@ clock.workspace = true
collections.workspace = true collections.workspace = true
convert_case.workspace = true convert_case.workspace = true
db.workspace = true db.workspace = true
diff.workspace = true buffer_diff.workspace = true
emojis.workspace = true emojis.workspace = true
file_icons.workspace = true file_icons.workspace = true
futures.workspace = true futures.workspace = true

View file

@ -73,17 +73,16 @@ use code_context_menus::{
AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu, AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
CompletionsMenu, ContextMenuOrigin, CompletionsMenu, ContextMenuOrigin,
}; };
use diff::DiffHunkStatus;
use git::blame::GitBlame; use git::blame::GitBlame;
use gpui::{ use gpui::{
div, impl_actions, linear_color_stop, linear_gradient, point, prelude::*, pulsating_between, div, impl_actions, linear_color_stop, linear_gradient, point, prelude::*, pulsating_between,
px, relative, size, Action, Animation, AnimationExt, AnyElement, App, AsyncWindowContext, px, relative, size, Action, Animation, AnimationExt, AnyElement, App, AsyncWindowContext,
AvailableSpace, Bounds, ClipboardEntry, ClipboardItem, Context, DispatchPhase, ElementId, AvailableSpace, Background, Bounds, ClipboardEntry, ClipboardItem, Context, DispatchPhase,
Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, ElementId, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent, Focusable,
FontWeight, Global, HighlightStyle, Hsla, InteractiveText, KeyContext, Modifiers, MouseButton, FontId, FontWeight, Global, HighlightStyle, Hsla, InteractiveText, KeyContext, Modifiers,
MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, SharedString, Size, Styled, MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, SharedString, Size,
StyledText, Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, Styled, StyledText, Subscription, Task, TextRun, TextStyle, TextStyleRefinement,
UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
}; };
use highlight_matching_bracket::refresh_matching_bracket_highlights; use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState}; use hover_popover::{hide_hover, HoverState};
@ -722,6 +721,7 @@ pub struct Editor {
show_git_blame_gutter: bool, show_git_blame_gutter: bool,
show_git_blame_inline: bool, show_git_blame_inline: bool,
show_git_blame_inline_delay_task: Option<Task<()>>, show_git_blame_inline_delay_task: Option<Task<()>>,
distinguish_unstaged_diff_hunks: bool,
git_blame_inline_enabled: bool, git_blame_inline_enabled: bool,
serialize_dirty_buffers: bool, serialize_dirty_buffers: bool,
show_selection_menu: Option<bool>, show_selection_menu: Option<bool>,
@ -1418,6 +1418,7 @@ impl Editor {
custom_context_menu: None, custom_context_menu: None,
show_git_blame_gutter: false, show_git_blame_gutter: false,
show_git_blame_inline: false, show_git_blame_inline: false,
distinguish_unstaged_diff_hunks: false,
show_selection_menu: None, show_selection_menu: None,
show_git_blame_inline_delay_task: None, show_git_blame_inline_delay_task: None,
git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(), git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
@ -6878,8 +6879,7 @@ impl Editor {
let buffer = buffer.read(cx); let buffer = buffer.read(cx);
let original_text = diff let original_text = diff
.read(cx) .read(cx)
.snapshot .base_text()
.base_text
.as_ref()? .as_ref()?
.as_rope() .as_rope()
.slice(hunk.diff_base_byte_range.clone()); .slice(hunk.diff_base_byte_range.clone());
@ -12290,6 +12290,10 @@ impl Editor {
}); });
} }
pub fn set_distinguish_unstaged_diff_hunks(&mut self) {
self.distinguish_unstaged_diff_hunks = true;
}
pub fn expand_all_diff_hunks( pub fn expand_all_diff_hunks(
&mut self, &mut self,
_: &ExpandAllHunkDiffs, _: &ExpandAllHunkDiffs,
@ -13332,14 +13336,14 @@ impl Editor {
&self, &self,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> BTreeMap<DisplayRow, Hsla> { ) -> BTreeMap<DisplayRow, Background> {
let snapshot = self.snapshot(window, cx); let snapshot = self.snapshot(window, cx);
let mut used_highlight_orders = HashMap::default(); let mut used_highlight_orders = HashMap::default();
self.highlighted_rows self.highlighted_rows
.iter() .iter()
.flat_map(|(_, highlighted_rows)| highlighted_rows.iter()) .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
.fold( .fold(
BTreeMap::<DisplayRow, Hsla>::new(), BTreeMap::<DisplayRow, Background>::new(),
|mut unique_rows, highlight| { |mut unique_rows, highlight| {
let start = highlight.range.start.to_display_point(&snapshot); let start = highlight.range.start.to_display_point(&snapshot);
let end = highlight.range.end.to_display_point(&snapshot); let end = highlight.range.end.to_display_point(&snapshot);
@ -13356,7 +13360,7 @@ impl Editor {
used_highlight_orders.entry(row).or_insert(highlight.index); used_highlight_orders.entry(row).or_insert(highlight.index);
if highlight.index >= *used_index { if highlight.index >= *used_index {
*used_index = highlight.index; *used_index = highlight.index;
unique_rows.insert(DisplayRow(row), highlight.color); unique_rows.insert(DisplayRow(row), highlight.color.into());
} }
} }
unique_rows unique_rows
@ -15518,7 +15522,7 @@ impl EditorSnapshot {
) { ) {
// Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
// when the caret is just above or just below the deleted hunk. // when the caret is just above or just below the deleted hunk.
let allow_adjacent = hunk.status() == DiffHunkStatus::Removed; let allow_adjacent = hunk.status().is_removed();
let related_to_selection = if allow_adjacent { let related_to_selection = if allow_adjacent {
hunk.row_range.overlaps(&query_rows) hunk.row_range.overlaps(&query_rows)
|| hunk.row_range.start == query_rows.end || hunk.row_range.start == query_rows.end

View file

@ -7,7 +7,7 @@ use crate::{
}, },
JoinLines, JoinLines,
}; };
use diff::{BufferDiff, DiffHunkStatus}; use buffer_diff::{BufferDiff, DiffHunkStatus};
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
@ -11989,7 +11989,7 @@ async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
struct Row9.2; struct Row9.2;
struct Row9.3; struct Row9.3;
struct Row10;"#}, struct Row10;"#},
vec![DiffHunkStatus::Added, DiffHunkStatus::Added], vec![DiffHunkStatus::added(), DiffHunkStatus::added()],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row1; struct Row1;
struct Row1.1; struct Row1.1;
@ -12027,7 +12027,7 @@ async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
struct Row8; struct Row8;
struct Row9; struct Row9;
struct Row10;"#}, struct Row10;"#},
vec![DiffHunkStatus::Added, DiffHunkStatus::Added], vec![DiffHunkStatus::added(), DiffHunkStatus::added()],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row1; struct Row1;
struct Row2; struct Row2;
@ -12074,11 +12074,11 @@ async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
«ˇ// something on bottom» «ˇ// something on bottom»
struct Row10;"#}, struct Row10;"#},
vec![ vec![
DiffHunkStatus::Added, DiffHunkStatus::added(),
DiffHunkStatus::Added, DiffHunkStatus::added(),
DiffHunkStatus::Added, DiffHunkStatus::added(),
DiffHunkStatus::Added, DiffHunkStatus::added(),
DiffHunkStatus::Added, DiffHunkStatus::added(),
], ],
indoc! {r#"struct Row; indoc! {r#"struct Row;
ˇstruct Row1; ˇstruct Row1;
@ -12126,7 +12126,7 @@ async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
struct Row99; struct Row99;
struct Row9; struct Row9;
struct Row10;"#}, struct Row10;"#},
vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified], vec![DiffHunkStatus::modified(), DiffHunkStatus::modified()],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row1; struct Row1;
struct Row33; struct Row33;
@ -12153,7 +12153,7 @@ async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
struct Row99; struct Row99;
struct Row9; struct Row9;
struct Row10;"#}, struct Row10;"#},
vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified], vec![DiffHunkStatus::modified(), DiffHunkStatus::modified()],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row1; struct Row1;
struct Row33; struct Row33;
@ -12182,12 +12182,12 @@ async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
struct Row9; struct Row9;
struct Row1011;ˇ"#}, struct Row1011;ˇ"#},
vec![ vec![
DiffHunkStatus::Modified, DiffHunkStatus::modified(),
DiffHunkStatus::Modified, DiffHunkStatus::modified(),
DiffHunkStatus::Modified, DiffHunkStatus::modified(),
DiffHunkStatus::Modified, DiffHunkStatus::modified(),
DiffHunkStatus::Modified, DiffHunkStatus::modified(),
DiffHunkStatus::Modified, DiffHunkStatus::modified(),
], ],
indoc! {r#"struct Row; indoc! {r#"struct Row;
ˇstruct Row1; ˇstruct Row1;
@ -12265,7 +12265,7 @@ struct Row10;"#};
ˇ ˇ
struct Row8; struct Row8;
struct Row10;"#}, struct Row10;"#},
vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed], vec![DiffHunkStatus::removed(), DiffHunkStatus::removed()],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row2; struct Row2;
@ -12288,7 +12288,7 @@ struct Row10;"#};
ˇ» ˇ»
struct Row8; struct Row8;
struct Row10;"#}, struct Row10;"#},
vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed], vec![DiffHunkStatus::removed(), DiffHunkStatus::removed()],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row2; struct Row2;
@ -12313,7 +12313,7 @@ struct Row10;"#};
struct Row8;ˇ struct Row8;ˇ
struct Row10;"#}, struct Row10;"#},
vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed], vec![DiffHunkStatus::removed(), DiffHunkStatus::removed()],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row1; struct Row1;
ˇstruct Row2; ˇstruct Row2;
@ -12338,9 +12338,9 @@ struct Row10;"#};
struct Row8;ˇ» struct Row8;ˇ»
struct Row10;"#}, struct Row10;"#},
vec![ vec![
DiffHunkStatus::Removed, DiffHunkStatus::removed(),
DiffHunkStatus::Removed, DiffHunkStatus::removed(),
DiffHunkStatus::Removed, DiffHunkStatus::removed(),
], ],
indoc! {r#"struct Row; indoc! {r#"struct Row;
struct Row1; struct Row1;

View file

@ -25,21 +25,21 @@ use crate::{
EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT, FILE_HEADER_HEIGHT, EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT, FILE_HEADER_HEIGHT,
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
}; };
use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus};
use client::ParticipantIndex; use client::ParticipantIndex;
use collections::{BTreeMap, HashMap, HashSet}; use collections::{BTreeMap, HashMap, HashSet};
use diff::DiffHunkStatus;
use file_icons::FileIcons; use file_icons::FileIcons;
use git::{blame::BlameEntry, Oid}; use git::{blame::BlameEntry, Oid};
use gpui::{ use gpui::{
anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad, anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, pattern_slash,
relative, size, svg, transparent_black, Action, AnyElement, App, AvailableSpace, Axis, Bounds, point, px, quad, relative, size, svg, transparent_black, Action, AnyElement, App,
ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase, AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners,
Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _,
Hsla, InteractiveElement, IntoElement, KeyBindingContextPredicate, Keystroke, Length, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, KeyBindingContextPredicate, Keystroke, Length, ModifiersChangedEvent, MouseButton,
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta,
StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
WeakEntity, Window, Subscription, TextRun, TextStyleRefinement, WeakEntity, Window,
}; };
use itertools::Itertools; use itertools::Itertools;
use language::{ use language::{
@ -85,7 +85,6 @@ enum DisplayDiffHunk {
Folded { Folded {
display_row: DisplayRow, display_row: DisplayRow,
}, },
Unfolded { Unfolded {
diff_base_byte_range: Range<usize>, diff_base_byte_range: Range<usize>,
display_row_range: Range<DisplayRow>, display_row_range: Range<DisplayRow>,
@ -2116,7 +2115,7 @@ impl EditorElement {
.get(&display_row) .get(&display_row)
.unwrap_or(&non_relative_number); .unwrap_or(&non_relative_number);
write!(&mut line_number, "{number}").unwrap(); write!(&mut line_number, "{number}").unwrap();
if row_info.diff_status == Some(DiffHunkStatus::Removed) { if matches!(row_info.diff_status, Some(DiffHunkStatus::Removed(_))) {
return None; return None;
} }
@ -4007,8 +4006,10 @@ impl EditorElement {
if row_infos[row_ix].diff_status.is_none() { if row_infos[row_ix].diff_status.is_none() {
continue; continue;
} }
if row_infos[row_ix].diff_status == Some(DiffHunkStatus::Added) if matches!(
&& *status != DiffHunkStatus::Added row_infos[row_ix].diff_status,
Some(DiffHunkStatus::Added(_))
) && !matches!(*status, DiffHunkStatus::Added(_))
{ {
continue; continue;
} }
@ -4191,26 +4192,26 @@ impl EditorElement {
window.paint_quad(fill(Bounds { origin, size }, color)); window.paint_quad(fill(Bounds { origin, size }, color));
}; };
let mut current_paint: Option<(Hsla, Range<DisplayRow>)> = None; let mut current_paint: Option<(gpui::Background, Range<DisplayRow>)> = None;
for (&new_row, &new_color) in &layout.highlighted_rows { for (&new_row, &new_background) in &layout.highlighted_rows {
match &mut current_paint { match &mut current_paint {
Some((current_color, current_range)) => { Some((current_background, current_range)) => {
let current_color = *current_color; let current_background = *current_background;
let new_range_started = current_color != new_color let new_range_started = current_background != new_background
|| current_range.end.next_row() != new_row; || current_range.end.next_row() != new_row;
if new_range_started { if new_range_started {
paint_highlight( paint_highlight(
current_range.start, current_range.start,
current_range.end, current_range.end,
current_color, current_background,
); );
current_paint = Some((new_color, new_row..new_row)); current_paint = Some((new_background, new_row..new_row));
continue; continue;
} else { } else {
current_range.end = current_range.end.next_row(); current_range.end = current_range.end.next_row();
} }
} }
None => current_paint = Some((new_color, new_row..new_row)), None => current_paint = Some((new_background, new_row..new_row)),
}; };
} }
if let Some((color, range)) = current_paint { if let Some((color, range)) = current_paint {
@ -4409,6 +4410,7 @@ impl EditorElement {
hunk_bounds, hunk_bounds,
cx.theme().status().modified, cx.theme().status().modified,
Corners::all(px(0.)), Corners::all(px(0.)),
&DiffHunkSecondaryStatus::None,
)) ))
} }
DisplayDiffHunk::Unfolded { DisplayDiffHunk::Unfolded {
@ -4416,22 +4418,29 @@ impl EditorElement {
display_row_range, display_row_range,
.. ..
} => hitbox.as_ref().map(|hunk_hitbox| match status { } => hitbox.as_ref().map(|hunk_hitbox| match status {
DiffHunkStatus::Added => ( DiffHunkStatus::Added(secondary_status) => (
hunk_hitbox.bounds, hunk_hitbox.bounds,
cx.theme().status().created, cx.theme().status().created,
Corners::all(px(0.)), Corners::all(px(0.)),
secondary_status,
), ),
DiffHunkStatus::Modified => ( DiffHunkStatus::Modified(secondary_status) => (
hunk_hitbox.bounds, hunk_hitbox.bounds,
cx.theme().status().modified, cx.theme().status().modified,
Corners::all(px(0.)), Corners::all(px(0.)),
secondary_status,
), ),
DiffHunkStatus::Removed if !display_row_range.is_empty() => ( DiffHunkStatus::Removed(secondary_status)
hunk_hitbox.bounds, if !display_row_range.is_empty() =>
cx.theme().status().deleted, {
Corners::all(px(0.)), (
), hunk_hitbox.bounds,
DiffHunkStatus::Removed => ( cx.theme().status().deleted,
Corners::all(px(0.)),
secondary_status,
)
}
DiffHunkStatus::Removed(secondary_status) => (
Bounds::new( Bounds::new(
point( point(
hunk_hitbox.origin.x - hunk_hitbox.size.width, hunk_hitbox.origin.x - hunk_hitbox.size.width,
@ -4441,11 +4450,17 @@ impl EditorElement {
), ),
cx.theme().status().deleted, cx.theme().status().deleted,
Corners::all(1. * line_height), Corners::all(1. * line_height),
secondary_status,
), ),
}), }),
}; };
if let Some((hunk_bounds, background_color, corner_radii)) = hunk_to_paint { if let Some((hunk_bounds, mut background_color, corner_radii, secondary_status)) =
hunk_to_paint
{
if *secondary_status != DiffHunkSecondaryStatus::None {
background_color.a *= 0.6;
}
window.paint_quad(quad( window.paint_quad(quad(
hunk_bounds, hunk_bounds,
corner_radii, corner_radii,
@ -4481,7 +4496,7 @@ impl EditorElement {
status, status,
.. ..
} => { } => {
if *status == DiffHunkStatus::Removed && display_row_range.is_empty() { if status.is_removed() && display_row_range.is_empty() {
let row = display_row_range.start; let row = display_row_range.start;
let offset = line_height / 2.; let offset = line_height / 2.;
@ -5128,9 +5143,9 @@ impl EditorElement {
end_display_row.0 -= 1; end_display_row.0 -= 1;
} }
let color = match &hunk.status() { let color = match &hunk.status() {
DiffHunkStatus::Added => theme.status().created, DiffHunkStatus::Added(_) => theme.status().created,
DiffHunkStatus::Modified => theme.status().modified, DiffHunkStatus::Modified(_) => theme.status().modified,
DiffHunkStatus::Removed => theme.status().deleted, DiffHunkStatus::Removed(_) => theme.status().deleted,
}; };
ColoredRange { ColoredRange {
start: start_display_row, start: start_display_row,
@ -6798,19 +6813,46 @@ impl Element for EditorElement {
) )
}; };
let mut highlighted_rows = self let (mut highlighted_rows, distinguish_unstaged_hunks) =
.editor self.editor.update(cx, |editor, cx| {
.update(cx, |editor, cx| editor.highlighted_display_rows(window, cx)); (
editor.highlighted_display_rows(window, cx),
editor.distinguish_unstaged_diff_hunks,
)
});
for (ix, row_info) in row_infos.iter().enumerate() { for (ix, row_info) in row_infos.iter().enumerate() {
let color = match row_info.diff_status { let background = match row_info.diff_status {
Some(DiffHunkStatus::Added) => style.status.created_background, Some(DiffHunkStatus::Added(secondary_status)) => {
Some(DiffHunkStatus::Removed) => style.status.deleted_background, let color = style.status.created_background;
match secondary_status {
DiffHunkSecondaryStatus::HasSecondaryHunk
| DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
if distinguish_unstaged_hunks =>
{
pattern_slash(color, line_height.0 / 4.0)
}
_ => color.into(),
}
}
Some(DiffHunkStatus::Removed(secondary_status)) => {
let color = style.status.deleted_background;
match secondary_status {
DiffHunkSecondaryStatus::HasSecondaryHunk
| DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
if distinguish_unstaged_hunks =>
{
pattern_slash(color, line_height.0 / 4.0)
}
_ => color.into(),
}
}
_ => continue, _ => continue,
}; };
highlighted_rows highlighted_rows
.entry(start_row + DisplayRow(ix as u32)) .entry(start_row + DisplayRow(ix as u32))
.or_insert(color); .or_insert(background);
} }
let highlighted_ranges = self.editor.read(cx).background_highlights_in_range( let highlighted_ranges = self.editor.read(cx).background_highlights_in_range(
@ -7643,7 +7685,7 @@ pub struct EditorLayout {
indent_guides: Option<Vec<IndentGuideLayout>>, indent_guides: Option<Vec<IndentGuideLayout>>,
visible_display_row_range: Range<DisplayRow>, visible_display_row_range: Range<DisplayRow>,
active_rows: BTreeMap<DisplayRow, bool>, active_rows: BTreeMap<DisplayRow, bool>,
highlighted_rows: BTreeMap<DisplayRow, Hsla>, highlighted_rows: BTreeMap<DisplayRow, gpui::Background>,
line_elements: SmallVec<[AnyElement; 1]>, line_elements: SmallVec<[AnyElement; 1]>,
line_numbers: Arc<HashMap<MultiBufferRow, LineNumberLayout>>, line_numbers: Arc<HashMap<MultiBufferRow, LineNumberLayout>>,
display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>, display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,

View file

@ -1,6 +1,6 @@
use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SemanticsProvider}; use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SemanticsProvider};
use buffer_diff::BufferDiff;
use collections::HashSet; use collections::HashSet;
use diff::BufferDiff;
use futures::{channel::mpsc, future::join_all}; use futures::{channel::mpsc, future::join_all};
use gpui::{App, Entity, EventEmitter, Focusable, Render, Subscription, Task}; use gpui::{App, Entity, EventEmitter, Focusable, Render, Subscription, Task};
use language::{Buffer, BufferEvent, Capability}; use language::{Buffer, BufferEvent, Capability};
@ -185,7 +185,7 @@ impl ProposedChangesEditor {
} else { } else {
branch_buffer = location.buffer.update(cx, |buffer, cx| buffer.branch(cx)); branch_buffer = location.buffer.update(cx, |buffer, cx| buffer.branch(cx));
new_diffs.push(cx.new(|cx| { new_diffs.push(cx.new(|cx| {
let mut diff = BufferDiff::new(&branch_buffer, cx); let mut diff = BufferDiff::new(branch_buffer.read(cx));
let _ = diff.set_base_text( let _ = diff.set_base_text(
location.buffer.clone(), location.buffer.clone(),
branch_buffer.read(cx).text_snapshot(), branch_buffer.read(cx).text_snapshot(),

View file

@ -2,8 +2,8 @@ use crate::{
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer, display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
RowExt, RowExt,
}; };
use buffer_diff::DiffHunkStatus;
use collections::BTreeMap; use collections::BTreeMap;
use diff::DiffHunkStatus;
use futures::Future; use futures::Future;
use gpui::{ use gpui::{
@ -459,9 +459,9 @@ pub fn assert_state_with_diff(
.zip(line_infos) .zip(line_infos)
.map(|(line, info)| { .map(|(line, info)| {
let mut marker = match info.diff_status { let mut marker = match info.diff_status {
Some(DiffHunkStatus::Added) => "+ ", Some(DiffHunkStatus::Added(_)) => "+ ",
Some(DiffHunkStatus::Removed) => "- ", Some(DiffHunkStatus::Removed(_)) => "- ",
Some(DiffHunkStatus::Modified) => unreachable!(), Some(DiffHunkStatus::Modified(_)) => unreachable!(),
None => { None => {
if has_diff { if has_diff {
" " " "

View file

@ -16,7 +16,7 @@ path = "src/git_ui.rs"
anyhow.workspace = true anyhow.workspace = true
collections.workspace = true collections.workspace = true
db.workspace = true db.workspace = true
diff.workspace = true buffer_diff.workspace = true
editor.workspace = true editor.workspace = true
feature_flags.workspace = true feature_flags.workspace = true
futures.workspace = true futures.workspace = true

View file

@ -1,8 +1,8 @@
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use anyhow::Result; use anyhow::Result;
use buffer_diff::BufferDiff;
use collections::HashSet; use collections::HashSet;
use diff::BufferDiff;
use editor::{scroll::Autoscroll, Editor, EditorEvent}; use editor::{scroll::Autoscroll, Editor, EditorEvent};
use feature_flags::FeatureFlagViewExt; use feature_flags::FeatureFlagViewExt;
use futures::StreamExt; use futures::StreamExt;
@ -126,6 +126,7 @@ impl ProjectDiff {
window, window,
cx, cx,
); );
diff_display_editor.set_distinguish_unstaged_diff_hunks();
diff_display_editor.set_expand_all_diff_hunks(cx); diff_display_editor.set_expand_all_diff_hunks(cx);
diff_display_editor.register_addon(GitPanelAddon { diff_display_editor.register_addon(GitPanelAddon {
git_panel: git_panel.clone(), git_panel: git_panel.clone(),
@ -317,10 +318,10 @@ impl ProjectDiff {
let snapshot = buffer.read(cx).snapshot(); let snapshot = buffer.read(cx).snapshot();
let diff = diff.read(cx); let diff = diff.read(cx);
let diff_hunk_ranges = if diff.snapshot.base_text.is_none() { let diff_hunk_ranges = if diff.base_text().is_none() {
vec![Point::zero()..snapshot.max_point()] vec![Point::zero()..snapshot.max_point()]
} else { } else {
diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot) diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx)
.map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot)) .map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot))
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };

View file

@ -25,10 +25,30 @@ impl Render for PatternExample {
.flex_col() .flex_col()
.border_1() .border_1()
.border_color(gpui::blue()) .border_color(gpui::blue())
.child(div().w(px(54.0)).h(px(18.0)).bg(pattern_slash(gpui::red()))) .child(
.child(div().w(px(54.0)).h(px(18.0)).bg(pattern_slash(gpui::red()))) div()
.child(div().w(px(54.0)).h(px(18.0)).bg(pattern_slash(gpui::red()))) .w(px(54.0))
.child(div().w(px(54.0)).h(px(18.0)).bg(pattern_slash(gpui::red()))), .h(px(18.0))
.bg(pattern_slash(gpui::red(), 18.0 / 2.0)),
)
.child(
div()
.w(px(54.0))
.h(px(18.0))
.bg(pattern_slash(gpui::red(), 18.0 / 2.0)),
)
.child(
div()
.w(px(54.0))
.h(px(18.0))
.bg(pattern_slash(gpui::red(), 18.0 / 2.0)),
)
.child(
div()
.w(px(54.0))
.h(px(18.0))
.bg(pattern_slash(gpui::red(), 18.0 / 2.0)),
),
) )
.child( .child(
div() div()
@ -42,25 +62,25 @@ impl Render for PatternExample {
div() div()
.w(px(256.0)) .w(px(256.0))
.h(px(56.0)) .h(px(56.0))
.bg(pattern_slash(gpui::red())), .bg(pattern_slash(gpui::red(), 56.0 / 3.0)),
) )
.child( .child(
div() div()
.w(px(256.0)) .w(px(256.0))
.h(px(56.0)) .h(px(56.0))
.bg(pattern_slash(gpui::green())), .bg(pattern_slash(gpui::green(), 56.0 / 3.0)),
) )
.child( .child(
div() div()
.w(px(256.0)) .w(px(256.0))
.h(px(56.0)) .h(px(56.0))
.bg(pattern_slash(gpui::blue())), .bg(pattern_slash(gpui::blue(), 56.0 / 3.0)),
) )
.child( .child(
div() div()
.w(px(256.0)) .w(px(256.0))
.h(px(26.0)) .h(px(26.0))
.bg(pattern_slash(gpui::yellow())), .bg(pattern_slash(gpui::yellow(), 56.0 / 3.0)),
), ),
) )
.child( .child(

View file

@ -587,7 +587,7 @@ pub struct Background {
pub(crate) tag: BackgroundTag, pub(crate) tag: BackgroundTag,
pub(crate) color_space: ColorSpace, pub(crate) color_space: ColorSpace,
pub(crate) solid: Hsla, pub(crate) solid: Hsla,
pub(crate) angle: f32, pub(crate) gradient_angle_or_pattern_height: f32,
pub(crate) colors: [LinearColorStop; 2], pub(crate) colors: [LinearColorStop; 2],
/// Padding for alignment for repr(C) layout. /// Padding for alignment for repr(C) layout.
pad: u32, pad: u32,
@ -600,7 +600,7 @@ impl Default for Background {
tag: BackgroundTag::Solid, tag: BackgroundTag::Solid,
solid: Hsla::default(), solid: Hsla::default(),
color_space: ColorSpace::default(), color_space: ColorSpace::default(),
angle: 0.0, gradient_angle_or_pattern_height: 0.0,
colors: [LinearColorStop::default(), LinearColorStop::default()], colors: [LinearColorStop::default(), LinearColorStop::default()],
pad: 0, pad: 0,
} }
@ -608,10 +608,11 @@ impl Default for Background {
} }
/// Creates a hash pattern background /// Creates a hash pattern background
pub fn pattern_slash(color: Hsla) -> Background { pub fn pattern_slash(color: Hsla, thickness: f32) -> Background {
Background { Background {
tag: BackgroundTag::PatternSlash, tag: BackgroundTag::PatternSlash,
solid: color, solid: color,
gradient_angle_or_pattern_height: thickness,
..Default::default() ..Default::default()
} }
} }
@ -630,7 +631,7 @@ pub fn linear_gradient(
) -> Background { ) -> Background {
Background { Background {
tag: BackgroundTag::LinearGradient, tag: BackgroundTag::LinearGradient,
angle, gradient_angle_or_pattern_height: angle,
colors: [from.into(), to.into()], colors: [from.into(), to.into()],
..Default::default() ..Default::default()
} }

View file

@ -51,7 +51,7 @@ struct Background {
// 1u is Oklab color // 1u is Oklab color
color_space: u32, color_space: u32,
solid: Hsla, solid: Hsla,
angle: f32, gradient_angle_or_pattern_height: f32,
colors: array<LinearColorStop, 2>, colors: array<LinearColorStop, 2>,
pad: u32, pad: u32,
} }
@ -310,17 +310,18 @@ fn prepare_gradient_color(tag: u32, color_space: u32,
} }
fn gradient_color(background: Background, position: vec2<f32>, bounds: Bounds, fn gradient_color(background: Background, position: vec2<f32>, bounds: Bounds,
sold_color: vec4<f32>, color0: vec4<f32>, color1: vec4<f32>) -> vec4<f32> { solid_color: vec4<f32>, color0: vec4<f32>, color1: vec4<f32>) -> vec4<f32> {
var background_color = vec4<f32>(0.0); var background_color = vec4<f32>(0.0);
switch (background.tag) { switch (background.tag) {
default: { default: {
return sold_color; return solid_color;
} }
case 1u: { case 1u: {
// Linear gradient background. // Linear gradient background.
// -90 degrees to match the CSS gradient angle. // -90 degrees to match the CSS gradient angle.
let radians = (background.angle % 360.0 - 90.0) * M_PI_F / 180.0; let angle = background.gradient_angle_or_pattern_height;
let radians = (angle % 360.0 - 90.0) * M_PI_F / 180.0;
var direction = vec2<f32>(cos(radians), sin(radians)); var direction = vec2<f32>(cos(radians), sin(radians));
let stop0_percentage = background.colors[0].percentage; let stop0_percentage = background.colors[0].percentage;
let stop1_percentage = background.colors[1].percentage; let stop1_percentage = background.colors[1].percentage;
@ -359,19 +360,18 @@ fn gradient_color(background: Background, position: vec2<f32>, bounds: Bounds,
} }
} }
case 2u: { case 2u: {
let base_pattern_size = bounds.size.y / 5.0; let pattern_height = background.gradient_angle_or_pattern_height;
let width = base_pattern_size * 0.5; let stripe_angle = M_PI_F / 4.0;
let slash_spacing = 0.89; let pattern_period = pattern_height * sin(stripe_angle);
let radians = M_PI_F / 4.0;
let rotation = mat2x2<f32>( let rotation = mat2x2<f32>(
cos(radians), -sin(radians), cos(stripe_angle), -sin(stripe_angle),
sin(radians), cos(radians) sin(stripe_angle), cos(stripe_angle)
); );
let relative_position = position - bounds.origin; let relative_position = position - bounds.origin;
let rotated_point = rotation * relative_position; let rotated_point = rotation * relative_position;
let pattern = (rotated_point.x / slash_spacing) % (base_pattern_size * 2.0); let pattern = rotated_point.x % pattern_period;
let distance = min(pattern, base_pattern_size * 2.0 - pattern) - width; let distance = min(pattern, pattern_period - pattern) - pattern_period / 4;
background_color = sold_color; background_color = solid_color;
background_color.a *= saturate(0.5 - distance); background_color.a *= saturate(0.5 - distance);
} }
} }

View file

@ -833,7 +833,8 @@ float4 fill_color(Background background,
break; break;
case 1: { case 1: {
// -90 degrees to match the CSS gradient angle. // -90 degrees to match the CSS gradient angle.
float radians = (fmod(background.angle, 360.0) - 90.0) * (M_PI_F / 180.0); float gradient_angle = background.gradient_angle_or_pattern_height;
float radians = (fmod(gradient_angle, 360.0) - 90.0) * (M_PI_F / 180.0);
float2 direction = float2(cos(radians), sin(radians)); float2 direction = float2(cos(radians), sin(radians));
// Expand the short side to be the same as the long side // Expand the short side to be the same as the long side
@ -874,19 +875,14 @@ float4 fill_color(Background background,
break; break;
} }
case 2: { case 2: {
// This pattern is full of magic numbers to make it line up perfectly float pattern_height = background.gradient_angle_or_pattern_height;
// when vertically stacked. Make sure you know what you are doing float stripe_angle = M_PI_F / 4.0;
// if you change this! float pattern_period = pattern_height * sin(stripe_angle);
float2x2 rotation = rotate2d(stripe_angle);
float base_pattern_size = bounds.size.height / 5;
float width = base_pattern_size * 0.5;
float slash_spacing = .89;
float radians = M_PI_F / 4.0;
float2x2 rotation = rotate2d(radians);
float2 relative_position = position - float2(bounds.origin.x, bounds.origin.y); float2 relative_position = position - float2(bounds.origin.x, bounds.origin.y);
float2 rotated_point = rotation * relative_position; float2 rotated_point = rotation * relative_position;
float pattern = fmod(rotated_point.x / slash_spacing, base_pattern_size * 2.0); float pattern = fmod(rotated_point.x, pattern_period);
float distance = min(pattern, base_pattern_size * 2.0 - pattern) - width; float distance = min(pattern, pattern_period - pattern) - pattern_period / 4.0;
color = solid_color; color = solid_color;
color.a *= saturate(0.5 - distance); color.a *= saturate(0.5 - distance);
break; break;

View file

@ -14,7 +14,7 @@ doctest = false
[features] [features]
test-support = [ test-support = [
"diff/test-support", "buffer_diff/test-support",
"gpui/test-support", "gpui/test-support",
"language/test-support", "language/test-support",
"text/test-support", "text/test-support",
@ -26,7 +26,7 @@ anyhow.workspace = true
clock.workspace = true clock.workspace = true
collections.workspace = true collections.workspace = true
ctor.workspace = true ctor.workspace = true
diff.workspace = true buffer_diff.workspace = true
env_logger.workspace = true env_logger.workspace = true
futures.workspace = true futures.workspace = true
gpui.workspace = true gpui.workspace = true
@ -47,7 +47,7 @@ tree-sitter.workspace = true
util.workspace = true util.workspace = true
[dev-dependencies] [dev-dependencies]
diff = { workspace = true, features = ["test-support"] } buffer_diff = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] }
indoc.workspace = true indoc.workspace = true
language = { workspace = true, features = ["test-support"] } language = { workspace = true, features = ["test-support"] }

View file

@ -73,7 +73,7 @@ impl Anchor {
if let Some(base_text) = snapshot if let Some(base_text) = snapshot
.diffs .diffs
.get(&excerpt.buffer_id) .get(&excerpt.buffer_id)
.and_then(|diff| diff.base_text.as_ref()) .and_then(|diff| diff.base_text())
{ {
let self_anchor = self.diff_base_anchor.filter(|a| base_text.can_resolve(a)); let self_anchor = self.diff_base_anchor.filter(|a| base_text.can_resolve(a));
let other_anchor = other.diff_base_anchor.filter(|a| base_text.can_resolve(a)); let other_anchor = other.diff_base_anchor.filter(|a| base_text.can_resolve(a));
@ -110,7 +110,7 @@ impl Anchor {
if let Some(base_text) = snapshot if let Some(base_text) = snapshot
.diffs .diffs
.get(&excerpt.buffer_id) .get(&excerpt.buffer_id)
.and_then(|diff| diff.base_text.as_ref()) .and_then(|diff| diff.base_text())
{ {
if a.buffer_id == Some(base_text.remote_id()) { if a.buffer_id == Some(base_text.remote_id()) {
return a.bias_left(base_text); return a.bias_left(base_text);
@ -135,7 +135,7 @@ impl Anchor {
if let Some(base_text) = snapshot if let Some(base_text) = snapshot
.diffs .diffs
.get(&excerpt.buffer_id) .get(&excerpt.buffer_id)
.and_then(|diff| diff.base_text.as_ref()) .and_then(|diff| diff.base_text())
{ {
if a.buffer_id == Some(base_text.remote_id()) { if a.buffer_id == Some(base_text.remote_id()) {
return a.bias_right(&base_text); return a.bias_right(&base_text);

View file

@ -7,9 +7,11 @@ pub use anchor::{Anchor, AnchorRangeExt, Offset};
pub use position::{TypedOffset, TypedPoint, TypedRow}; pub use position::{TypedOffset, TypedPoint, TypedRow};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use buffer_diff::{
BufferDiff, BufferDiffEvent, BufferDiffSnapshot, DiffHunkSecondaryStatus, DiffHunkStatus,
};
use clock::ReplicaId; use clock::ReplicaId;
use collections::{BTreeMap, Bound, HashMap, HashSet}; use collections::{BTreeMap, Bound, HashMap, HashSet};
use diff::{BufferDiff, BufferDiffEvent, BufferDiffSnapshot, DiffHunkStatus};
use futures::{channel::mpsc, SinkExt}; use futures::{channel::mpsc, SinkExt};
use gpui::{App, Context, Entity, EntityId, EventEmitter, Task}; use gpui::{App, Context, Entity, EntityId, EventEmitter, Task};
use itertools::Itertools; use itertools::Itertools;
@ -129,16 +131,18 @@ pub struct MultiBufferDiffHunk {
pub excerpt_id: ExcerptId, pub excerpt_id: ExcerptId,
/// The range within the buffer's diff base that this hunk corresponds to. /// The range within the buffer's diff base that this hunk corresponds to.
pub diff_base_byte_range: Range<usize>, pub diff_base_byte_range: Range<usize>,
/// Whether or not this hunk also appears in the 'secondary diff'.
pub secondary_status: DiffHunkSecondaryStatus,
} }
impl MultiBufferDiffHunk { impl MultiBufferDiffHunk {
pub fn status(&self) -> DiffHunkStatus { pub fn status(&self) -> DiffHunkStatus {
if self.buffer_range.start == self.buffer_range.end { if self.buffer_range.start == self.buffer_range.end {
DiffHunkStatus::Removed DiffHunkStatus::Removed(self.secondary_status)
} else if self.diff_base_byte_range.is_empty() { } else if self.diff_base_byte_range.is_empty() {
DiffHunkStatus::Added DiffHunkStatus::Added(self.secondary_status)
} else { } else {
DiffHunkStatus::Modified DiffHunkStatus::Modified(self.secondary_status)
} }
} }
} }
@ -225,7 +229,14 @@ impl DiffState {
DiffState { DiffState {
_subscription: cx.subscribe(&diff, |this, diff, event, cx| match event { _subscription: cx.subscribe(&diff, |this, diff, event, cx| match event {
BufferDiffEvent::DiffChanged { changed_range } => { BufferDiffEvent::DiffChanged { changed_range } => {
this.buffer_diff_changed(diff, changed_range.clone(), cx) let changed_range = if let Some(changed_range) = changed_range {
changed_range.clone()
} else if diff.read(cx).base_text().is_none() && this.all_diff_hunks_expanded {
text::Anchor::MIN..text::Anchor::MAX
} else {
return;
};
this.buffer_diff_changed(diff, changed_range, cx)
} }
BufferDiffEvent::LanguageChanged => this.buffer_diff_language_changed(diff, cx), BufferDiffEvent::LanguageChanged => this.buffer_diff_language_changed(diff, cx),
}), }),
@ -241,7 +252,7 @@ pub struct MultiBufferSnapshot {
excerpts: SumTree<Excerpt>, excerpts: SumTree<Excerpt>,
excerpt_ids: SumTree<ExcerptIdMapping>, excerpt_ids: SumTree<ExcerptIdMapping>,
diffs: TreeMap<BufferId, BufferDiffSnapshot>, diffs: TreeMap<BufferId, BufferDiffSnapshot>,
pub diff_transforms: SumTree<DiffTransform>, diff_transforms: SumTree<DiffTransform>,
trailing_excerpt_update_count: usize, trailing_excerpt_update_count: usize,
non_text_state_update_count: usize, non_text_state_update_count: usize,
edit_count: usize, edit_count: usize,
@ -252,20 +263,27 @@ pub struct MultiBufferSnapshot {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum DiffTransform { enum DiffTransform {
BufferContent { BufferContent {
summary: TextSummary, summary: TextSummary,
inserted_hunk_anchor: Option<(ExcerptId, text::Anchor)>, inserted_hunk_info: Option<DiffTransformHunkInfo>,
}, },
DeletedHunk { DeletedHunk {
summary: TextSummary, summary: TextSummary,
buffer_id: BufferId, buffer_id: BufferId,
hunk_anchor: (ExcerptId, text::Anchor), hunk_info: DiffTransformHunkInfo,
base_text_byte_range: Range<usize>, base_text_byte_range: Range<usize>,
has_trailing_newline: bool, has_trailing_newline: bool,
}, },
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
struct DiffTransformHunkInfo {
excerpt_id: ExcerptId,
hunk_start_anchor: text::Anchor,
hunk_secondary_status: DiffHunkSecondaryStatus,
}
#[derive(Clone)] #[derive(Clone)]
pub struct ExcerptInfo { pub struct ExcerptInfo {
pub id: ExcerptId, pub id: ExcerptId,
@ -310,7 +328,7 @@ pub struct RowInfo {
pub buffer_id: Option<BufferId>, pub buffer_id: Option<BufferId>,
pub buffer_row: Option<u32>, pub buffer_row: Option<u32>,
pub multibuffer_row: Option<MultiBufferRow>, pub multibuffer_row: Option<MultiBufferRow>,
pub diff_status: Option<diff::DiffHunkStatus>, pub diff_status: Option<buffer_diff::DiffHunkStatus>,
} }
/// A slice into a [`Buffer`] that is being edited in a [`MultiBuffer`]. /// A slice into a [`Buffer`] that is being edited in a [`MultiBuffer`].
@ -431,7 +449,7 @@ struct MultiBufferCursor<'a, D: TextDimension> {
struct MultiBufferRegion<'a, D: TextDimension> { struct MultiBufferRegion<'a, D: TextDimension> {
buffer: &'a BufferSnapshot, buffer: &'a BufferSnapshot,
is_main_buffer: bool, is_main_buffer: bool,
is_inserted_hunk: bool, diff_hunk_status: Option<DiffHunkStatus>,
excerpt: &'a Excerpt, excerpt: &'a Excerpt,
buffer_range: Range<D>, buffer_range: Range<D>,
range: Range<D>, range: Range<D>,
@ -2146,7 +2164,7 @@ impl MultiBuffer {
let mut snapshot = self.snapshot.borrow_mut(); let mut snapshot = self.snapshot.borrow_mut();
let diff = diff.read(cx); let diff = diff.read(cx);
let buffer_id = diff.buffer_id; let buffer_id = diff.buffer_id;
let diff = diff.snapshot.clone(); let diff = diff.snapshot(cx);
snapshot.diffs.insert(buffer_id, diff); snapshot.diffs.insert(buffer_id, diff);
} }
@ -2160,36 +2178,29 @@ impl MultiBuffer {
let diff = diff.read(cx); let diff = diff.read(cx);
let buffer_id = diff.buffer_id; let buffer_id = diff.buffer_id;
let mut diff = diff.snapshot.clone();
if diff.base_text.is_none() && self.all_diff_hunks_expanded {
diff = BufferDiffSnapshot::new_with_single_insertion(cx);
}
let mut snapshot = self.snapshot.borrow_mut();
let base_text_changed =
snapshot
.diffs
.get(&buffer_id)
.map_or(true, |diff_snapshot| {
match (&diff_snapshot.base_text, &diff.base_text) {
(None, None) => false,
(None, Some(_)) => true,
(Some(_), None) => true,
(Some(old), Some(new)) => {
let (old_id, old_empty) = (old.remote_id(), old.is_empty());
let (new_id, new_empty) = (new.remote_id(), new.is_empty());
new_id != old_id && (!new_empty || !old_empty)
}
}
});
snapshot.diffs.insert(buffer_id, diff);
let buffers = self.buffers.borrow(); let buffers = self.buffers.borrow();
let Some(buffer_state) = buffers.get(&buffer_id) else { let Some(buffer_state) = buffers.get(&buffer_id) else {
return; return;
}; };
let diff_change_range = range.to_offset(buffer_state.buffer.read(cx)); let buffer = buffer_state.buffer.read(cx);
let diff_change_range = range.to_offset(buffer);
let mut new_diff = diff.snapshot(cx);
if new_diff.base_text().is_none() && self.all_diff_hunks_expanded {
let secondary_diff_insertion = new_diff
.secondary_diff()
.map_or(true, |secondary_diff| secondary_diff.base_text().is_none());
new_diff = BufferDiff::build_with_single_insertion(secondary_diff_insertion, cx);
}
let mut snapshot = self.snapshot.borrow_mut();
let base_text_changed = snapshot
.diffs
.get(&buffer_id)
.map_or(true, |old_diff| !new_diff.base_texts_eq(old_diff));
snapshot.diffs.insert(buffer_id, new_diff);
let mut excerpt_edits = Vec::new(); let mut excerpt_edits = Vec::new();
for locator in &buffer_state.excerpts { for locator in &buffer_state.excerpts {
@ -2367,7 +2378,7 @@ impl MultiBuffer {
if *cursor.start() >= end { if *cursor.start() >= end {
break; break;
} }
if item.hunk_anchor().is_some() { if item.hunk_info().is_some() {
return true; return true;
} }
cursor.next(&()); cursor.next(&());
@ -2820,11 +2831,11 @@ impl MultiBuffer {
let keep_next_old_transform = (old_diff_transforms.start().0 >= edit.old.end) let keep_next_old_transform = (old_diff_transforms.start().0 >= edit.old.end)
&& match old_diff_transforms.item() { && match old_diff_transforms.item() {
Some(DiffTransform::BufferContent { Some(DiffTransform::BufferContent {
inserted_hunk_anchor: Some(hunk_anchor), inserted_hunk_info: Some(hunk),
.. ..
}) => excerpts }) => excerpts.item().is_some_and(|excerpt| {
.item() hunk.hunk_start_anchor.is_valid(&excerpt.buffer)
.is_some_and(|excerpt| hunk_anchor.1.is_valid(&excerpt.buffer)), }),
_ => true, _ => true,
}; };
@ -2853,7 +2864,7 @@ impl MultiBuffer {
new_diff_transforms.push( new_diff_transforms.push(
DiffTransform::BufferContent { DiffTransform::BufferContent {
summary: Default::default(), summary: Default::default(),
inserted_hunk_anchor: None, inserted_hunk_info: None,
}, },
&(), &(),
); );
@ -2876,8 +2887,8 @@ impl MultiBuffer {
excerpts: &mut Cursor<Excerpt, TypedOffset<Excerpt>>, excerpts: &mut Cursor<Excerpt, TypedOffset<Excerpt>>,
old_diff_transforms: &mut Cursor<DiffTransform, (TypedOffset<Excerpt>, usize)>, old_diff_transforms: &mut Cursor<DiffTransform, (TypedOffset<Excerpt>, usize)>,
new_diff_transforms: &mut SumTree<DiffTransform>, new_diff_transforms: &mut SumTree<DiffTransform>,
end_of_current_insert: &mut Option<(TypedOffset<Excerpt>, ExcerptId, text::Anchor)>, end_of_current_insert: &mut Option<(TypedOffset<Excerpt>, DiffTransformHunkInfo)>,
old_expanded_hunks: &mut HashSet<(ExcerptId, text::Anchor)>, old_expanded_hunks: &mut HashSet<DiffTransformHunkInfo>,
snapshot: &MultiBufferSnapshot, snapshot: &MultiBufferSnapshot,
change_kind: DiffChangeKind, change_kind: DiffChangeKind,
) -> bool { ) -> bool {
@ -2889,12 +2900,12 @@ impl MultiBuffer {
// Record which hunks were previously expanded. // Record which hunks were previously expanded.
while let Some(item) = old_diff_transforms.item() { while let Some(item) = old_diff_transforms.item() {
if let Some(hunk_anchor) = item.hunk_anchor() { if let Some(hunk_info) = item.hunk_info() {
log::trace!( log::trace!(
"previously expanded hunk at {}", "previously expanded hunk at {}",
old_diff_transforms.start().0 old_diff_transforms.start().0
); );
old_expanded_hunks.insert(hunk_anchor); old_expanded_hunks.insert(hunk_info);
} }
if old_diff_transforms.end(&()).0 > edit.old.end { if old_diff_transforms.end(&()).0 > edit.old.end {
break; break;
@ -2918,7 +2929,7 @@ impl MultiBuffer {
if let Some((diff, base_text)) = snapshot if let Some((diff, base_text)) = snapshot
.diffs .diffs
.get(&excerpt.buffer_id) .get(&excerpt.buffer_id)
.and_then(|diff| Some((diff, diff.base_text.as_ref()?))) .and_then(|diff| Some((diff, diff.base_text()?)))
{ {
let buffer = &excerpt.buffer; let buffer = &excerpt.buffer;
let excerpt_start = *excerpts.start(); let excerpt_start = *excerpts.start();
@ -2936,7 +2947,11 @@ impl MultiBuffer {
for hunk in diff.hunks_intersecting_range(edit_anchor_range, buffer) { for hunk in diff.hunks_intersecting_range(edit_anchor_range, buffer) {
let hunk_buffer_range = hunk.buffer_range.to_offset(buffer); let hunk_buffer_range = hunk.buffer_range.to_offset(buffer);
let hunk_anchor = (excerpt.id, hunk.buffer_range.start); let hunk_info = DiffTransformHunkInfo {
excerpt_id: excerpt.id,
hunk_start_anchor: hunk.buffer_range.start,
hunk_secondary_status: hunk.secondary_status,
};
if hunk_buffer_range.start < excerpt_buffer_start { if hunk_buffer_range.start < excerpt_buffer_start {
log::trace!("skipping hunk that starts before excerpt"); log::trace!("skipping hunk that starts before excerpt");
continue; continue;
@ -2960,7 +2975,7 @@ impl MultiBuffer {
// For every existing hunk, determine if it was previously expanded // For every existing hunk, determine if it was previously expanded
// and if it should currently be expanded. // and if it should currently be expanded.
let was_previously_expanded = old_expanded_hunks.contains(&hunk_anchor); let was_previously_expanded = old_expanded_hunks.contains(&hunk_info);
let should_expand_hunk = match &change_kind { let should_expand_hunk = match &change_kind {
DiffChangeKind::DiffUpdated { base_changed: true } => { DiffChangeKind::DiffUpdated { base_changed: true } => {
self.all_diff_hunks_expanded self.all_diff_hunks_expanded
@ -3008,7 +3023,7 @@ impl MultiBuffer {
base_text_byte_range: hunk.diff_base_byte_range.clone(), base_text_byte_range: hunk.diff_base_byte_range.clone(),
summary: base_text_summary, summary: base_text_summary,
buffer_id: excerpt.buffer_id, buffer_id: excerpt.buffer_id,
hunk_anchor, hunk_info,
has_trailing_newline, has_trailing_newline,
}, },
&(), &(),
@ -3016,11 +3031,8 @@ impl MultiBuffer {
} }
if !hunk_buffer_range.is_empty() { if !hunk_buffer_range.is_empty() {
*end_of_current_insert = Some(( *end_of_current_insert =
hunk_excerpt_end.min(excerpt_end), Some((hunk_excerpt_end.min(excerpt_end), hunk_info));
hunk_anchor.0,
hunk_anchor.1,
));
} }
} }
} }
@ -3042,13 +3054,13 @@ impl MultiBuffer {
subtree: SumTree<DiffTransform>, subtree: SumTree<DiffTransform>,
) { ) {
if let Some(DiffTransform::BufferContent { if let Some(DiffTransform::BufferContent {
inserted_hunk_anchor, inserted_hunk_info,
summary, summary,
}) = subtree.first() }) = subtree.first()
{ {
if self.extend_last_buffer_content_transform( if self.extend_last_buffer_content_transform(
new_transforms, new_transforms,
*inserted_hunk_anchor, *inserted_hunk_info,
*summary, *summary,
) { ) {
let mut cursor = subtree.cursor::<()>(&()); let mut cursor = subtree.cursor::<()>(&());
@ -3067,7 +3079,7 @@ impl MultiBuffer {
transform: DiffTransform, transform: DiffTransform,
) { ) {
if let DiffTransform::BufferContent { if let DiffTransform::BufferContent {
inserted_hunk_anchor, inserted_hunk_info: inserted_hunk_anchor,
summary, summary,
} = transform } = transform
{ {
@ -3087,19 +3099,14 @@ impl MultiBuffer {
old_snapshot: &MultiBufferSnapshot, old_snapshot: &MultiBufferSnapshot,
new_transforms: &mut SumTree<DiffTransform>, new_transforms: &mut SumTree<DiffTransform>,
end_offset: ExcerptOffset, end_offset: ExcerptOffset,
current_inserted_hunk: Option<(ExcerptOffset, ExcerptId, text::Anchor)>, current_inserted_hunk: Option<(ExcerptOffset, DiffTransformHunkInfo)>,
) { ) {
let inserted_region = let inserted_region = current_inserted_hunk.map(|(insertion_end_offset, hunk_info)| {
current_inserted_hunk.map(|(insertion_end_offset, excerpt_id, anchor)| { (end_offset.min(insertion_end_offset), Some(hunk_info))
( });
end_offset.min(insertion_end_offset),
Some((excerpt_id, anchor)),
)
});
let unchanged_region = [(end_offset, None)]; let unchanged_region = [(end_offset, None)];
for (end_offset, inserted_hunk_anchor) in for (end_offset, inserted_hunk_info) in inserted_region.into_iter().chain(unchanged_region)
inserted_region.into_iter().chain(unchanged_region)
{ {
let start_offset = new_transforms.summary().excerpt_len(); let start_offset = new_transforms.summary().excerpt_len();
if end_offset <= start_offset { if end_offset <= start_offset {
@ -3110,13 +3117,13 @@ impl MultiBuffer {
if !self.extend_last_buffer_content_transform( if !self.extend_last_buffer_content_transform(
new_transforms, new_transforms,
inserted_hunk_anchor, inserted_hunk_info,
summary_to_add, summary_to_add,
) { ) {
new_transforms.push( new_transforms.push(
DiffTransform::BufferContent { DiffTransform::BufferContent {
summary: summary_to_add, summary: summary_to_add,
inserted_hunk_anchor, inserted_hunk_info,
}, },
&(), &(),
) )
@ -3127,7 +3134,7 @@ impl MultiBuffer {
fn extend_last_buffer_content_transform( fn extend_last_buffer_content_transform(
&self, &self,
new_transforms: &mut SumTree<DiffTransform>, new_transforms: &mut SumTree<DiffTransform>,
new_inserted_hunk_anchor: Option<(ExcerptId, text::Anchor)>, new_inserted_hunk_info: Option<DiffTransformHunkInfo>,
summary_to_add: TextSummary, summary_to_add: TextSummary,
) -> bool { ) -> bool {
let mut did_extend = false; let mut did_extend = false;
@ -3135,10 +3142,10 @@ impl MultiBuffer {
|last_transform| { |last_transform| {
if let DiffTransform::BufferContent { if let DiffTransform::BufferContent {
summary, summary,
inserted_hunk_anchor, inserted_hunk_info: inserted_hunk_anchor,
} = last_transform } = last_transform
{ {
if *inserted_hunk_anchor == new_inserted_hunk_anchor { if *inserted_hunk_anchor == new_inserted_hunk_info {
*summary += summary_to_add; *summary += summary_to_add;
did_extend = true; did_extend = true;
} }
@ -3469,6 +3476,7 @@ impl MultiBufferSnapshot {
excerpt_id: excerpt.id, excerpt_id: excerpt.id,
buffer_range: hunk.buffer_range.clone(), buffer_range: hunk.buffer_range.clone(),
diff_base_byte_range: hunk.diff_base_byte_range.clone(), diff_base_byte_range: hunk.diff_base_byte_range.clone(),
secondary_status: hunk.secondary_status,
}) })
}) })
} }
@ -3837,6 +3845,7 @@ impl MultiBufferSnapshot {
excerpt_id: excerpt.id, excerpt_id: excerpt.id,
buffer_range: hunk.buffer_range.clone(), buffer_range: hunk.buffer_range.clone(),
diff_base_byte_range: hunk.diff_base_byte_range.clone(), diff_base_byte_range: hunk.diff_base_byte_range.clone(),
secondary_status: hunk.secondary_status,
}); });
} }
} }
@ -4309,10 +4318,7 @@ impl MultiBufferSnapshot {
} => { } => {
let buffer_start = base_text_byte_range.start + start_overshoot; let buffer_start = base_text_byte_range.start + start_overshoot;
let mut buffer_end = base_text_byte_range.start + end_overshoot; let mut buffer_end = base_text_byte_range.start + end_overshoot;
let Some(base_text) = self let Some(base_text) = self.diffs.get(buffer_id).and_then(|diff| diff.base_text())
.diffs
.get(buffer_id)
.and_then(|diff| diff.base_text.as_ref())
else { else {
panic!("{:?} is in non-existent deleted hunk", range.start) panic!("{:?} is in non-existent deleted hunk", range.start)
}; };
@ -4361,10 +4367,7 @@ impl MultiBufferSnapshot {
.. ..
} => { } => {
let buffer_end = base_text_byte_range.start + overshoot; let buffer_end = base_text_byte_range.start + overshoot;
let Some(base_text) = self let Some(base_text) = self.diffs.get(buffer_id).and_then(|diff| diff.base_text())
.diffs
.get(buffer_id)
.and_then(|diff| diff.base_text.as_ref())
else { else {
panic!("{:?} is in non-existent deleted hunk", range.end) panic!("{:?} is in non-existent deleted hunk", range.end)
}; };
@ -4469,10 +4472,8 @@ impl MultiBufferSnapshot {
}) => { }) => {
let mut in_deleted_hunk = false; let mut in_deleted_hunk = false;
if let Some(diff_base_anchor) = &anchor.diff_base_anchor { if let Some(diff_base_anchor) = &anchor.diff_base_anchor {
if let Some(base_text) = self if let Some(base_text) =
.diffs self.diffs.get(buffer_id).and_then(|diff| diff.base_text())
.get(buffer_id)
.and_then(|diff| diff.base_text.as_ref())
{ {
if base_text.can_resolve(&diff_base_anchor) { if base_text.can_resolve(&diff_base_anchor) {
let base_text_offset = diff_base_anchor.to_offset(&base_text); let base_text_offset = diff_base_anchor.to_offset(&base_text);
@ -4809,7 +4810,7 @@ impl MultiBufferSnapshot {
let base_text = self let base_text = self
.diffs .diffs
.get(buffer_id) .get(buffer_id)
.and_then(|diff| diff.base_text.as_ref()) .and_then(|diff| diff.base_text())
.expect("missing diff base"); .expect("missing diff base");
if offset_in_transform > base_text_byte_range.len() { if offset_in_transform > base_text_byte_range.len() {
debug_assert!(*has_trailing_newline); debug_assert!(*has_trailing_newline);
@ -5969,17 +5970,17 @@ impl MultiBufferSnapshot {
for item in self.diff_transforms.iter() { for item in self.diff_transforms.iter() {
if let DiffTransform::BufferContent { if let DiffTransform::BufferContent {
summary, summary,
inserted_hunk_anchor, inserted_hunk_info,
} = item } = item
{ {
if let Some(DiffTransform::BufferContent { if let Some(DiffTransform::BufferContent {
inserted_hunk_anchor: prev_inserted_hunk_anchor, inserted_hunk_info: prev_inserted_hunk_info,
.. ..
}) = prev_transform }) = prev_transform
{ {
if *inserted_hunk_anchor == *prev_inserted_hunk_anchor { if *inserted_hunk_info == *prev_inserted_hunk_info {
panic!( panic!(
"multiple adjacent buffer content transforms with is_inserted_hunk = {inserted_hunk_anchor:?}. transforms: {:+?}", "multiple adjacent buffer content transforms with is_inserted_hunk = {inserted_hunk_info:?}. transforms: {:+?}",
self.diff_transforms.items(&())); self.diff_transforms.items(&()));
} }
} }
@ -6149,10 +6150,11 @@ where
buffer_id, buffer_id,
base_text_byte_range, base_text_byte_range,
has_trailing_newline, has_trailing_newline,
hunk_info,
.. ..
} => { } => {
let diff = self.diffs.get(&buffer_id)?; let diff = self.diffs.get(&buffer_id)?;
let buffer = diff.base_text.as_ref()?; let buffer = diff.base_text()?;
let mut rope_cursor = buffer.as_rope().cursor(0); let mut rope_cursor = buffer.as_rope().cursor(0);
let buffer_start = rope_cursor.summary::<D>(base_text_byte_range.start); let buffer_start = rope_cursor.summary::<D>(base_text_byte_range.start);
let buffer_range_len = rope_cursor.summary::<D>(base_text_byte_range.end); let buffer_range_len = rope_cursor.summary::<D>(base_text_byte_range.end);
@ -6165,14 +6167,15 @@ where
excerpt, excerpt,
has_trailing_newline: *has_trailing_newline, has_trailing_newline: *has_trailing_newline,
is_main_buffer: false, is_main_buffer: false,
is_inserted_hunk: false, diff_hunk_status: Some(DiffHunkStatus::Removed(
hunk_info.hunk_secondary_status,
)),
buffer_range: buffer_start..buffer_end, buffer_range: buffer_start..buffer_end,
range: start..end, range: start..end,
}); });
} }
DiffTransform::BufferContent { DiffTransform::BufferContent {
inserted_hunk_anchor, inserted_hunk_info, ..
..
} => { } => {
let buffer = &excerpt.buffer; let buffer = &excerpt.buffer;
let buffer_context_start = excerpt.range.context.start.summary::<D>(buffer); let buffer_context_start = excerpt.range.context.start.summary::<D>(buffer);
@ -6209,7 +6212,8 @@ where
excerpt, excerpt,
has_trailing_newline, has_trailing_newline,
is_main_buffer: true, is_main_buffer: true,
is_inserted_hunk: inserted_hunk_anchor.is_some(), diff_hunk_status: inserted_hunk_info
.map(|info| DiffHunkStatus::Added(info.hunk_secondary_status)),
buffer_range: buffer_start..buffer_end, buffer_range: buffer_start..buffer_end,
range: start..end, range: start..end,
}) })
@ -6717,13 +6721,12 @@ impl sum_tree::KeyedItem for ExcerptIdMapping {
} }
impl DiffTransform { impl DiffTransform {
fn hunk_anchor(&self) -> Option<(ExcerptId, text::Anchor)> { fn hunk_info(&self) -> Option<DiffTransformHunkInfo> {
match self { match self {
DiffTransform::DeletedHunk { hunk_anchor, .. } => Some(*hunk_anchor), DiffTransform::DeletedHunk { hunk_info, .. } => Some(*hunk_info),
DiffTransform::BufferContent { DiffTransform::BufferContent {
inserted_hunk_anchor, inserted_hunk_info, ..
.. } => *inserted_hunk_info,
} => *inserted_hunk_anchor,
} }
} }
} }
@ -7020,13 +7023,9 @@ impl<'a> Iterator for MultiBufferRows<'a> {
buffer_id: Some(region.buffer.remote_id()), buffer_id: Some(region.buffer.remote_id()),
buffer_row: Some(buffer_point.row), buffer_row: Some(buffer_point.row),
multibuffer_row: Some(MultiBufferRow(self.point.row)), multibuffer_row: Some(MultiBufferRow(self.point.row)),
diff_status: if region.is_inserted_hunk && self.point < region.range.end { diff_status: region
Some(DiffHunkStatus::Added) .diff_hunk_status
} else if !region.is_main_buffer { .filter(|_| self.point < region.range.end),
Some(DiffHunkStatus::Removed)
} else {
None
},
}); });
self.point += Point::new(1, 0); self.point += Point::new(1, 0);
result result
@ -7194,7 +7193,7 @@ impl<'a> Iterator for MultiBufferChunks<'a> {
} }
chunks chunks
} else { } else {
let base_buffer = &self.diffs.get(&buffer_id)?.base_text.as_ref()?; let base_buffer = &self.diffs.get(&buffer_id)?.base_text()?;
base_buffer.chunks(base_text_start..base_text_end, self.language_aware) base_buffer.chunks(base_text_start..base_text_end, self.language_aware)
}; };

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use diff::DiffHunkStatus; use buffer_diff::DiffHunkStatus;
use gpui::{App, TestAppContext}; use gpui::{App, TestAppContext};
use indoc::indoc; use indoc::indoc;
use language::{Buffer, Rope}; use language::{Buffer, Rope};
@ -979,8 +979,6 @@ fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx)); let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
multibuffer.update(cx, |multibuffer, cx| { multibuffer.update(cx, |multibuffer, cx| {
multibuffer.set_all_diff_hunks_expanded(cx);
multibuffer.add_diff(diff.clone(), cx);
multibuffer.push_excerpts( multibuffer.push_excerpts(
buffer.clone(), buffer.clone(),
[ExcerptRange { [ExcerptRange {
@ -989,6 +987,8 @@ fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
}], }],
cx, cx,
); );
multibuffer.set_all_diff_hunks_expanded(cx);
multibuffer.add_diff(diff.clone(), cx);
}); });
cx.run_until_parked(); cx.run_until_parked();
@ -1325,13 +1325,13 @@ fn test_basic_diff_hunks(cx: &mut TestAppContext) {
.map(|info| (info.buffer_row, info.diff_status)) .map(|info| (info.buffer_row, info.diff_status))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
vec![ vec![
(Some(0), Some(DiffHunkStatus::Added)), (Some(0), Some(DiffHunkStatus::added())),
(Some(1), None), (Some(1), None),
(Some(1), Some(DiffHunkStatus::Removed)), (Some(1), Some(DiffHunkStatus::removed())),
(Some(2), Some(DiffHunkStatus::Added)), (Some(2), Some(DiffHunkStatus::added())),
(Some(3), None), (Some(3), None),
(Some(3), Some(DiffHunkStatus::Removed)), (Some(3), Some(DiffHunkStatus::removed())),
(Some(4), Some(DiffHunkStatus::Removed)), (Some(4), Some(DiffHunkStatus::removed())),
(Some(4), None), (Some(4), None),
(Some(5), None) (Some(5), None)
] ]
@ -1999,12 +1999,8 @@ fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id()); let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id()); let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
let base_id_1 = diff_1.read_with(cx, |diff, _| { let base_id_1 = diff_1.read_with(cx, |diff, _| diff.base_text().as_ref().unwrap().remote_id());
diff.snapshot.base_text.as_ref().unwrap().remote_id() let base_id_2 = diff_2.read_with(cx, |diff, _| diff.base_text().as_ref().unwrap().remote_id());
});
let base_id_2 = diff_2.read_with(cx, |diff, _| {
diff.snapshot.base_text.as_ref().unwrap().remote_id()
});
let buffer_lines = (0..=snapshot.max_row().0) let buffer_lines = (0..=snapshot.max_row().0)
.map(|row| { .map(|row| {
@ -2191,9 +2187,8 @@ impl ReferenceMultibuffer {
let Some(diff) = self.diffs.get(&buffer_id) else { let Some(diff) = self.diffs.get(&buffer_id) else {
return; return;
}; };
let diff = diff.read(cx).snapshot.clone();
let excerpt_range = excerpt.range.to_offset(&buffer); let excerpt_range = excerpt.range.to_offset(&buffer);
for hunk in diff.hunks_intersecting_range(range, &buffer) { for hunk in diff.read(cx).hunks_intersecting_range(range, &buffer, cx) {
let hunk_range = hunk.buffer_range.to_offset(&buffer); let hunk_range = hunk.buffer_range.to_offset(&buffer);
if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end { if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end {
continue; continue;
@ -2226,12 +2221,12 @@ impl ReferenceMultibuffer {
let buffer = excerpt.buffer.read(cx); let buffer = excerpt.buffer.read(cx);
let buffer_range = excerpt.range.to_offset(buffer); let buffer_range = excerpt.range.to_offset(buffer);
let diff = self.diffs.get(&buffer.remote_id()).unwrap().read(cx); let diff = self.diffs.get(&buffer.remote_id()).unwrap().read(cx);
let diff = diff.snapshot.clone(); // let diff = diff.snapshot.clone();
let base_buffer = diff.base_text.as_ref().unwrap(); let base_buffer = diff.base_text().unwrap();
let mut offset = buffer_range.start; let mut offset = buffer_range.start;
let mut hunks = diff let mut hunks = diff
.hunks_intersecting_range(excerpt.range.clone(), buffer) .hunks_intersecting_range(excerpt.range.clone(), buffer, cx)
.peekable(); .peekable();
while let Some(hunk) = hunks.next() { while let Some(hunk) = hunks.next() {
@ -2284,7 +2279,7 @@ impl ReferenceMultibuffer {
buffer_start: Some( buffer_start: Some(
base_buffer.offset_to_point(hunk.diff_base_byte_range.start), base_buffer.offset_to_point(hunk.diff_base_byte_range.start),
), ),
status: Some(DiffHunkStatus::Removed), status: Some(DiffHunkStatus::Removed(hunk.secondary_status)),
}); });
} }
@ -2299,7 +2294,7 @@ impl ReferenceMultibuffer {
buffer_id: Some(buffer.remote_id()), buffer_id: Some(buffer.remote_id()),
range: len..text.len(), range: len..text.len(),
buffer_start: Some(buffer.offset_to_point(offset)), buffer_start: Some(buffer.offset_to_point(offset)),
status: Some(DiffHunkStatus::Added), status: Some(DiffHunkStatus::Added(hunk.secondary_status)),
}); });
offset = hunk_range.end; offset = hunk_range.end;
} }
@ -2365,8 +2360,8 @@ impl ReferenceMultibuffer {
let buffer = excerpt.buffer.read(cx).snapshot(); let buffer = excerpt.buffer.read(cx).snapshot();
let excerpt_range = excerpt.range.to_offset(&buffer); let excerpt_range = excerpt.range.to_offset(&buffer);
let buffer_id = buffer.remote_id(); let buffer_id = buffer.remote_id();
let diff = &self.diffs.get(&buffer_id).unwrap().read(cx).snapshot; let diff = self.diffs.get(&buffer_id).unwrap().read(cx);
let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer).peekable(); let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer, cx).peekable();
excerpt.expanded_diff_hunks.retain(|hunk_anchor| { excerpt.expanded_diff_hunks.retain(|hunk_anchor| {
if !hunk_anchor.is_valid(&buffer) { if !hunk_anchor.is_valid(&buffer) {
return false; return false;
@ -2670,7 +2665,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
expected_row_infos expected_row_infos
.into_iter() .into_iter()
.filter_map( .filter_map(
|info| if info.diff_status == Some(DiffHunkStatus::Removed) { |info| if matches!(info.diff_status, Some(DiffHunkStatus::Removed(_))) {
None None
} else { } else {
info.buffer_row info.buffer_row
@ -3027,9 +3022,9 @@ fn format_diff(
.zip(row_infos) .zip(row_infos)
.map(|((ix, line), info)| { .map(|((ix, line), info)| {
let marker = match info.diff_status { let marker = match info.diff_status {
Some(DiffHunkStatus::Added) => "+ ", Some(DiffHunkStatus::Added(_)) => "+ ",
Some(DiffHunkStatus::Removed) => "- ", Some(DiffHunkStatus::Removed(_)) => "- ",
Some(DiffHunkStatus::Modified) => unreachable!(), Some(DiffHunkStatus::Modified(_)) => unreachable!(),
None => { None => {
if has_diff && !line.is_empty() { if has_diff && !line.is_empty() {
" " " "

View file

@ -30,7 +30,7 @@ async-trait.workspace = true
client.workspace = true client.workspace = true
clock.workspace = true clock.workspace = true
collections.workspace = true collections.workspace = true
diff.workspace = true buffer_diff.workspace = true
fs.workspace = true fs.workspace = true
futures.workspace = true futures.workspace = true
fuzzy.workspace = true fuzzy.workspace = true
@ -78,7 +78,7 @@ fancy-regex.workspace = true
[dev-dependencies] [dev-dependencies]
client = { workspace = true, features = ["test-support"] } client = { workspace = true, features = ["test-support"] }
collections = { workspace = true, features = ["test-support"] } collections = { workspace = true, features = ["test-support"] }
diff = { workspace = true, features = ["test-support"] } buffer_diff = { workspace = true, features = ["test-support"] }
env_logger.workspace = true env_logger.workspace = true
fs = { workspace = true, features = ["test-support"] } fs = { workspace = true, features = ["test-support"] }
git2.workspace = true git2.workspace = true

View file

@ -6,9 +6,9 @@ use crate::{
}; };
use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry}; use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
use anyhow::{anyhow, bail, Context as _, Result}; use anyhow::{anyhow, bail, Context as _, Result};
use buffer_diff::{BufferDiff, BufferDiffEvent};
use client::Client; use client::Client;
use collections::{hash_map, HashMap, HashSet}; use collections::{hash_map, HashMap, HashSet};
use diff::{BufferDiff, BufferDiffEvent, BufferDiffSnapshot};
use fs::Fs; use fs::Fs;
use futures::{channel::oneshot, future::Shared, Future, FutureExt as _, StreamExt}; use futures::{channel::oneshot, future::Shared, Future, FutureExt as _, StreamExt};
use git::{blame::Blame, repository::RepoPath}; use git::{blame::Blame, repository::RepoPath};
@ -207,72 +207,74 @@ impl BufferDiffState {
_ => false, _ => false,
}; };
self.recalculate_diff_task = Some(cx.spawn(|this, mut cx| async move { self.recalculate_diff_task = Some(cx.spawn(|this, mut cx| async move {
let mut unstaged_changed_range = None;
if let Some(unstaged_diff) = &unstaged_diff { if let Some(unstaged_diff) = &unstaged_diff {
let snapshot = if index_changed || language_changed { unstaged_changed_range = BufferDiff::update_diff(
cx.update(|cx| { unstaged_diff.clone(),
BufferDiffSnapshot::build( buffer.clone(),
buffer.clone(), index,
index, index_changed,
language.clone(), language_changed,
language_registry.clone(), language.clone(),
cx, language_registry.clone(),
) &mut cx,
})? )
.await .await?;
} else {
unstaged_diff
.read_with(&cx, |changes, cx| {
BufferDiffSnapshot::build_with_base_buffer(
buffer.clone(),
index,
changes.snapshot.base_text.clone(),
cx,
)
})?
.await
};
unstaged_diff.update(&mut cx, |unstaged_diff, cx| { unstaged_diff.update(&mut cx, |_, cx| {
unstaged_diff.set_state(snapshot, &buffer, cx);
if language_changed { if language_changed {
cx.emit(BufferDiffEvent::LanguageChanged); cx.emit(BufferDiffEvent::LanguageChanged);
} }
if let Some(changed_range) = unstaged_changed_range.clone() {
cx.emit(BufferDiffEvent::DiffChanged {
changed_range: Some(changed_range),
})
}
})?; })?;
} }
if let Some(uncommitted_diff) = &uncommitted_diff { if let Some(uncommitted_diff) = &uncommitted_diff {
let snapshot = let uncommitted_changed_range =
if let (Some(unstaged_diff), true) = (&unstaged_diff, index_matches_head) { if let (Some(unstaged_diff), true) = (&unstaged_diff, index_matches_head) {
unstaged_diff.read_with(&cx, |diff, _| diff.snapshot.clone())? uncommitted_diff.update(&mut cx, |uncommitted_diff, cx| {
} else if head_changed || language_changed { uncommitted_diff.update_diff_from(&buffer, unstaged_diff, cx)
cx.update(|cx| {
BufferDiffSnapshot::build(
buffer.clone(),
head,
language.clone(),
language_registry.clone(),
cx,
)
})? })?
.await
} else { } else {
uncommitted_diff BufferDiff::update_diff(
.read_with(&cx, |changes, cx| { uncommitted_diff.clone(),
BufferDiffSnapshot::build_with_base_buffer( buffer.clone(),
buffer.clone(), head,
head, head_changed,
changes.snapshot.base_text.clone(), language_changed,
cx, language.clone(),
) language_registry.clone(),
})? &mut cx,
.await )
.await?
}; };
uncommitted_diff.update(&mut cx, |diff, cx| { uncommitted_diff.update(&mut cx, |uncommitted_diff, cx| {
diff.set_state(snapshot, &buffer, cx);
if language_changed { if language_changed {
cx.emit(BufferDiffEvent::LanguageChanged); cx.emit(BufferDiffEvent::LanguageChanged);
} }
let changed_range = match (unstaged_changed_range, uncommitted_changed_range) {
(None, None) => None,
(Some(unstaged_range), None) => {
uncommitted_diff.range_to_hunk_range(unstaged_range, &buffer, cx)
}
(None, Some(uncommitted_range)) => Some(uncommitted_range),
(Some(unstaged_range), Some(uncommitted_range)) => maybe!({
let expanded_range = uncommitted_diff.range_to_hunk_range(
unstaged_range,
&buffer,
cx,
)?;
let start = expanded_range.start.min(&uncommitted_range.start, &buffer);
let end = expanded_range.end.max(&uncommitted_range.end, &buffer);
Some(start..end)
}),
};
cx.emit(BufferDiffEvent::DiffChanged { changed_range });
})?; })?;
} }
@ -280,6 +282,7 @@ impl BufferDiffState {
this.update(&mut cx, |this, _| { this.update(&mut cx, |this, _| {
this.index_changed = false; this.index_changed = false;
this.head_changed = false; this.head_changed = false;
this.language_changed = false;
for tx in this.diff_updated_futures.drain(..) { for tx in this.diff_updated_futures.drain(..) {
tx.send(()).ok(); tx.send(()).ok();
} }
@ -1478,29 +1481,19 @@ impl BufferStore {
diff_state.language = language; diff_state.language = language;
diff_state.language_registry = language_registry; diff_state.language_registry = language_registry;
let diff = cx.new(|_| BufferDiff { let diff = cx.new(|_| BufferDiff::new(&text_snapshot));
buffer_id,
snapshot: BufferDiffSnapshot::new(&text_snapshot),
unstaged_diff: None,
});
match kind { match kind {
DiffKind::Unstaged => diff_state.unstaged_diff = Some(diff.downgrade()), DiffKind::Unstaged => diff_state.unstaged_diff = Some(diff.downgrade()),
DiffKind::Uncommitted => { DiffKind::Uncommitted => {
let unstaged_diff = if let Some(diff) = diff_state.unstaged_diff() { let unstaged_diff = if let Some(diff) = diff_state.unstaged_diff() {
diff diff
} else { } else {
let unstaged_diff = cx.new(|_| BufferDiff { let unstaged_diff = cx.new(|_| BufferDiff::new(&text_snapshot));
buffer_id,
snapshot: BufferDiffSnapshot::new(&text_snapshot),
unstaged_diff: None,
});
diff_state.unstaged_diff = Some(unstaged_diff.downgrade()); diff_state.unstaged_diff = Some(unstaged_diff.downgrade());
unstaged_diff unstaged_diff
}; };
diff.update(cx, |diff, _| { diff.update(cx, |diff, _| diff.set_secondary_diff(unstaged_diff));
diff.unstaged_diff = Some(unstaged_diff);
});
diff_state.uncommitted_diff = Some(diff.downgrade()) diff_state.uncommitted_diff = Some(diff.downgrade())
} }
}; };
@ -2397,9 +2390,8 @@ impl BufferStore {
shared.diff = Some(diff.clone()); shared.diff = Some(diff.clone());
} }
})?; })?;
let staged_text = diff.read_with(&cx, |diff, _| { let staged_text =
diff.snapshot.base_text.as_ref().map(|buffer| buffer.text()) diff.read_with(&cx, |diff, _| diff.base_text().map(|buffer| buffer.text()))?;
})?;
Ok(proto::OpenUnstagedDiffResponse { staged_text }) Ok(proto::OpenUnstagedDiffResponse { staged_text })
} }
@ -2430,14 +2422,13 @@ impl BufferStore {
use proto::open_uncommitted_diff_response::Mode; use proto::open_uncommitted_diff_response::Mode;
let staged_buffer = diff let staged_buffer = diff
.unstaged_diff .secondary_diff()
.as_ref() .and_then(|diff| diff.read(cx).base_text());
.and_then(|diff| diff.read(cx).snapshot.base_text.as_ref());
let mode; let mode;
let staged_text; let staged_text;
let committed_text; let committed_text;
if let Some(committed_buffer) = &diff.snapshot.base_text { if let Some(committed_buffer) = diff.base_text() {
committed_text = Some(committed_buffer.text()); committed_text = Some(committed_buffer.text());
if let Some(staged_buffer) = staged_buffer { if let Some(staged_buffer) = staged_buffer {
if staged_buffer.remote_id() == committed_buffer.remote_id() { if staged_buffer.remote_id() == committed_buffer.remote_id() {

View file

@ -21,7 +21,7 @@ mod project_tests;
mod direnv; mod direnv;
mod environment; mod environment;
use diff::BufferDiff; use buffer_diff::BufferDiff;
pub use environment::EnvironmentErrorMessage; pub use environment::EnvironmentErrorMessage;
use git::Repository; use git::Repository;
pub mod search_history; pub mod search_history;

View file

@ -1,5 +1,5 @@
use crate::{Event, *}; use crate::{Event, *};
use diff::assert_hunks; use buffer_diff::{assert_hunks, DiffHunkSecondaryStatus, DiffHunkStatus};
use fs::FakeFs; use fs::FakeFs;
use futures::{future, StreamExt}; use futures::{future, StreamExt};
use gpui::{App, SemanticVersion, UpdateGlobal}; use gpui::{App, SemanticVersion, UpdateGlobal};
@ -5692,15 +5692,16 @@ async fn test_unstaged_diff_for_buffer(cx: &mut gpui::TestAppContext) {
unstaged_diff.update(cx, |unstaged_diff, cx| { unstaged_diff.update(cx, |unstaged_diff, cx| {
let snapshot = buffer.read(cx).snapshot(); let snapshot = buffer.read(cx).snapshot();
assert_hunks( assert_hunks(
unstaged_diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), unstaged_diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx),
&snapshot, &snapshot,
&unstaged_diff.base_text_string().unwrap(), &unstaged_diff.base_text_string().unwrap(),
&[ &[
(0..1, "", "// print goodbye\n"), (0..1, "", "// print goodbye\n", DiffHunkStatus::added()),
( (
2..3, 2..3,
" println!(\"hello world\");\n", " println!(\"hello world\");\n",
" println!(\"goodbye world\");\n", " println!(\"goodbye world\");\n",
DiffHunkStatus::modified(),
), ),
], ],
); );
@ -5722,10 +5723,15 @@ async fn test_unstaged_diff_for_buffer(cx: &mut gpui::TestAppContext) {
unstaged_diff.update(cx, |unstaged_diff, cx| { unstaged_diff.update(cx, |unstaged_diff, cx| {
let snapshot = buffer.read(cx).snapshot(); let snapshot = buffer.read(cx).snapshot();
assert_hunks( assert_hunks(
unstaged_diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), unstaged_diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx),
&snapshot, &snapshot,
&unstaged_diff.snapshot.base_text.as_ref().unwrap().text(), &unstaged_diff.base_text().unwrap().text(),
&[(2..3, "", " println!(\"goodbye world\");\n")], &[(
2..3,
"",
" println!(\"goodbye world\");\n",
DiffHunkStatus::added(),
)],
); );
}); });
} }
@ -5795,10 +5801,7 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
uncommitted_diff.read_with(cx, |diff, _| { uncommitted_diff.read_with(cx, |diff, _| {
assert_eq!( assert_eq!(
diff.snapshot diff.base_text().and_then(|base| base.language().cloned()),
.base_text
.as_ref()
.and_then(|base| base.language().cloned()),
Some(language) Some(language)
) )
}); });
@ -5807,15 +5810,21 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
uncommitted_diff.update(cx, |uncommitted_diff, cx| { uncommitted_diff.update(cx, |uncommitted_diff, cx| {
let snapshot = buffer.read(cx).snapshot(); let snapshot = buffer.read(cx).snapshot();
assert_hunks( assert_hunks(
uncommitted_diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), uncommitted_diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx),
&snapshot, &snapshot,
&uncommitted_diff.base_text_string().unwrap(), &uncommitted_diff.base_text_string().unwrap(),
&[ &[
(0..1, "", "// print goodbye\n"), (
0..1,
"",
"// print goodbye\n",
DiffHunkStatus::Added(DiffHunkSecondaryStatus::HasSecondaryHunk),
),
( (
2..3, 2..3,
" println!(\"hello world\");\n", " println!(\"hello world\");\n",
" println!(\"goodbye world\");\n", " println!(\"goodbye world\");\n",
DiffHunkStatus::modified(),
), ),
], ],
); );
@ -5837,10 +5846,15 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
uncommitted_diff.update(cx, |uncommitted_diff, cx| { uncommitted_diff.update(cx, |uncommitted_diff, cx| {
let snapshot = buffer.read(cx).snapshot(); let snapshot = buffer.read(cx).snapshot();
assert_hunks( assert_hunks(
uncommitted_diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), uncommitted_diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx),
&snapshot, &snapshot,
&uncommitted_diff.snapshot.base_text.as_ref().unwrap().text(), &uncommitted_diff.base_text().unwrap().text(),
&[(2..3, "", " println!(\"goodbye world\");\n")], &[(
2..3,
"",
" println!(\"goodbye world\");\n",
DiffHunkStatus::added(),
)],
); );
}); });
} }
@ -5898,13 +5912,14 @@ async fn test_single_file_diffs(cx: &mut gpui::TestAppContext) {
uncommitted_diff.update(cx, |uncommitted_diff, cx| { uncommitted_diff.update(cx, |uncommitted_diff, cx| {
let snapshot = buffer.read(cx).snapshot(); let snapshot = buffer.read(cx).snapshot();
assert_hunks( assert_hunks(
uncommitted_diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), uncommitted_diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx),
&snapshot, &snapshot,
&uncommitted_diff.snapshot.base_text.as_ref().unwrap().text(), &uncommitted_diff.base_text_string().unwrap(),
&[( &[(
1..2, 1..2,
" println!(\"hello from HEAD\");\n", " println!(\"hello from HEAD\");\n",
" println!(\"hello from the working copy\");\n", " println!(\"hello from the working copy\");\n",
DiffHunkStatus::modified(),
)], )],
); );
}); });

View file

@ -1246,8 +1246,7 @@ async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppC
diff.read_with(cx, |diff, cx| { diff.read_with(cx, |diff, cx| {
assert_eq!(diff.base_text_string().unwrap(), text_1); assert_eq!(diff.base_text_string().unwrap(), text_1);
assert_eq!( assert_eq!(
diff.unstaged_diff diff.secondary_diff()
.as_ref()
.unwrap() .unwrap()
.read(cx) .read(cx)
.base_text_string() .base_text_string()
@ -1266,8 +1265,7 @@ async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppC
diff.read_with(cx, |diff, cx| { diff.read_with(cx, |diff, cx| {
assert_eq!(diff.base_text_string().unwrap(), text_1); assert_eq!(diff.base_text_string().unwrap(), text_1);
assert_eq!( assert_eq!(
diff.unstaged_diff diff.secondary_diff()
.as_ref()
.unwrap() .unwrap()
.read(cx) .read(cx)
.base_text_string() .base_text_string()
@ -1286,8 +1284,7 @@ async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppC
diff.read_with(cx, |diff, cx| { diff.read_with(cx, |diff, cx| {
assert_eq!(diff.base_text_string().unwrap(), text_2); assert_eq!(diff.base_text_string().unwrap(), text_2);
assert_eq!( assert_eq!(
diff.unstaged_diff diff.secondary_diff()
.as_ref()
.unwrap() .unwrap()
.read(cx) .read(cx)
.base_text_string() .base_text_string()