diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 182efdfdd6c520e30d285ed822f66f9a3f98a368..12fcc1395bf5a430452f1647cc197aa07df91f4b 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -609,15 +609,6 @@ impl Item for ProjectDiagnosticsEditor { unreachable!() } - fn git_diff_recalc( - &mut self, - project: ModelHandle, - cx: &mut ViewContext, - ) -> Task> { - self.editor - .update(cx, |editor, cx| editor.git_diff_recalc(project, cx)) - } - fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { Editor::to_item_events(event) } @@ -1508,6 +1499,7 @@ mod tests { language::init(cx); client::init_settings(cx); workspace::init_settings(cx); + Project::init_settings(cx); }); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5a504a610cd63529e80e0b1a6f872f0fe08fb8bd..f5d109e15bd2138fba6914e058b575a5bdf42e80 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6873,6 +6873,7 @@ impl Editor { multi_buffer::Event::Saved => cx.emit(Event::Saved), multi_buffer::Event::FileHandleChanged => cx.emit(Event::TitleChanged), multi_buffer::Event::Reloaded => cx.emit(Event::TitleChanged), + multi_buffer::Event::DiffBaseChanged => cx.emit(Event::DiffBaseChanged), multi_buffer::Event::Closed => cx.emit(Event::Closed), multi_buffer::Event::DiagnosticsUpdated => { self.refresh_active_diagnostics(cx); @@ -7261,6 +7262,7 @@ pub enum Event { DirtyChanged, Saved, TitleChanged, + DiffBaseChanged, SelectionsChanged { local: bool, }, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 180de155e998fff1492c22311502d87ebcf744bf..bc671b9ffc3652856d16faf567784eab438440b7 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -1246,7 +1246,7 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { #[gpui::test] async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache())); cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height)); @@ -1358,7 +1358,7 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppCon #[gpui::test] async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache())); cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height)); @@ -1473,7 +1473,7 @@ async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; cx.set_state("one «two threeˇ» four"); cx.update_editor(|editor, cx| { editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx); @@ -1637,7 +1637,7 @@ async fn test_newline_above(cx: &mut gpui::TestAppContext) { .unwrap(), ); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); cx.set_state(indoc! {" const a: ˇA = ( @@ -1685,7 +1685,7 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) { .unwrap(), ); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); cx.set_state(indoc! {" const a: ˇA = ( @@ -1751,7 +1751,7 @@ async fn test_tab(cx: &mut gpui::TestAppContext) { settings.defaults.tab_size = NonZeroU32::new(3) }); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; cx.set_state(indoc! {" ˇabˇc ˇ🏀ˇ🏀ˇefg @@ -1779,7 +1779,7 @@ async fn test_tab(cx: &mut gpui::TestAppContext) { async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; let language = Arc::new( Language::new( LanguageConfig::default(), @@ -1850,7 +1850,7 @@ async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) { .unwrap(), ); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); cx.set_state(indoc! {" fn a() { @@ -1876,7 +1876,7 @@ async fn test_indent_outdent(cx: &mut gpui::TestAppContext) { settings.defaults.tab_size = NonZeroU32::new(4); }); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; cx.set_state(indoc! {" «oneˇ» «twoˇ» @@ -1949,7 +1949,7 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) { settings.defaults.hard_tabs = Some(true); }); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; // select two ranges on one line cx.set_state(indoc! {" @@ -2156,7 +2156,7 @@ fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) { async fn test_backspace(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; // Basic backspace cx.set_state(indoc! {" @@ -2205,7 +2205,7 @@ async fn test_backspace(cx: &mut gpui::TestAppContext) { async fn test_delete(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; cx.set_state(indoc! {" onˇe two three fou«rˇ» five six @@ -2559,7 +2559,7 @@ fn test_transpose(cx: &mut TestAppContext) { async fn test_clipboard(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six "); cx.update_editor(|e, cx| e.cut(&Cut, cx)); @@ -2641,7 +2641,7 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) { async fn test_paste_multiline(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; let language = Arc::new(Language::new( LanguageConfig::default(), Some(tree_sitter_rust::language()), @@ -3085,7 +3085,7 @@ fn test_add_selection_above_below(cx: &mut TestAppContext) { async fn test_select_next(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; cx.set_state("abc\nˇabc abc\ndefabc\nabc"); cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx)); @@ -3314,7 +3314,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) { async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; let language = Arc::new(Language::new( LanguageConfig { @@ -3485,7 +3485,7 @@ async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) { async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; let html_language = Arc::new( Language::new( @@ -3721,7 +3721,7 @@ async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) { async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; let rust_language = Arc::new( Language::new( @@ -4938,7 +4938,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) let registry = Arc::new(LanguageRegistry::test()); registry.add(language.clone()); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; cx.update_buffer(|buffer, cx| { buffer.set_language_registry(registry); buffer.set_language(Some(language), cx); @@ -5060,7 +5060,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; let html_language = Arc::new( Language::new( @@ -5985,7 +5985,7 @@ fn test_combine_syntax_and_fuzzy_match_highlights() { async fn go_to_hunk(deterministic: Arc, cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let mut cx = EditorTestContext::new(cx); + let mut cx = EditorTestContext::new(cx).await; let diff_base = r#" use some::mod; diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4e5863407f208ae0d4b0b84030d1b21521164ec6..6be065084814e570a0411ffb91df4a4eb380e1ac 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -40,7 +40,10 @@ use language::{ language_settings::ShowWhitespaceSetting, Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, Selection, }; -use project::ProjectPath; +use project::{ + project_settings::{GitGutterSetting, ProjectSettings}, + ProjectPath, +}; use smallvec::SmallVec; use std::{ borrow::Cow, @@ -51,7 +54,7 @@ use std::{ sync::Arc, }; use text::Point; -use workspace::{item::Item, GitGutterSetting, WorkspaceSettings}; +use workspace::item::Item; enum FoldMarkers {} @@ -551,11 +554,8 @@ impl EditorElement { let scroll_top = scroll_position.y() * line_height; let show_gutter = matches!( - settings::get::(cx) - .git - .git_gutter - .unwrap_or_default(), - GitGutterSetting::TrackedFiles + settings::get::(cx).git.git_gutter, + Some(GitGutterSetting::TrackedFiles) ); if show_gutter { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 483fd56cc5f3c3934aeb4619525ee7746567cdae..40e7c89cb298e34510002550349f7faff6f0fa19 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -720,17 +720,6 @@ impl Item for Editor { }) } - fn git_diff_recalc( - &mut self, - _project: ModelHandle, - cx: &mut ViewContext, - ) -> Task> { - self.buffer().update(cx, |multibuffer, cx| { - multibuffer.git_diff_recalc(cx); - }); - Task::ready(Ok(())) - } - fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { let mut result = SmallVec::new(); match event { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 6b1ad6c5b2d6eb7c2e7bd9be9376b5fad04b8dd7..4650dff38f58088eab5fc1638080680dc86dd84e 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -66,6 +66,7 @@ pub enum Event { }, Edited, Reloaded, + DiffBaseChanged, LanguageChanged, Reparsed, Saved, @@ -343,17 +344,6 @@ impl MultiBuffer { self.read(cx).symbols_containing(offset, theme) } - pub fn git_diff_recalc(&mut self, cx: &mut ModelContext) { - let buffers = self.buffers.borrow(); - for buffer_state in buffers.values() { - if buffer_state.buffer.read(cx).needs_git_diff_recalc() { - buffer_state - .buffer - .update(cx, |buffer, cx| buffer.git_diff_recalc(cx)) - } - } - } - pub fn edit( &mut self, edits: I, @@ -1312,6 +1302,7 @@ impl MultiBuffer { language::Event::Saved => Event::Saved, language::Event::FileHandleChanged => Event::FileHandleChanged, language::Event::Reloaded => Event::Reloaded, + language::Event::DiffBaseChanged => Event::DiffBaseChanged, language::Event::LanguageChanged => Event::LanguageChanged, language::Event::Reparsed => Event::Reparsed, language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated, @@ -1550,6 +1541,13 @@ impl MultiBuffer { cx.add_model(|cx| Self::singleton(buffer, cx)) } + pub fn build_from_buffer( + buffer: ModelHandle, + cx: &mut gpui::AppContext, + ) -> ModelHandle { + cx.add_model(|cx| Self::singleton(buffer, cx)) + } + pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui::AppContext) -> ModelHandle { cx.add_model(|cx| { let mut multibuffer = MultiBuffer::new(0); @@ -3870,10 +3868,13 @@ where #[cfg(test)] mod tests { + use crate::editor_tests::init_test; + use super::*; use futures::StreamExt; use gpui::{AppContext, TestAppContext}; use language::{Buffer, Rope}; + use project::{FakeFs, Project}; use rand::prelude::*; use settings::SettingsStore; use std::{env, rc::Rc}; @@ -4564,73 +4565,85 @@ mod tests { #[gpui::test] async fn test_diff_hunks_in_range(cx: &mut TestAppContext) { use git::diff::DiffHunkStatus; + init_test(cx, |_| {}); + + let fs = FakeFs::new(cx.background()); + let project = Project::test(fs, [], cx).await; // buffer has two modified hunks with two rows each - let buffer_1 = cx.add_model(|cx| { - let mut buffer = Buffer::new( - 0, - " - 1.zero - 1.ONE - 1.TWO - 1.three - 1.FOUR - 1.FIVE - 1.six - " - .unindent(), - cx, - ); + let buffer_1 = project + .update(cx, |project, cx| { + project.create_buffer( + " + 1.zero + 1.ONE + 1.TWO + 1.three + 1.FOUR + 1.FIVE + 1.six + " + .unindent() + .as_str(), + None, + cx, + ) + }) + .unwrap(); + buffer_1.update(cx, |buffer, cx| { buffer.set_diff_base( Some( " - 1.zero - 1.one - 1.two - 1.three - 1.four - 1.five - 1.six - " + 1.zero + 1.one + 1.two + 1.three + 1.four + 1.five + 1.six + " .unindent(), ), cx, ); - buffer }); // buffer has a deletion hunk and an insertion hunk - let buffer_2 = cx.add_model(|cx| { - let mut buffer = Buffer::new( - 0, - " - 2.zero - 2.one - 2.two - 2.three - 2.four - 2.five - 2.six - " - .unindent(), - cx, - ); + let buffer_2 = project + .update(cx, |project, cx| { + project.create_buffer( + " + 2.zero + 2.one + 2.two + 2.three + 2.four + 2.five + 2.six + " + .unindent() + .as_str(), + None, + cx, + ) + }) + .unwrap(); + buffer_2.update(cx, |buffer, cx| { buffer.set_diff_base( Some( " - 2.zero - 2.one - 2.one-and-a-half - 2.two - 2.three - 2.four - 2.six - " + 2.zero + 2.one + 2.one-and-a-half + 2.two + 2.three + 2.four + 2.six + " .unindent(), ), cx, ); - buffer }); cx.foreground().run_until_parked(); diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index e520562ebba1d410e58e9ae3a9438899701a62c5..95da7ff297341e18f99ca57f7cbcadb707a8fdb4 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -7,6 +7,7 @@ use gpui::{ }; use indoc::indoc; use language::{Buffer, BufferSnapshot}; +use project::{FakeFs, Project}; use std::{ any::TypeId, ops::{Deref, DerefMut, Range}, @@ -25,11 +26,16 @@ pub struct EditorTestContext<'a> { } impl<'a> EditorTestContext<'a> { - pub fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> { + pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> { + let fs = FakeFs::new(cx.background()); + let project = Project::test(fs, [], cx).await; + let buffer = project + .update(cx, |project, cx| project.create_buffer("", None, cx)) + .unwrap(); let (window_id, editor) = cx.update(|cx| { cx.add_window(Default::default(), |cx| { cx.focus_self(); - build_editor(MultiBuffer::build_simple("", cx), cx) + build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx) }) }); diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 6d2ba115b7f7eadaf4c2aa51903f0fdb95adae38..c4fce491523134e2b7f64678cd89da121fff251f 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1198,6 +1198,7 @@ mod tests { super::init(cx); editor::init(cx); workspace::init_settings(cx); + Project::init_settings(cx); state }) } diff --git a/crates/git/src/diff.rs b/crates/git/src/diff.rs index 8260dfc98d9834f8cefff6dfc274a4d7a9e237d7..39383cfc78b297e355c1d4c096219069cd92b1da 100644 --- a/crates/git/src/diff.rs +++ b/crates/git/src/diff.rs @@ -161,13 +161,6 @@ impl BufferDiff { self.tree = SumTree::new(); } - pub fn needs_update(&self, buffer: &text::BufferSnapshot) -> bool { - match &self.last_buffer_version { - Some(last) => buffer.version().changed_since(last), - None => true, - } - } - pub async fn update(&mut self, diff_base: &str, buffer: &text::BufferSnapshot) { let mut tree = SumTree::new(); diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5539d1d941726148fc543a59652d57190e1d400d..93b50cf597cfd3e0ed18ac8e317fa5847740f1b1 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -50,16 +50,10 @@ pub use {tree_sitter_rust, tree_sitter_typescript}; pub use lsp::DiagnosticSeverity; -struct GitDiffStatus { - diff: git::diff::BufferDiff, - update_in_progress: bool, - update_requested: bool, -} - pub struct Buffer { text: TextBuffer, diff_base: Option, - git_diff_status: GitDiffStatus, + git_diff: git::diff::BufferDiff, file: Option>, saved_version: clock::Global, saved_version_fingerprint: RopeFingerprint, @@ -195,6 +189,7 @@ pub enum Event { Saved, FileHandleChanged, Reloaded, + DiffBaseChanged, LanguageChanged, Reparsed, DiagnosticsUpdated, @@ -466,11 +461,7 @@ impl Buffer { was_dirty_before_starting_transaction: None, text: buffer, diff_base, - git_diff_status: GitDiffStatus { - diff: git::diff::BufferDiff::new(), - update_in_progress: false, - update_requested: false, - }, + git_diff: git::diff::BufferDiff::new(), file, syntax_map: Mutex::new(SyntaxMap::new()), parsing_in_background: false, @@ -501,7 +492,7 @@ impl Buffer { BufferSnapshot { text, syntax, - git_diff: self.git_diff_status.diff.clone(), + git_diff: self.git_diff.clone(), file: self.file.clone(), remote_selections: self.remote_selections.clone(), diagnostics: self.diagnostics.clone(), @@ -620,7 +611,6 @@ impl Buffer { cx, ); } - self.git_diff_recalc(cx); cx.emit(Event::Reloaded); cx.notify(); } @@ -676,50 +666,29 @@ impl Buffer { pub fn set_diff_base(&mut self, diff_base: Option, cx: &mut ModelContext) { self.diff_base = diff_base; self.git_diff_recalc(cx); + cx.emit(Event::DiffBaseChanged); } - pub fn needs_git_diff_recalc(&self) -> bool { - self.git_diff_status.diff.needs_update(self) - } - - pub fn git_diff_recalc(&mut self, cx: &mut ModelContext) { - if self.git_diff_status.update_in_progress { - self.git_diff_status.update_requested = true; - return; - } - - if let Some(diff_base) = &self.diff_base { - let snapshot = self.snapshot(); - let diff_base = diff_base.clone(); + pub fn git_diff_recalc(&mut self, cx: &mut ModelContext) -> Option> { + let diff_base = self.diff_base.clone()?; // TODO: Make this an Arc + let snapshot = self.snapshot(); - let mut diff = self.git_diff_status.diff.clone(); - let diff = cx.background().spawn(async move { - diff.update(&diff_base, &snapshot).await; - diff - }); + let mut diff = self.git_diff.clone(); + let diff = cx.background().spawn(async move { + diff.update(&diff_base, &snapshot).await; + diff + }); - cx.spawn_weak(|this, mut cx| async move { - let buffer_diff = diff.await; - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, cx| { - this.git_diff_status.diff = buffer_diff; - this.git_diff_update_count += 1; - cx.notify(); - - this.git_diff_status.update_in_progress = false; - if this.git_diff_status.update_requested { - this.git_diff_recalc(cx); - } - }) - } - }) - .detach() - } else { - let snapshot = self.snapshot(); - self.git_diff_status.diff.clear(&snapshot); - self.git_diff_update_count += 1; - cx.notify(); - } + let handle = cx.weak_handle(); + Some(cx.spawn_weak(|_, mut cx| async move { + let buffer_diff = diff.await; + if let Some(this) = handle.upgrade(&mut cx) { + this.update(&mut cx, |this, _| { + this.git_diff = buffer_diff; + this.git_diff_update_count += 1; + }) + } + })) } pub fn close(&mut self, cx: &mut ModelContext) { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 391a698a1b7dd17fba4161665cb65edd5df4943e..6a6f87897860ebe09b81326553c1bf779012cc88 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1,6 +1,6 @@ mod ignore; mod lsp_command; -mod project_settings; +pub mod project_settings; pub mod search; pub mod terminals; pub mod worktree; @@ -14,7 +14,10 @@ use clock::ReplicaId; use collections::{hash_map, BTreeMap, HashMap, HashSet}; use copilot::Copilot; use futures::{ - channel::mpsc::{self, UnboundedReceiver}, + channel::{ + mpsc::{self, UnboundedReceiver}, + oneshot, + }, future::{try_join_all, Shared}, stream::FuturesUnordered, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt, @@ -130,6 +133,8 @@ pub struct Project { incomplete_remote_buffers: HashMap>>, buffer_snapshots: HashMap>>, // buffer_id -> server_id -> vec of snapshots buffers_being_formatted: HashSet, + buffers_needing_diff: HashSet>, + git_diff_debouncer: DelayedDebounced, nonce: u128, _maintain_buffer_languages: Task<()>, _maintain_workspace_config: Task<()>, @@ -137,6 +142,49 @@ pub struct Project { copilot_enabled: bool, } +struct DelayedDebounced { + task: Option>, + cancel_channel: Option>, +} + +impl DelayedDebounced { + fn new() -> DelayedDebounced { + DelayedDebounced { + task: None, + cancel_channel: None, + } + } + + fn fire_new(&mut self, delay: Duration, cx: &mut ModelContext, func: F) + where + F: 'static + FnOnce(&mut Project, &mut ModelContext) -> Task<()>, + { + if let Some(channel) = self.cancel_channel.take() { + _ = channel.send(()); + } + + let (sender, mut receiver) = oneshot::channel::<()>(); + self.cancel_channel = Some(sender); + + let previous_task = self.task.take(); + self.task = Some(cx.spawn(|workspace, mut cx| async move { + let mut timer = cx.background().timer(delay).fuse(); + if let Some(previous_task) = previous_task { + previous_task.await; + } + + futures::select_biased! { + _ = receiver => return, + _ = timer => {} + } + + workspace + .update(&mut cx, |workspace, cx| (func)(workspace, cx)) + .await; + })); + } +} + struct LspBufferSnapshot { version: i32, snapshot: TextBufferSnapshot, @@ -484,6 +532,8 @@ impl Project { language_server_statuses: Default::default(), last_workspace_edits_by_language_server: Default::default(), buffers_being_formatted: Default::default(), + buffers_needing_diff: Default::default(), + git_diff_debouncer: DelayedDebounced::new(), nonce: StdRng::from_entropy().gen(), terminals: Terminals { local_handles: Vec::new(), @@ -573,6 +623,8 @@ impl Project { last_workspace_edits_by_language_server: Default::default(), opened_buffers: Default::default(), buffers_being_formatted: Default::default(), + buffers_needing_diff: Default::default(), + git_diff_debouncer: DelayedDebounced::new(), buffer_snapshots: Default::default(), nonce: StdRng::from_entropy().gen(), terminals: Terminals { @@ -1607,6 +1659,7 @@ impl Project { buffer: &ModelHandle, cx: &mut ModelContext, ) -> Result<()> { + self.request_buffer_diff_recalculation(buffer, cx); buffer.update(cx, |buffer, _| { buffer.set_language_registry(self.languages.clone()) }); @@ -1924,6 +1977,13 @@ impl Project { event: &BufferEvent, cx: &mut ModelContext, ) -> Option<()> { + if matches!( + event, + BufferEvent::Edited { .. } | BufferEvent::Reloaded | BufferEvent::DiffBaseChanged + ) { + self.request_buffer_diff_recalculation(&buffer, cx); + } + match event { BufferEvent::Operation(operation) => { self.buffer_ordered_messages_tx @@ -2063,6 +2123,74 @@ impl Project { None } + fn request_buffer_diff_recalculation( + &mut self, + buffer: &ModelHandle, + cx: &mut ModelContext, + ) { + self.buffers_needing_diff.insert(buffer.downgrade()); + let first_insertion = self.buffers_needing_diff.len() == 1; + + let settings = settings::get::(cx); + let delay = if let Some(delay) = settings.git.gutter_debounce { + delay + } else { + if first_insertion { + let this = cx.weak_handle(); + cx.defer(move |cx| { + if let Some(this) = this.upgrade(cx) { + this.update(cx, |this, cx| { + this.recalculate_buffer_diffs(cx).detach(); + }); + } + }); + } + return; + }; + + const MIN_DELAY: u64 = 50; + let delay = delay.max(MIN_DELAY); + let duration = Duration::from_millis(delay); + + self.git_diff_debouncer + .fire_new(duration, cx, move |this, cx| { + this.recalculate_buffer_diffs(cx) + }); + } + + fn recalculate_buffer_diffs(&mut self, cx: &mut ModelContext) -> Task<()> { + cx.spawn(|this, mut cx| async move { + let buffers: Vec<_> = this.update(&mut cx, |this, _| { + this.buffers_needing_diff.drain().collect() + }); + + let tasks: Vec<_> = this.update(&mut cx, |_, cx| { + buffers + .iter() + .filter_map(|buffer| { + let buffer = buffer.upgrade(cx)?; + buffer.update(cx, |buffer, cx| buffer.git_diff_recalc(cx)) + }) + .collect() + }); + + futures::future::join_all(tasks).await; + + this.update(&mut cx, |this, cx| { + if !this.buffers_needing_diff.is_empty() { + this.recalculate_buffer_diffs(cx).detach(); + } else { + // TODO: Would a `ModelContext.notify()` suffice here? + for buffer in buffers { + if let Some(buffer) = buffer.upgrade(cx) { + buffer.update(cx, |_, cx| cx.notify()); + } + } + } + }); + }) + } + fn language_servers_for_worktree( &self, worktree_id: WorktreeId, @@ -6189,11 +6317,13 @@ impl Project { let Some(this) = this.upgrade(&cx) else { return Err(anyhow!("project dropped")); }; + let buffer = this.read_with(&cx, |this, cx| { this.opened_buffers .get(&id) .and_then(|buffer| buffer.upgrade(cx)) }); + if let Some(buffer) = buffer { break buffer; } else if this.read_with(&cx, |this, _| this.is_read_only()) { @@ -6204,12 +6334,13 @@ impl Project { this.incomplete_remote_buffers.entry(id).or_default(); }); drop(this); + opened_buffer_rx .next() .await .ok_or_else(|| anyhow!("project dropped while waiting for buffer"))?; }; - buffer.update(&mut cx, |buffer, cx| buffer.git_diff_recalc(cx)); + Ok(buffer) }) } diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index c542d1d13fd42c3cd2721c92981e74129556a554..607b2848139aa88b1d36821030507de8c85ed72a 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -8,6 +8,22 @@ use std::sync::Arc; pub struct ProjectSettings { #[serde(default)] pub lsp: HashMap, LspSettings>, + #[serde(default)] + pub git: GitSettings, +} + +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct GitSettings { + pub git_gutter: Option, + pub gutter_debounce: Option, +} + +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum GitGutterSetting { + #[default] + TrackedFiles, + Hide, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index e48ce6258b34f38f438dd6035a533a99a0be5d0a..285e3ee9b678c71708f13c66e6af11b41a80ac48 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1273,6 +1273,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) { // The diagnostics have moved down since they were created. buffer.next_notification(cx).await; + buffer.next_notification(cx).await; buffer.read_with(cx, |buffer, _| { assert_eq!( buffer diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index d2c035f91675a779b84a4facfc6331091b481910..3a7044de0c705be6084401c2f90e9ef2b007e1d7 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -719,11 +719,7 @@ impl LocalWorktree { .background() .spawn(async move { text::Buffer::new(0, id, contents) }) .await; - Ok(cx.add_model(|cx| { - let mut buffer = Buffer::build(text_buffer, diff_base, Some(Arc::new(file))); - buffer.git_diff_recalc(cx); - buffer - })) + Ok(cx.add_model(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file))))) }) } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 6604472d863ac338b98fe7f088c37b68db122d54..33e4340f2254f8fb6c97ba0e283ff4347446ea00 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -2229,6 +2229,7 @@ mod tests { editor::init_settings(cx); crate::init(cx); workspace::init_settings(cx); + Project::init_settings(cx); }); } @@ -2243,6 +2244,7 @@ mod tests { pane::init(cx); crate::init(cx); workspace::init(app_state.clone(), cx); + Project::init_settings(cx); }); } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 915957401d440c40dd13d4b3364b0be76e09f3ef..7d95ec232d8359dac2755a1b96fa754914b5ede1 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -360,15 +360,6 @@ impl Item for ProjectSearchView { .update(cx, |editor, cx| editor.navigate(data, cx)) } - fn git_diff_recalc( - &mut self, - project: ModelHandle, - cx: &mut ViewContext, - ) -> Task> { - self.results_editor - .update(cx, |editor, cx| editor.git_diff_recalc(project, cx)) - } - fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { match event { ViewEvent::UpdateTab => { @@ -1277,6 +1268,7 @@ pub mod tests { client::init_settings(cx); editor::init_settings(cx); workspace::init_settings(cx); + Project::init_settings(cx); }); } } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index c9470780153608e25bf879f94109e61626673cc4..9a3fb5e475dd0c612fecccfefdecab95606bb776 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -1,9 +1,8 @@ use crate::{ - pane, persistence::model::ItemId, searchable::SearchableItemHandle, DelayedDebouncedEditAction, - FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, - WorkspaceId, + pane, persistence::model::ItemId, searchable::SearchableItemHandle, FollowableItemBuilders, + ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId, }; -use crate::{AutosaveSetting, WorkspaceSettings}; +use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings}; use anyhow::Result; use client::{proto, Client}; use gpui::{ @@ -102,13 +101,6 @@ pub trait Item: View { ) -> Task> { unimplemented!("reload() must be implemented if can_save() returns true") } - fn git_diff_recalc( - &mut self, - _project: ModelHandle, - _cx: &mut ViewContext, - ) -> Task> { - Task::ready(Ok(())) - } fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { SmallVec::new() } @@ -221,11 +213,6 @@ pub trait ItemHandle: 'static + fmt::Debug { cx: &mut WindowContext, ) -> Task>; fn reload(&self, project: ModelHandle, cx: &mut WindowContext) -> Task>; - fn git_diff_recalc( - &self, - project: ModelHandle, - cx: &mut WindowContext, - ) -> Task>; fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>; fn to_followable_item_handle(&self, cx: &AppContext) -> Option>; fn on_release( @@ -381,7 +368,6 @@ impl ItemHandle for ViewHandle { .is_none() { let mut pending_autosave = DelayedDebouncedEditAction::new(); - let mut pending_git_update = DelayedDebouncedEditAction::new(); let pending_update = Rc::new(RefCell::new(None)); let pending_update_scheduled = Rc::new(AtomicBool::new(false)); @@ -450,48 +436,14 @@ impl ItemHandle for ViewHandle { } ItemEvent::Edit => { - let settings = settings::get::(cx); - let debounce_delay = settings.git.gutter_debounce; - - if let AutosaveSetting::AfterDelay { milliseconds } = - settings.autosave - { + let autosave = settings::get::(cx).autosave; + if let AutosaveSetting::AfterDelay { milliseconds } = autosave { let delay = Duration::from_millis(milliseconds); let item = item.clone(); pending_autosave.fire_new(delay, cx, move |workspace, cx| { Pane::autosave_item(&item, workspace.project().clone(), cx) }); } - - let item = item.clone(); - - if let Some(delay) = debounce_delay { - const MIN_GIT_DELAY: u64 = 50; - - let delay = delay.max(MIN_GIT_DELAY); - let duration = Duration::from_millis(delay); - - pending_git_update.fire_new( - duration, - cx, - move |workspace, cx| { - item.git_diff_recalc(workspace.project().clone(), cx) - }, - ); - } else { - cx.spawn(|workspace, mut cx| async move { - workspace - .update(&mut cx, |workspace, cx| { - item.git_diff_recalc( - workspace.project().clone(), - cx, - ) - })? - .await?; - anyhow::Ok(()) - }) - .detach_and_log_err(cx); - } } _ => {} @@ -576,14 +528,6 @@ impl ItemHandle for ViewHandle { self.update(cx, |item, cx| item.reload(project, cx)) } - fn git_diff_recalc( - &self, - project: ModelHandle, - cx: &mut WindowContext, - ) -> Task> { - self.update(cx, |item, cx| item.git_diff_recalc(project, cx)) - } - fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle> { self.read(cx).act_as_type(type_id, self, cx) } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 0510d89cefc2096b1ee622cd825396b01a25d6ee..346fa7637d7659c62d40a39b6c7b3c5b6c339bfa 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -442,7 +442,7 @@ impl DelayedDebouncedEditAction { } } - fn fire_new(&mut self, delay: Duration, cx: &mut ViewContext, f: F) + fn fire_new(&mut self, delay: Duration, cx: &mut ViewContext, func: F) where F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> Task>, { @@ -466,7 +466,7 @@ impl DelayedDebouncedEditAction { } if let Some(result) = workspace - .update(&mut cx, |workspace, cx| (f)(workspace, cx)) + .update(&mut cx, |workspace, cx| (func)(workspace, cx)) .log_err() { result.await.log_err(); diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index 4202c00a8d7a9fb6384280977493896ab5906fbc..64831670189696ecd8c90877193af0fb11c8239b 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -8,7 +8,6 @@ pub struct WorkspaceSettings { pub confirm_quit: bool, pub show_call_status_icon: bool, pub autosave: AutosaveSetting, - pub git: GitSettings, } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] @@ -17,7 +16,6 @@ pub struct WorkspaceSettingsContent { pub confirm_quit: Option, pub show_call_status_icon: Option, pub autosave: Option, - pub git: Option, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 1dfe9c24e54dd15f098d2a4e5d1df2f2c2e75f1e..619dd81a80407faf2f001fb4d47449bee98938bf 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2085,6 +2085,7 @@ mod tests { theme::init((), cx); call::init(app_state.client.clone(), app_state.user_store.clone(), cx); workspace::init(app_state.clone(), cx); + Project::init_settings(cx); language::init(cx); editor::init(cx); project_panel::init_settings(cx);