git: Add notification to git clone (#41712)
Adds a simple notification when cloning a repo using the integrated git clone on Zed. Before this, the user had no feedback after starting the cloning action. Demo: https://github.com/user-attachments/assets/72fcdf1b-fc99-4fe5-8db2-7c30b170f12f Not sure about that icon I'm using for the animation, but that can be easily changed. Release Notes: - Added notification when cloning a repo from zed
This commit is contained in:
Generated
+1
@@ -96,6 +96,7 @@ dependencies = [
|
||||
"auto_update",
|
||||
"editor",
|
||||
"extension_host",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"language",
|
||||
|
||||
@@ -17,6 +17,7 @@ anyhow.workspace = true
|
||||
auto_update.workspace = true
|
||||
editor.workspace = true
|
||||
extension_host.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
|
||||
@@ -51,6 +51,7 @@ pub struct ActivityIndicator {
|
||||
project: Entity<Project>,
|
||||
auto_updater: Option<Entity<AutoUpdater>>,
|
||||
context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
fs_jobs: Vec<fs::JobInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -99,6 +100,27 @@ impl ActivityIndicator {
|
||||
})
|
||||
.detach();
|
||||
|
||||
let fs = project.read(cx).fs().clone();
|
||||
let mut job_events = fs.subscribe_to_jobs();
|
||||
cx.spawn(async move |this, cx| {
|
||||
while let Some(job_event) = job_events.next().await {
|
||||
this.update(cx, |this: &mut ActivityIndicator, cx| {
|
||||
match job_event {
|
||||
fs::JobEvent::Started { info } => {
|
||||
this.fs_jobs.retain(|j| j.id != info.id);
|
||||
this.fs_jobs.push(info);
|
||||
}
|
||||
fs::JobEvent::Completed { id } => {
|
||||
this.fs_jobs.retain(|j| j.id != id);
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
})?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.subscribe(
|
||||
&project.read(cx).lsp_store(),
|
||||
|activity_indicator, _, event, cx| {
|
||||
@@ -201,7 +223,8 @@ impl ActivityIndicator {
|
||||
statuses: Vec::new(),
|
||||
project: project.clone(),
|
||||
auto_updater,
|
||||
context_menu_handle: Default::default(),
|
||||
context_menu_handle: PopoverMenuHandle::default(),
|
||||
fs_jobs: Vec::new(),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -432,6 +455,23 @@ impl ActivityIndicator {
|
||||
});
|
||||
}
|
||||
|
||||
// Show any long-running fs command
|
||||
for fs_job in &self.fs_jobs {
|
||||
if Instant::now().duration_since(fs_job.start) >= GIT_OPERATION_DELAY {
|
||||
return Some(Content {
|
||||
icon: Some(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::Small)
|
||||
.with_rotate_animation(2)
|
||||
.into_any_element(),
|
||||
),
|
||||
message: fs_job.message.clone().into(),
|
||||
on_click: None,
|
||||
tooltip_message: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Show any language server installation info.
|
||||
let mut downloading = SmallVec::<[_; 3]>::new();
|
||||
let mut checking_for_update = SmallVec::<[_; 3]>::new();
|
||||
|
||||
+87
-3
@@ -4,6 +4,10 @@ mod mac_watcher;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub mod fs_watcher;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use ashpd::desktop::trash;
|
||||
@@ -12,6 +16,7 @@ use gpui::App;
|
||||
use gpui::BackgroundExecutor;
|
||||
use gpui::Global;
|
||||
use gpui::ReadGlobal as _;
|
||||
use gpui::SharedString;
|
||||
use std::borrow::Cow;
|
||||
use util::command::new_smol_command;
|
||||
|
||||
@@ -51,8 +56,7 @@ use git::{
|
||||
repository::{RepoPath, repo_path},
|
||||
status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus},
|
||||
};
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use parking_lot::Mutex;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use smol::io::AsyncReadExt;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -148,6 +152,7 @@ pub trait Fs: Send + Sync {
|
||||
async fn git_clone(&self, repo_url: &str, abs_work_directory: &Path) -> Result<()>;
|
||||
fn is_fake(&self) -> bool;
|
||||
async fn is_case_sensitive(&self) -> Result<bool>;
|
||||
fn subscribe_to_jobs(&self) -> JobEventReceiver;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn as_fake(&self) -> Arc<FakeFs> {
|
||||
@@ -215,6 +220,55 @@ pub struct Metadata {
|
||||
#[serde(transparent)]
|
||||
pub struct MTime(SystemTime);
|
||||
|
||||
pub type JobId = usize;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct JobInfo {
|
||||
pub start: Instant,
|
||||
pub message: SharedString,
|
||||
pub id: JobId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum JobEvent {
|
||||
Started { info: JobInfo },
|
||||
Completed { id: JobId },
|
||||
}
|
||||
|
||||
pub type JobEventSender = futures::channel::mpsc::UnboundedSender<JobEvent>;
|
||||
pub type JobEventReceiver = futures::channel::mpsc::UnboundedReceiver<JobEvent>;
|
||||
|
||||
struct JobTracker {
|
||||
id: JobId,
|
||||
subscribers: Arc<Mutex<Vec<JobEventSender>>>,
|
||||
}
|
||||
|
||||
impl JobTracker {
|
||||
fn new(info: JobInfo, subscribers: Arc<Mutex<Vec<JobEventSender>>>) -> Self {
|
||||
let id = info.id;
|
||||
{
|
||||
let mut subs = subscribers.lock();
|
||||
subs.retain(|sender| {
|
||||
sender
|
||||
.unbounded_send(JobEvent::Started { info: info.clone() })
|
||||
.is_ok()
|
||||
});
|
||||
}
|
||||
Self { id, subscribers }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for JobTracker {
|
||||
fn drop(&mut self) {
|
||||
let mut subs = self.subscribers.lock();
|
||||
subs.retain(|sender| {
|
||||
sender
|
||||
.unbounded_send(JobEvent::Completed { id: self.id })
|
||||
.is_ok()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl MTime {
|
||||
/// Conversion intended for persistence and testing.
|
||||
pub fn from_seconds_and_nanos(secs: u64, nanos: u32) -> Self {
|
||||
@@ -257,6 +311,8 @@ impl From<MTime> for proto::Timestamp {
|
||||
pub struct RealFs {
|
||||
bundled_git_binary_path: Option<PathBuf>,
|
||||
executor: BackgroundExecutor,
|
||||
next_job_id: Arc<AtomicUsize>,
|
||||
job_event_subscribers: Arc<Mutex<Vec<JobEventSender>>>,
|
||||
}
|
||||
|
||||
pub trait FileHandle: Send + Sync + std::fmt::Debug {
|
||||
@@ -361,6 +417,8 @@ impl RealFs {
|
||||
Self {
|
||||
bundled_git_binary_path: git_binary_path,
|
||||
executor,
|
||||
next_job_id: Arc::new(AtomicUsize::new(0)),
|
||||
job_event_subscribers: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -862,7 +920,6 @@ impl Fs for RealFs {
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
|
||||
Arc<dyn Watcher>,
|
||||
) {
|
||||
use parking_lot::Mutex;
|
||||
use util::{ResultExt as _, paths::SanitizedPath};
|
||||
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
@@ -959,6 +1016,15 @@ impl Fs for RealFs {
|
||||
}
|
||||
|
||||
async fn git_clone(&self, repo_url: &str, abs_work_directory: &Path) -> Result<()> {
|
||||
let job_id = self.next_job_id.fetch_add(1, Ordering::SeqCst);
|
||||
let job_info = JobInfo {
|
||||
id: job_id,
|
||||
start: Instant::now(),
|
||||
message: SharedString::from(format!("Cloning {}", repo_url)),
|
||||
};
|
||||
|
||||
let _job_tracker = JobTracker::new(job_info, self.job_event_subscribers.clone());
|
||||
|
||||
let output = new_smol_command("git")
|
||||
.current_dir(abs_work_directory)
|
||||
.args(&["clone", repo_url])
|
||||
@@ -979,6 +1045,12 @@ impl Fs for RealFs {
|
||||
false
|
||||
}
|
||||
|
||||
fn subscribe_to_jobs(&self) -> JobEventReceiver {
|
||||
let (sender, receiver) = futures::channel::mpsc::unbounded();
|
||||
self.job_event_subscribers.lock().push(sender);
|
||||
receiver
|
||||
}
|
||||
|
||||
/// Checks whether the file system is case sensitive by attempting to create two files
|
||||
/// that have the same name except for the casing.
|
||||
///
|
||||
@@ -1049,6 +1121,7 @@ struct FakeFsState {
|
||||
read_dir_call_count: usize,
|
||||
path_write_counts: std::collections::HashMap<PathBuf, usize>,
|
||||
moves: std::collections::HashMap<u64, PathBuf>,
|
||||
job_event_subscribers: Arc<Mutex<Vec<JobEventSender>>>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -1333,6 +1406,7 @@ impl FakeFs {
|
||||
metadata_call_count: 0,
|
||||
path_write_counts: Default::default(),
|
||||
moves: Default::default(),
|
||||
job_event_subscribers: Arc::new(Mutex::new(Vec::new())),
|
||||
})),
|
||||
});
|
||||
|
||||
@@ -2587,6 +2661,12 @@ impl Fs for FakeFs {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn subscribe_to_jobs(&self) -> JobEventReceiver {
|
||||
let (sender, receiver) = futures::channel::mpsc::unbounded();
|
||||
self.state.lock().job_event_subscribers.lock().push(sender);
|
||||
receiver
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn as_fake(&self) -> Arc<FakeFs> {
|
||||
self.this.upgrade().unwrap()
|
||||
@@ -3201,6 +3281,8 @@ mod tests {
|
||||
let fs = RealFs {
|
||||
bundled_git_binary_path: None,
|
||||
executor,
|
||||
next_job_id: Arc::new(AtomicUsize::new(0)),
|
||||
job_event_subscribers: Arc::new(Mutex::new(Vec::new())),
|
||||
};
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_to_be_replaced = temp_dir.path().join("file.txt");
|
||||
@@ -3219,6 +3301,8 @@ mod tests {
|
||||
let fs = RealFs {
|
||||
bundled_git_binary_path: None,
|
||||
executor,
|
||||
next_job_id: Arc::new(AtomicUsize::new(0)),
|
||||
job_event_subscribers: Arc::new(Mutex::new(Vec::new())),
|
||||
};
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_to_be_replaced = temp_dir.path().join("file.txt");
|
||||
|
||||
Reference in New Issue
Block a user