Detailed changes
@@ -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);
})),
@@ -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<Self>| {
- 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<Self>| {
+ 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<Project>, cx: &mut Context<Self>) {
+ fn handle_project_shared(&mut self, _: Entity<Project>, cx: &mut Context<Self>) {
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>,
+ project: Entity<Project>,
event: &project::Event,
cx: &mut Context<Self>,
) {
match event {
+ project::Event::RemoteIdChanged(_) => {
+ self.handle_project_shared(project, cx);
+ }
project::Event::Reshared => {
self.advertise_contexts(cx);
}
@@ -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);
@@ -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),
_ => {}
@@ -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<WorktreeId, EnvironmentErrorMessage>,
}
+pub enum ProjectEnvironmentEvent {
+ ErrorsUpdated,
+}
+
+impl EventEmitter<ProjectEnvironmentEvent> for ProjectEnvironment {}
+
impl ProjectEnvironment {
pub fn new(
worktree_store: &Entity<WorktreeStore>,
@@ -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>,
+ ) {
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();
}
@@ -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<Self>,
- ) -> 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(
@@ -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<Self>, 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>) {
+ 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<Self>) -> 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<Self>) {
@@ -2509,15 +2504,11 @@ impl Project {
}
}
- fn on_worktree_added(&mut self, worktree: &Entity<Worktree>, cx: &mut Context<Self>) {
- {
- 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<Worktree>, _: &mut Context<Self>) {
+ 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<Self>) {
@@ -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<proto::WorktreeMetadata>,
cx: &mut Context<Project>,
) -> Result<()> {
- cx.notify();
self.worktree_store.update(cx, |worktree_store, cx| {
worktree_store.set_worktrees_from_proto(worktrees, self.replica_id(), cx)
})
@@ -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()));
@@ -852,6 +852,7 @@ impl<T: Item> ItemHandle for Entity<T> {
.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();
@@ -879,8 +879,6 @@ impl Workspace {
window: &mut Window,
cx: &mut Context<Self>,
) -> 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(_) => {
@@ -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<Worktree> {
.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<Worktree> {
.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;
@@ -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]