diff --git a/crates/assistant/src/slash_command/file_command.rs b/crates/assistant/src/slash_command/file_command.rs index 0a1794cae1..1d0fa2bf3e 100644 --- a/crates/assistant/src/slash_command/file_command.rs +++ b/crates/assistant/src/slash_command/file_command.rs @@ -4,6 +4,7 @@ use assistant_slash_command::{ SlashCommandOutput, SlashCommandOutputSection, SlashCommandResult, }; use futures::channel::mpsc; +use futures::Stream; use fuzzy::PathMatch; use gpui::{AppContext, Model, Task, View, WeakView}; use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate}; @@ -196,7 +197,12 @@ impl SlashCommand for FileSlashCommand { return Task::ready(Err(anyhow!("missing path"))); }; - collect_files(workspace.read(cx).project().clone(), arguments, cx) + Task::ready(Ok(collect_files( + workspace.read(cx).project().clone(), + arguments, + cx, + ) + .boxed())) } } @@ -204,7 +210,7 @@ fn collect_files( project: Model, glob_inputs: &[String], cx: &mut AppContext, -) -> Task { +) -> impl Stream> { let Ok(matchers) = glob_inputs .into_iter() .map(|glob_input| { @@ -213,7 +219,7 @@ fn collect_files( }) .collect::>>() else { - return Task::ready(Err(anyhow!("invalid path"))); + return futures::stream::once(async { Err(anyhow!("invalid path")) }).boxed(); }; let project_handle = project.downgrade(); @@ -357,8 +363,12 @@ fn collect_files( events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?; } } - Ok(events_rx.boxed()) + + anyhow::Ok(()) }) + .detach_and_log_err(cx); + + events_rx.boxed() } pub fn codeblock_fence_for_path( @@ -550,6 +560,7 @@ mod test { use project::Project; use serde_json::json; use settings::SettingsStore; + use smol::stream::StreamExt; use crate::slash_command::file_command::collect_files; @@ -590,11 +601,9 @@ mod test { let project = Project::test(fs, ["/root".as_ref()], cx).await; - let result_1 = cx - .update(|cx| collect_files(project.clone(), &["root/dir".to_string()], cx)) - .await - .unwrap(); - let result_1 = SlashCommandOutput::from_event_stream(result_1) + let result_1 = + cx.update(|cx| collect_files(project.clone(), &["root/dir".to_string()], cx)); + let result_1 = SlashCommandOutput::from_event_stream(result_1.boxed()) .await .unwrap(); @@ -602,20 +611,16 @@ mod test { // 4 files + 2 directories assert_eq!(result_1.sections.len(), 6); - let result_2 = cx - .update(|cx| collect_files(project.clone(), &["root/dir/".to_string()], cx)) - .await - .unwrap(); - let result_2 = SlashCommandOutput::from_event_stream(result_2) + let result_2 = + cx.update(|cx| collect_files(project.clone(), &["root/dir/".to_string()], cx)); + let result_2 = SlashCommandOutput::from_event_stream(result_2.boxed()) .await .unwrap(); assert_eq!(result_1, result_2); - let result = cx - .update(|cx| collect_files(project.clone(), &["root/dir*".to_string()], cx)) - .await - .unwrap(); + let result = + cx.update(|cx| collect_files(project.clone(), &["root/dir*".to_string()], cx).boxed()); let result = SlashCommandOutput::from_event_stream(result).await.unwrap(); assert!(result.text.starts_with("root/dir")); @@ -659,11 +664,11 @@ mod test { let project = Project::test(fs, ["/zed".as_ref()], cx).await; - let result = cx - .update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx)) + let result = + cx.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx)); + let result = SlashCommandOutput::from_event_stream(result.boxed()) .await .unwrap(); - let result = SlashCommandOutput::from_event_stream(result).await.unwrap(); // Sanity check assert!(result.text.starts_with("zed/assets/themes\n")); @@ -721,11 +726,11 @@ mod test { let project = Project::test(fs, ["/zed".as_ref()], cx).await; - let result = cx - .update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx)) + let result = + cx.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx)); + let result = SlashCommandOutput::from_event_stream(result.boxed()) .await .unwrap(); - let result = SlashCommandOutput::from_event_stream(result).await.unwrap(); assert!(result.text.starts_with("zed/assets/themes\n")); assert_eq!(result.sections[0].label, "zed/assets/themes/LICENSE"); diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index a45eb3a05b..61154cb504 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -432,10 +432,11 @@ impl AutoUpdater { cx.notify(); } - pub async fn get_latest_remote_server_release( + pub async fn download_remote_server_release( os: &str, arch: &str, - mut release_channel: ReleaseChannel, + release_channel: ReleaseChannel, + version: Option, cx: &mut AsyncAppContext, ) -> Result { let this = cx.update(|cx| { @@ -445,15 +446,12 @@ impl AutoUpdater { .ok_or_else(|| anyhow!("auto-update not initialized")) })??; - if release_channel == ReleaseChannel::Dev { - release_channel = ReleaseChannel::Nightly; - } - - let release = Self::get_latest_release( + let release = Self::get_release( &this, "zed-remote-server", os, arch, + version, Some(release_channel), cx, ) @@ -468,17 +466,21 @@ impl AutoUpdater { let client = this.read_with(cx, |this, _| this.http_client.clone())?; if smol::fs::metadata(&version_path).await.is_err() { - log::info!("downloading zed-remote-server {os} {arch}"); + log::info!( + "downloading zed-remote-server {os} {arch} version {}", + release.version + ); download_remote_server_binary(&version_path, release, client, cx).await?; } Ok(version_path) } - pub async fn get_latest_remote_server_release_url( + pub async fn get_remote_server_release_url( os: &str, arch: &str, - mut release_channel: ReleaseChannel, + release_channel: ReleaseChannel, + version: Option, cx: &mut AsyncAppContext, ) -> Result<(String, String)> { let this = cx.update(|cx| { @@ -488,15 +490,12 @@ impl AutoUpdater { .ok_or_else(|| anyhow!("auto-update not initialized")) })??; - if release_channel == ReleaseChannel::Dev { - release_channel = ReleaseChannel::Nightly; - } - - let release = Self::get_latest_release( + let release = Self::get_release( &this, "zed-remote-server", os, arch, + version, Some(release_channel), cx, ) @@ -508,6 +507,56 @@ impl AutoUpdater { Ok((release.url, body)) } + async fn get_release( + this: &Model, + asset: &str, + os: &str, + arch: &str, + version: Option, + release_channel: Option, + cx: &mut AsyncAppContext, + ) -> Result { + let client = this.read_with(cx, |this, _| this.http_client.clone())?; + + if let Some(version) = version { + let channel = release_channel.map(|c| c.dev_name()).unwrap_or("stable"); + + let url = format!("/api/releases/{channel}/{version}/{asset}-{os}-{arch}.gz?update=1",); + + Ok(JsonRelease { + version: version.to_string(), + url: client.build_url(&url), + }) + } else { + let mut url_string = client.build_url(&format!( + "/api/releases/latest?asset={}&os={}&arch={}", + asset, os, arch + )); + if let Some(param) = release_channel.and_then(|c| c.release_query_param()) { + url_string += "&"; + url_string += param; + } + + let mut response = client.get(&url_string, Default::default(), true).await?; + let mut body = Vec::new(); + response.body_mut().read_to_end(&mut body).await?; + + if !response.status().is_success() { + return Err(anyhow!( + "failed to fetch release: {:?}", + String::from_utf8_lossy(&body), + )); + } + + serde_json::from_slice(body.as_slice()).with_context(|| { + format!( + "error deserializing release {:?}", + String::from_utf8_lossy(&body), + ) + }) + } + } + async fn get_latest_release( this: &Model, asset: &str, @@ -516,38 +565,7 @@ impl AutoUpdater { release_channel: Option, cx: &mut AsyncAppContext, ) -> Result { - let client = this.read_with(cx, |this, _| this.http_client.clone())?; - let mut url_string = client.build_url(&format!( - "/api/releases/latest?asset={}&os={}&arch={}", - asset, os, arch - )); - if let Some(param) = release_channel.and_then(|c| c.release_query_param()) { - url_string += "&"; - url_string += param; - } - - let mut response = client.get(&url_string, Default::default(), true).await?; - - let mut body = Vec::new(); - response - .body_mut() - .read_to_end(&mut body) - .await - .context("error reading release")?; - - if !response.status().is_success() { - Err(anyhow!( - "failed to fetch release: {:?}", - String::from_utf8_lossy(&body), - ))?; - } - - serde_json::from_slice(body.as_slice()).with_context(|| { - format!( - "error deserializing release {:?}", - String::from_utf8_lossy(&body), - ) - }) + Self::get_release(this, asset, os, arch, None, release_channel, cx).await } async fn update(this: Model, mut cx: AsyncAppContext) -> Result<()> { diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 0718c4f983..47e4c91dbd 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -517,17 +517,31 @@ impl SshClientDelegate { } } + // For nightly channel, always get latest + let current_version = if release_channel == ReleaseChannel::Nightly { + None + } else { + Some(version) + }; + + self.update_status( + Some(&format!("Checking remote server release {}", version)), + cx, + ); + if download_binary_on_host { - let (request_url, request_body) = AutoUpdater::get_latest_remote_server_release_url( + let (request_url, request_body) = AutoUpdater::get_remote_server_release_url( platform.os, platform.arch, release_channel, + current_version, cx, ) .await .map_err(|e| { anyhow!( - "Failed to get remote server binary download url (os: {}, arch: {}): {}", + "Failed to get remote server binary download url (version: {}, os: {}, arch: {}): {}", + version, platform.os, platform.arch, e @@ -542,17 +556,18 @@ impl SshClientDelegate { version, )) } else { - self.update_status(Some("Checking for latest version of remote server"), cx); - let binary_path = AutoUpdater::get_latest_remote_server_release( + let binary_path = AutoUpdater::download_remote_server_release( platform.os, platform.arch, release_channel, + current_version, cx, ) .await .map_err(|e| { anyhow!( - "Failed to download remote server binary (os: {}, arch: {}): {}", + "Failed to download remote server binary (version: {}, os: {}, arch: {}): {}", + version, platform.os, platform.arch, e diff --git a/crates/remote/src/ssh_session.rs b/crates/remote/src/ssh_session.rs index 8e0c345f74..656560f0b6 100644 --- a/crates/remote/src/ssh_session.rs +++ b/crates/remote/src/ssh_session.rs @@ -1707,21 +1707,32 @@ impl SshRemoteConnection { let (binary, version) = delegate.get_server_binary(platform, cx).await??; - let mut server_binary_exists = false; - if !server_binary_exists && cfg!(not(debug_assertions)) { + let mut remote_version = None; + if cfg!(not(debug_assertions)) { if let Ok(installed_version) = run_cmd(self.socket.ssh_command(dst_path).arg("version")).await { - if installed_version.trim() == version.to_string() { - server_binary_exists = true; + if let Ok(version) = installed_version.trim().parse::() { + remote_version = Some(version); + } else { + log::warn!("failed to parse version of remote server: {installed_version:?}",); } - log::info!("checked remote server binary for version. latest version: {}. remote server version: {}", version.to_string(), installed_version.trim()); } - } - if server_binary_exists { - log::info!("remote development server already present",); - return Ok(()); + if let Some(remote_version) = remote_version { + if remote_version == version { + log::info!("remote development server present and matching client version"); + return Ok(()); + } else if remote_version > version { + let error = anyhow!("The version of the remote server ({}) is newer than the Zed version ({}). Please update Zed.", remote_version, version); + return Err(error); + } else { + log::info!( + "remote development server has older version: {}. updating...", + remote_version + ); + } + } } match binary { diff --git a/script/bundle-mac b/script/bundle-mac index 5305bb76e3..b7e8ad23f7 100755 --- a/script/bundle-mac +++ b/script/bundle-mac @@ -90,7 +90,7 @@ function build() { channel=$(