diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 182efdfdd6..b163a25d95 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) } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4e5863407f..6be0650848 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 483fd56cc5..40e7c89cb2 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 e749f5dcfb..d470dcbaaf 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -343,17 +343,6 @@ impl MultiBuffer { self.read(cx).symbols_containing(offset, theme) } - pub fn git_diff_recalc(&mut self, _: &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, diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index f6eb2a5d2d..27e9bfab38 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -620,7 +620,6 @@ impl Buffer { cx, ); } - self.git_diff_recalc(cx); cx.emit(Event::Reloaded); cx.notify(); } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3f520c7e99..b66f525061 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, @@ -131,6 +134,7 @@ pub struct Project { 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<()>, @@ -138,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, @@ -486,6 +533,7 @@ impl Project { 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(), @@ -576,6 +624,7 @@ impl Project { 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 { @@ -2077,19 +2126,36 @@ impl Project { cx: &mut ModelContext, ) { self.buffers_needing_diff.insert(buffer.downgrade()); - if self.buffers_needing_diff.len() == 1 { - 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); - }); - } + 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) { + 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() @@ -2109,7 +2175,7 @@ impl Project { this.update(&mut cx, |this, cx| { if !this.buffers_needing_diff.is_empty() { - this.recalculate_buffer_diffs(cx); + this.recalculate_buffer_diffs(cx).detach(); } else { // TODO: Would a `ModelContext.notify()` suffice here? for buffer in buffers { @@ -2120,7 +2186,6 @@ impl Project { } }); }) - .detach(); } fn language_servers_for_worktree( diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index c542d1d13f..607b284813 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/search/src/project_search.rs b/crates/search/src/project_search.rs index 915957401d..d86823d224 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 => { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index c947078015..9a3fb5e475 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 0510d89cef..346fa7637d 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 4202c00a8d..6483167018 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)]