diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 7a86e5019642b4e91c320f6d07392ecd0120af5a..105bcff8db096b4e0772fe29b12f750cf5dc9656 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -9,7 +9,10 @@ use gpui::{ }; use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId}; use lsp::LanguageServerName; -use project::{EnvironmentErrorMessage, LanguageServerProgress, Project, WorktreeId}; +use project::{ + EnvironmentErrorMessage, LanguageServerProgress, LspStoreEvent, Project, + ProjectEnvironmentEvent, WorktreeId, +}; use smallvec::SmallVec; use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration}; use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip}; @@ -73,7 +76,22 @@ impl ActivityIndicator { }) .detach(); - cx.observe(&project, |_, _, cx| cx.notify()).detach(); + cx.subscribe( + &project.read(cx).lsp_store(), + |_, _, event, cx| match event { + LspStoreEvent::LanguageServerUpdate { .. } => cx.notify(), + _ => {} + }, + ) + .detach(); + + cx.subscribe( + &project.read(cx).environment().clone(), + |_, _, event, cx| match event { + ProjectEnvironmentEvent::ErrorsUpdated => cx.notify(), + }, + ) + .detach(); if let Some(auto_updater) = auto_updater.as_ref() { cx.observe(auto_updater, |_, _, cx| cx.notify()).detach(); @@ -204,7 +222,7 @@ impl ActivityIndicator { message: error.0.clone(), on_click: Some(Arc::new(move |this, window, cx| { this.project.update(cx, |project, cx| { - project.remove_environment_error(cx, worktree_id); + project.remove_environment_error(worktree_id, cx); }); window.dispatch_action(Box::new(workspace::OpenLog), cx); })), diff --git a/crates/assistant_context_editor/src/context_store.rs b/crates/assistant_context_editor/src/context_store.rs index cda9f68ea7bf6940ab3a39f0ff165e11819a75f6..557b6d3e6eb48eff22d316b939706d13249dbc34 100644 --- a/crates/assistant_context_editor/src/context_store.rs +++ b/crates/assistant_context_editor/src/context_store.rs @@ -104,49 +104,53 @@ impl ContextStore { const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100); let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await; - let this = cx.new(|cx: &mut Context| { - let context_server_factory_registry = - ContextServerFactoryRegistry::default_global(cx); - let context_server_manager = cx.new(|cx| { - ContextServerManager::new(context_server_factory_registry, project.clone(), cx) - }); - let mut this = Self { - contexts: Vec::new(), - contexts_metadata: Vec::new(), - context_server_manager, - context_server_slash_command_ids: HashMap::default(), - host_contexts: Vec::new(), - fs, - languages, - slash_commands, - telemetry, - _watch_updates: cx.spawn(|this, mut cx| { - async move { - while events.next().await.is_some() { - this.update(&mut cx, |this, cx| this.reload(cx))? - .await - .log_err(); + let this = + cx.new(|cx: &mut Context| { + let context_server_factory_registry = + ContextServerFactoryRegistry::default_global(cx); + let context_server_manager = cx.new(|cx| { + ContextServerManager::new( + context_server_factory_registry, + project.clone(), + cx, + ) + }); + let mut this = Self { + contexts: Vec::new(), + contexts_metadata: Vec::new(), + context_server_manager, + context_server_slash_command_ids: HashMap::default(), + host_contexts: Vec::new(), + fs, + languages, + slash_commands, + telemetry, + _watch_updates: cx.spawn(|this, mut cx| { + async move { + while events.next().await.is_some() { + this.update(&mut cx, |this, cx| this.reload(cx))? + .await + .log_err(); + } + anyhow::Ok(()) } - anyhow::Ok(()) - } - .log_err() - }), - client_subscription: None, - _project_subscriptions: vec![ - cx.observe(&project, Self::handle_project_changed), - cx.subscribe(&project, Self::handle_project_event), - ], - project_is_shared: false, - client: project.read(cx).client(), - project: project.clone(), - prompt_builder, - }; - this.handle_project_changed(project.clone(), cx); - this.synchronize_contexts(cx); - this.register_context_server_handlers(cx); - this.reload(cx).detach_and_log_err(cx); - this - })?; + .log_err() + }), + client_subscription: None, + _project_subscriptions: vec![ + cx.subscribe(&project, Self::handle_project_event) + ], + project_is_shared: false, + client: project.read(cx).client(), + project: project.clone(), + prompt_builder, + }; + this.handle_project_shared(project.clone(), cx); + this.synchronize_contexts(cx); + this.register_context_server_handlers(cx); + this.reload(cx).detach_and_log_err(cx); + this + })?; Ok(this) }) @@ -288,7 +292,7 @@ impl ContextStore { })? } - fn handle_project_changed(&mut self, _: Entity, cx: &mut Context) { + fn handle_project_shared(&mut self, _: Entity, cx: &mut Context) { let is_shared = self.project.read(cx).is_shared(); let was_shared = mem::replace(&mut self.project_is_shared, is_shared); if is_shared == was_shared { @@ -318,11 +322,14 @@ impl ContextStore { fn handle_project_event( &mut self, - _: Entity, + project: Entity, event: &project::Event, cx: &mut Context, ) { match event { + project::Event::RemoteIdChanged(_) => { + self.handle_project_shared(project, cx); + } project::Event::Reshared => { self.advertise_contexts(cx); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 86c46ad015643b18621c33b7f8e39ec37c02b141..49176f782306108c0da018aa7ac6b2018a1d1cda 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1250,11 +1250,6 @@ impl Editor { let mut project_subscriptions = Vec::new(); if mode == EditorMode::Full { if let Some(project) = project.as_ref() { - if buffer.read(cx).is_singleton() { - project_subscriptions.push(cx.observe_in(project, window, |_, _, _, cx| { - cx.emit(EditorEvent::TitleChanged); - })); - } project_subscriptions.push(cx.subscribe_in( project, window, @@ -15442,14 +15437,9 @@ impl Editor { } multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged), multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved), - multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => { - cx.emit(EditorEvent::TitleChanged) - } - // multi_buffer::Event::DiffBaseChanged => { - // self.scrollbar_marker_state.dirty = true; - // cx.emit(EditorEvent::DiffBaseChanged); - // cx.notify(); - // } + multi_buffer::Event::FileHandleChanged + | multi_buffer::Event::Reloaded + | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged), multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed), multi_buffer::Event::DiagnosticsUpdated => { self.refresh_active_diagnostics(cx); diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index c7f8c5db9bacd69866a88264254309090464eaf8..e5f5a23b0cccf288e58df88c2e859461acc9295a 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -121,6 +121,7 @@ pub enum Event { Discarded, DirtyChanged, DiagnosticsUpdated, + BufferDiffChanged, } /// A diff hunk, representing a range of consequent lines in a multibuffer. @@ -253,6 +254,7 @@ impl DiffState { if let Some(changed_range) = changed_range.clone() { this.buffer_diff_changed(diff, changed_range, cx) } + cx.emit(Event::BufferDiffChanged); } BufferDiffEvent::LanguageChanged => this.buffer_diff_language_changed(diff, cx), _ => {} diff --git a/crates/project/src/environment.rs b/crates/project/src/environment.rs index 9a5aa9fba14245e63f44f77d2840e9a70722cf52..ad550ce0d92c8f016601957b98d28bff4d1921b4 100644 --- a/crates/project/src/environment.rs +++ b/crates/project/src/environment.rs @@ -3,7 +3,7 @@ use std::{path::Path, sync::Arc}; use util::ResultExt; use collections::HashMap; -use gpui::{App, AppContext as _, Context, Entity, Task}; +use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task}; use settings::Settings as _; use worktree::WorktreeId; @@ -19,6 +19,12 @@ pub struct ProjectEnvironment { environment_error_messages: HashMap, } +pub enum ProjectEnvironmentEvent { + ErrorsUpdated, +} + +impl EventEmitter for ProjectEnvironment {} + impl ProjectEnvironment { pub fn new( worktree_store: &Entity, @@ -65,8 +71,13 @@ impl ProjectEnvironment { self.environment_error_messages.iter() } - pub(crate) fn remove_environment_error(&mut self, worktree_id: WorktreeId) { + pub(crate) fn remove_environment_error( + &mut self, + worktree_id: WorktreeId, + cx: &mut Context, + ) { self.environment_error_messages.remove(&worktree_id); + cx.emit(ProjectEnvironmentEvent::ErrorsUpdated); } /// Returns the project environment, if possible. @@ -158,8 +169,9 @@ impl ProjectEnvironment { } if let Some(error) = error_message { - this.update(&mut cx, |this, _| { + this.update(&mut cx, |this, cx| { this.environment_error_messages.insert(worktree_id, error); + cx.emit(ProjectEnvironmentEvent::ErrorsUpdated) }) .log_err(); } diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 11e5384f60054b92cee4b5721909e73a6012d8e7..4060cdf2fe1364bfd45c3d2fe73b94b3bef9f5ed 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -6667,33 +6667,19 @@ impl LspStore { cx, ); } - lsp::WorkDoneProgress::Report(report) => { - if self.on_lsp_work_progress( - language_server_id, - token.clone(), - LanguageServerProgress { - title: None, - is_disk_based_diagnostics_progress, - is_cancellable: report.cancellable.unwrap_or(false), - message: report.message.clone(), - percentage: report.percentage.map(|p| p as usize), - last_update_at: cx.background_executor().now(), - }, - cx, - ) { - cx.emit(LspStoreEvent::LanguageServerUpdate { - language_server_id, - message: proto::update_language_server::Variant::WorkProgress( - proto::LspWorkProgress { - token, - message: report.message, - percentage: report.percentage, - is_cancellable: report.cancellable, - }, - ), - }) - } - } + lsp::WorkDoneProgress::Report(report) => self.on_lsp_work_progress( + language_server_id, + token, + LanguageServerProgress { + title: None, + is_disk_based_diagnostics_progress, + is_cancellable: report.cancellable.unwrap_or(false), + message: report.message, + percentage: report.percentage.map(|p| p as usize), + last_update_at: cx.background_executor().now(), + }, + cx, + ), lsp::WorkDoneProgress::End(_) => { language_server_status.progress_tokens.remove(&token); self.on_lsp_work_end(language_server_id, token.clone(), cx); @@ -6733,13 +6719,13 @@ impl LspStore { token: String, progress: LanguageServerProgress, cx: &mut Context, - ) -> bool { + ) { + let mut did_update = false; if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) { - match status.pending_work.entry(token) { + match status.pending_work.entry(token.clone()) { btree_map::Entry::Vacant(entry) => { - entry.insert(progress); - cx.notify(); - return true; + entry.insert(progress.clone()); + did_update = true; } btree_map::Entry::Occupied(mut entry) => { let entry = entry.get_mut(); @@ -6748,7 +6734,7 @@ impl LspStore { { entry.last_update_at = progress.last_update_at; if progress.message.is_some() { - entry.message = progress.message; + entry.message = progress.message.clone(); } if progress.percentage.is_some() { entry.percentage = progress.percentage; @@ -6756,14 +6742,25 @@ impl LspStore { if progress.is_cancellable != entry.is_cancellable { entry.is_cancellable = progress.is_cancellable; } - cx.notify(); - return true; + did_update = true; } } } } - false + if did_update { + cx.emit(LspStoreEvent::LanguageServerUpdate { + language_server_id, + message: proto::update_language_server::Variant::WorkProgress( + proto::LspWorkProgress { + token, + message: progress.message, + percentage: progress.percentage.map(|p| p as u32), + is_cancellable: Some(progress.is_cancellable), + }, + ), + }) + } } fn on_lsp_work_end( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index bdc8404e6b7f46acd798de74d1a789ef18d30cc2..a109e98f5f9724f53d1b2ed0608e466c64da0ebf 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -22,7 +22,7 @@ mod project_tests; mod direnv; mod environment; use buffer_diff::BufferDiff; -pub use environment::EnvironmentErrorMessage; +pub use environment::{EnvironmentErrorMessage, ProjectEnvironmentEvent}; use git::Repository; pub mod search_history; mod yarn; @@ -886,7 +886,6 @@ impl Project { }); cx.subscribe(&ssh, Self::on_ssh_event).detach(); - cx.observe(&ssh, |_, _, cx| cx.notify()).detach(); let this = Self { buffer_ordered_messages_tx: tx, @@ -1371,9 +1370,9 @@ impl Project { self.environment.read(cx).environment_errors() } - pub fn remove_environment_error(&mut self, cx: &mut Context, worktree_id: WorktreeId) { - self.environment.update(cx, |environment, _| { - environment.remove_environment_error(worktree_id); + pub fn remove_environment_error(&mut self, worktree_id: WorktreeId, cx: &mut Context) { + self.environment.update(cx, |environment, cx| { + environment.remove_environment_error(worktree_id, cx); }); } @@ -1764,7 +1763,6 @@ impl Project { }; cx.emit(Event::RemoteIdChanged(Some(project_id))); - cx.notify(); Ok(()) } @@ -1780,7 +1778,6 @@ impl Project { self.worktree_store.update(cx, |worktree_store, cx| { worktree_store.send_project_updates(cx); }); - cx.notify(); cx.emit(Event::Reshared); Ok(()) } @@ -1810,13 +1807,12 @@ impl Project { self.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync) .unwrap(); cx.emit(Event::Rejoined); - cx.notify(); Ok(()) } pub fn unshare(&mut self, cx: &mut Context) -> Result<()> { self.unshare_internal(cx)?; - cx.notify(); + cx.emit(Event::RemoteIdChanged(None)); Ok(()) } @@ -1860,7 +1856,6 @@ impl Project { } self.disconnected_from_host_internal(cx); cx.emit(Event::DisconnectedFromHost); - cx.notify(); } pub fn set_role(&mut self, role: proto::ChannelRole, cx: &mut Context) { @@ -2509,15 +2504,11 @@ impl Project { } } - fn on_worktree_added(&mut self, worktree: &Entity, cx: &mut Context) { - { - let mut remotely_created_models = self.remotely_created_models.lock(); - if remotely_created_models.retain_count > 0 { - remotely_created_models.worktrees.push(worktree.clone()) - } + fn on_worktree_added(&mut self, worktree: &Entity, _: &mut Context) { + let mut remotely_created_models = self.remotely_created_models.lock(); + if remotely_created_models.retain_count > 0 { + remotely_created_models.worktrees.push(worktree.clone()) } - cx.observe(worktree, |_, _, cx| cx.notify()).detach(); - cx.notify(); } fn on_worktree_released(&mut self, id_to_remove: WorktreeId, cx: &mut Context) { @@ -2529,8 +2520,6 @@ impl Project { }) .log_err(); } - - cx.notify(); } fn on_buffer_event( @@ -3804,7 +3793,6 @@ impl Project { cx.emit(Event::CollaboratorJoined(collaborator.peer_id)); this.collaborators .insert(collaborator.peer_id, collaborator); - cx.notify(); })?; Ok(()) @@ -3848,7 +3836,6 @@ impl Project { old_peer_id, new_peer_id, }); - cx.notify(); Ok(()) })? } @@ -3876,7 +3863,6 @@ impl Project { }); cx.emit(Event::CollaboratorLeft(peer_id)); - cx.notify(); Ok(()) })? } @@ -4292,7 +4278,6 @@ impl Project { worktrees: Vec, cx: &mut Context, ) -> Result<()> { - cx.notify(); self.worktree_store.update(cx, |worktree_store, cx| { worktree_store.set_worktrees_from_proto(worktrees, self.replica_id(), cx) }) diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index f0f186f570ae1be84196e9dc9e46e56856f89dc1..de514d1339a91e4064af725d30951d142c889354 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -307,7 +307,7 @@ impl TitleBar { cx.notify() }), ); - subscriptions.push(cx.observe(&project, |_, _, cx| cx.notify())); + subscriptions.push(cx.subscribe(&project, |_, _, _, cx| cx.notify())); subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx))); subscriptions.push(cx.observe_window_activation(window, Self::window_activation_changed)); subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify())); diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 83eb5d27c588b6ec1752eb22b0273cd2de7ed3ff..8bf0691c7543ea8e728f32c56ba26a8ad7423329 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -852,6 +852,7 @@ impl ItemHandle for Entity { .detach(); let item_id = self.item_id(); + workspace.update_item_dirty_state(self, window, cx); cx.observe_release_in(self, window, move |workspace, _, _, _| { workspace.panes_by_item.remove(&item_id); event_subscription.take(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6ac97d50455f428d13c8cbe31e709451a2f7e5bf..2b51bfbfad461082db54ec35fb56dc10fe368aba 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -879,8 +879,6 @@ impl Workspace { window: &mut Window, cx: &mut Context, ) -> Self { - cx.observe_in(&project, window, |_, _, _, cx| cx.notify()) - .detach(); cx.subscribe_in(&project, window, move |this, _, event, window, cx| { match event { project::Event::RemoteIdChanged(_) => { diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index c2ac090a1d61773d637b9fa42ee953d1ec971525..0e3cec4c093874fc3c5c0ed138c4a1175723ae87 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -1570,7 +1570,6 @@ impl LocalWorktree { this.update_abs_path_and_refresh(new_path, cx); } } - cx.notify(); }) .ok(); } @@ -5889,14 +5888,21 @@ impl WorktreeModelHandle for Entity { .await .unwrap(); - cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_some()) - .await; + let mut events = cx.events(&tree); + while events.next().await.is_some() { + if tree.update(cx, |tree, _| tree.entry_for_path(file_name).is_some()) { + break; + } + } fs.remove_file(&root_path.join(file_name), Default::default()) .await .unwrap(); - cx.condition(&tree, |tree, _| tree.entry_for_path(file_name).is_none()) - .await; + while events.next().await.is_some() { + if tree.update(cx, |tree, _| tree.entry_for_path(file_name).is_none()) { + break; + } + } cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; @@ -5950,19 +5956,22 @@ impl WorktreeModelHandle for Entity { .await .unwrap(); - cx.condition(&tree, |tree, _| { - scan_id_increased(tree, &mut git_dir_scan_id) - }) - .await; + let mut events = cx.events(&tree); + while events.next().await.is_some() { + if tree.update(cx, |tree, _| scan_id_increased(tree, &mut git_dir_scan_id)) { + break; + } + } fs.remove_file(&root_path.join(file_name), Default::default()) .await .unwrap(); - cx.condition(&tree, |tree, _| { - scan_id_increased(tree, &mut git_dir_scan_id) - }) - .await; + while events.next().await.is_some() { + if tree.update(cx, |tree, _| scan_id_increased(tree, &mut git_dir_scan_id)) { + break; + } + } cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index 15131b0eac17624414f00ee26f50e343412b782b..616213020d7e88b3fc01fcfa797fd352a4d82c19 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -12,6 +12,7 @@ use git::{ }, GITIGNORE, }; +use git2::RepositoryInitOptions; use gpui::{AppContext as _, BorrowAppContext, Context, Task, TestAppContext}; use parking_lot::Mutex; use postage::stream::Stream; @@ -855,7 +856,7 @@ async fn test_write_file(cx: &mut TestAppContext) { "ignored-dir": {} })); - let tree = Worktree::local( + let worktree = Worktree::local( dir.path(), true, Arc::new(RealFs::default()), @@ -868,32 +869,34 @@ async fn test_write_file(cx: &mut TestAppContext) { #[cfg(not(target_os = "macos"))] fs::fs_watcher::global(|_| {}).unwrap(); - cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + cx.read(|cx| worktree.read(cx).as_local().unwrap().scan_complete()) .await; - tree.flush_fs_events(cx).await; + worktree.flush_fs_events(cx).await; - tree.update(cx, |tree, cx| { - tree.write_file( - Path::new("tracked-dir/file.txt"), - "hello".into(), - Default::default(), - cx, - ) - }) - .await - .unwrap(); - tree.update(cx, |tree, cx| { - tree.write_file( - Path::new("ignored-dir/file.txt"), - "world".into(), - Default::default(), - cx, - ) - }) - .await - .unwrap(); + worktree + .update(cx, |tree, cx| { + tree.write_file( + Path::new("tracked-dir/file.txt"), + "hello".into(), + Default::default(), + cx, + ) + }) + .await + .unwrap(); + worktree + .update(cx, |tree, cx| { + tree.write_file( + Path::new("ignored-dir/file.txt"), + "world".into(), + Default::default(), + cx, + ) + }) + .await + .unwrap(); - tree.read_with(cx, |tree, _| { + worktree.read_with(cx, |tree, _| { let tracked = tree.entry_for_path("tracked-dir/file.txt").unwrap(); let ignored = tree.entry_for_path("ignored-dir/file.txt").unwrap(); assert!(!tracked.is_ignored); @@ -3349,7 +3352,7 @@ async fn test_conflicted_cherry_pick(cx: &mut TestAppContext) { .expect("Failed to get HEAD") .peel_to_commit() .expect("HEAD is not a commit"); - git_checkout("refs/heads/master", &repo); + git_checkout("refs/heads/main", &repo); std::fs::write(root_path.join("project/a.txt"), "b").unwrap(); git_add("a.txt", &repo); git_commit("improve letter", &repo); @@ -3479,7 +3482,9 @@ const MODIFIED: GitSummary = GitSummary { #[track_caller] fn git_init(path: &Path) -> git2::Repository { - git2::Repository::init(path).expect("Failed to initialize git repository") + let mut init_opts = RepositoryInitOptions::new(); + init_opts.initial_head("main"); + git2::Repository::init_opts(path, &init_opts).expect("Failed to initialize git repository") } #[track_caller]