From ca01a8b9cb58c90efb29475b5fa4b001a6146392 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 5 Feb 2025 13:29:39 -0800 Subject: [PATCH] Fix two issues with diff highlights (#24309) * fix syntax highlighting of deleted text when buffer language changes * do not highlight entire untracked files as created, except in the project diff view Release Notes: - N/A Co-authored-by: ConradIrwin Co-authored-by: cole-miller --- crates/git/src/diff.rs | 12 ++++ crates/git_ui/src/project_diff.rs | 16 +++-- crates/language/src/buffer.rs | 17 ++++++ crates/multi_buffer/src/multi_buffer.rs | 77 +++++++++++++++++-------- crates/project/src/buffer_store.rs | 56 +++++++++++------- 5 files changed, 126 insertions(+), 52 deletions(-) diff --git a/crates/git/src/diff.rs b/crates/git/src/diff.rs index 7fd6628a89efc8..764c2541193218 100644 --- a/crates/git/src/diff.rs +++ b/crates/git/src/diff.rs @@ -74,6 +74,18 @@ impl BufferDiff { } } + pub fn new_with_single_insertion(buffer: &BufferSnapshot) -> Self { + Self { + tree: SumTree::from_item( + InternalDiffHunk { + buffer_range: Anchor::MIN..Anchor::MAX, + diff_base_byte_range: 0..0, + }, + buffer, + ), + } + } + pub fn build(diff_base: Option<&str>, buffer: &text::BufferSnapshot) -> Self { let mut tree = SumTree::new(buffer); diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index a78f097e244d33..d35bb59d6a79e4 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -9,7 +9,7 @@ use gpui::{ actions, AnyElement, AnyView, App, AppContext, AsyncWindowContext, Entity, EventEmitter, FocusHandle, Focusable, Render, Subscription, Task, WeakEntity, }; -use language::{Anchor, Buffer, Capability, OffsetRangeExt}; +use language::{Anchor, Buffer, Capability, OffsetRangeExt, Point}; use multi_buffer::{MultiBuffer, PathKey}; use project::{buffer_store::BufferChangeSet, git::GitState, Project, ProjectPath}; use theme::ActiveTheme; @@ -293,11 +293,15 @@ impl ProjectDiff { let change_set = diff_buffer.change_set; let snapshot = buffer.read(cx).snapshot(); - let diff_hunk_ranges = change_set - .read(cx) - .diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot) - .map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot)) - .collect::>(); + let change_set = change_set.read(cx); + let diff_hunk_ranges = if change_set.base_text.is_none() { + vec![Point::zero()..snapshot.max_point()] + } else { + change_set + .diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot) + .map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot)) + .collect::>() + }; self.multibuffer.update(cx, |multibuffer, cx| { multibuffer.set_excerpts_for_path( diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index ceb387d2e10ce5..b2112f31c5ae5b 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1001,6 +1001,23 @@ impl Buffer { } } + pub fn build_empty_snapshot(cx: &mut App) -> BufferSnapshot { + let entity_id = cx.reserve_entity::().entity_id(); + let buffer_id = entity_id.as_non_zero_u64().into(); + let text = + TextBuffer::new_normalized(0, buffer_id, Default::default(), Rope::new()).snapshot(); + let syntax = SyntaxMap::new(&text).snapshot(); + BufferSnapshot { + text, + syntax, + file: None, + diagnostics: Default::default(), + remote_selections: Default::default(), + language: None, + non_text_state_update_count: 0, + } + } + #[cfg(any(test, feature = "test-support"))] pub fn build_snapshot_sync( text: Rope, diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index d00c161a8633a1..1986944a1d6e95 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -220,6 +220,22 @@ struct ChangeSetState { _subscription: gpui::Subscription, } +impl ChangeSetState { + fn new(change_set: Entity, cx: &mut Context) -> Self { + ChangeSetState { + _subscription: cx.subscribe(&change_set, |this, change_set, event, cx| match event { + BufferChangeSetEvent::DiffChanged { changed_range } => { + this.buffer_diff_changed(change_set, changed_range.clone(), cx) + } + BufferChangeSetEvent::LanguageChanged => { + this.buffer_diff_language_changed(change_set, cx) + } + }), + change_set, + } + } +} + /// The contents of a [`MultiBuffer`] at a single point in time. #[derive(Clone, Default)] pub struct MultiBufferSnapshot { @@ -560,17 +576,7 @@ impl MultiBuffer { for (buffer_id, change_set_state) in self.diff_bases.iter() { diff_bases.insert( *buffer_id, - ChangeSetState { - _subscription: new_cx.subscribe( - &change_set_state.change_set, - |this, change_set, event, cx| match event { - BufferChangeSetEvent::DiffChanged { changed_range } => { - this.buffer_diff_changed(change_set, changed_range.clone(), cx) - } - }, - ), - change_set: change_set_state.change_set.clone(), - }, + ChangeSetState::new(change_set_state.change_set.clone(), new_cx), ); } Self { @@ -2146,6 +2152,30 @@ impl MultiBuffer { }); } + fn buffer_diff_language_changed( + &mut self, + change_set: Entity, + cx: &mut Context, + ) { + self.sync(cx); + let mut snapshot = self.snapshot.borrow_mut(); + let change_set = change_set.read(cx); + let buffer_id = change_set.buffer_id; + let base_text = change_set.base_text.clone(); + let diff = change_set.diff_to_buffer.clone(); + if let Some(base_text) = base_text { + snapshot.diffs.insert( + buffer_id, + DiffSnapshot { + diff: diff.clone(), + base_text, + }, + ); + } else { + snapshot.diffs.remove(&buffer_id); + } + } + fn buffer_diff_changed( &mut self, change_set: Entity, @@ -2175,6 +2205,15 @@ impl MultiBuffer { base_text, }, ); + } else if self.all_diff_hunks_expanded { + let base_text = Buffer::build_empty_snapshot(cx); + snapshot.diffs.insert( + buffer_id, + DiffSnapshot { + diff: git::diff::BufferDiff::new_with_single_insertion(&base_text), + base_text, + }, + ); } else { snapshot.diffs.remove(&buffer_id); } @@ -2316,20 +2355,8 @@ impl MultiBuffer { pub fn add_change_set(&mut self, change_set: Entity, cx: &mut Context) { let buffer_id = change_set.read(cx).buffer_id; self.buffer_diff_changed(change_set.clone(), text::Anchor::MIN..text::Anchor::MAX, cx); - self.diff_bases.insert( - buffer_id, - ChangeSetState { - _subscription: cx.subscribe( - &change_set, - |this, change_set, event, cx| match event { - BufferChangeSetEvent::DiffChanged { changed_range } => { - this.buffer_diff_changed(change_set, changed_range.clone(), cx); - } - }, - ), - change_set, - }, - ); + self.diff_bases + .insert(buffer_id, ChangeSetState::new(change_set, cx)); } pub fn change_set_for(&self, buffer_id: BufferId) -> Option> { diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs index d3831dcce30f62..82bfca95b7216f 100644 --- a/crates/project/src/buffer_store.rs +++ b/crates/project/src/buffer_store.rs @@ -85,6 +85,7 @@ struct BufferChangeSetState { index_text: Option>, head_changed: bool, index_changed: bool, + language_changed: bool, } #[derive(Clone, Debug)] @@ -101,8 +102,7 @@ enum DiffBasesChange { impl BufferChangeSetState { fn buffer_language_changed(&mut self, buffer: Entity, cx: &mut Context) { self.language = buffer.read(cx).language().cloned(); - self.index_changed = self.index_text.is_some(); - self.head_changed = self.head_text.is_some(); + self.language_changed = true; let _ = self.recalculate_diffs(buffer.read(cx).text_snapshot(), cx); } @@ -149,34 +149,40 @@ impl BufferChangeSetState { ) -> oneshot::Receiver<()> { match diff_bases_change { DiffBasesChange::SetIndex(index) => { - let mut index = index.unwrap_or_default(); - text::LineEnding::normalize(&mut index); - self.index_text = Some(Arc::new(index)); + self.index_text = index.map(|mut index| { + text::LineEnding::normalize(&mut index); + Arc::new(index) + }); self.index_changed = true; } DiffBasesChange::SetHead(head) => { - let mut head = head.unwrap_or_default(); - text::LineEnding::normalize(&mut head); - self.head_text = Some(Arc::new(head)); + self.head_text = head.map(|mut head| { + text::LineEnding::normalize(&mut head); + Arc::new(head) + }); self.head_changed = true; } DiffBasesChange::SetBoth(text) => { - let mut text = text.unwrap_or_default(); - text::LineEnding::normalize(&mut text); - self.head_text = Some(Arc::new(text)); - self.index_text = self.head_text.clone(); + let text = text.map(|mut text| { + text::LineEnding::normalize(&mut text); + Arc::new(text) + }); + self.head_text = text.clone(); + self.index_text = text; self.head_changed = true; self.index_changed = true; } DiffBasesChange::SetEach { index, head } => { - let mut index = index.unwrap_or_default(); - text::LineEnding::normalize(&mut index); - let mut head = head.unwrap_or_default(); - text::LineEnding::normalize(&mut head); - self.index_text = Some(Arc::new(index)); - self.head_text = Some(Arc::new(head)); - self.head_changed = true; + self.index_text = index.map(|mut index| { + text::LineEnding::normalize(&mut index); + Arc::new(index) + }); self.index_changed = true; + self.head_text = head.map(|mut head| { + text::LineEnding::normalize(&mut head); + Arc::new(head) + }); + self.head_changed = true; } } @@ -199,6 +205,7 @@ impl BufferChangeSetState { let index = self.index_text.clone(); let index_changed = self.index_changed; let head_changed = self.head_changed; + let language_changed = self.language_changed; let index_matches_head = match (self.index_text.as_ref(), self.head_text.as_ref()) { (Some(index), Some(head)) => Arc::ptr_eq(index, head), (None, None) => true, @@ -206,7 +213,7 @@ impl BufferChangeSetState { }; self.recalculate_diff_task = Some(cx.spawn(|this, mut cx| async move { if let Some(unstaged_changes) = &unstaged_changes { - let staged_snapshot = if index_changed { + let staged_snapshot = if index_changed || language_changed { let staged_snapshot = cx.update(|cx| { index.as_ref().map(|head| { language::Buffer::build_snapshot( @@ -238,6 +245,9 @@ impl BufferChangeSetState { unstaged_changes.update(&mut cx, |unstaged_changes, cx| { unstaged_changes.set_state(staged_snapshot.clone(), diff, &buffer, cx); + if language_changed { + cx.emit(BufferChangeSetEvent::LanguageChanged); + } })?; } @@ -252,7 +262,7 @@ impl BufferChangeSetState { ) })? } else { - let committed_snapshot = if head_changed { + let committed_snapshot = if head_changed || language_changed { let committed_snapshot = cx.update(|cx| { head.as_ref().map(|head| { language::Buffer::build_snapshot( @@ -284,6 +294,9 @@ impl BufferChangeSetState { uncommitted_changes.update(&mut cx, |change_set, cx| { change_set.set_state(snapshot, diff, &buffer, cx); + if language_changed { + cx.emit(BufferChangeSetEvent::LanguageChanged); + } })?; } @@ -323,6 +336,7 @@ impl std::fmt::Debug for BufferChangeSet { pub enum BufferChangeSetEvent { DiffChanged { changed_range: Range }, + LanguageChanged, } enum BufferStoreState {