diff --git a/Cargo.lock b/Cargo.lock index 2c2ec4595c294c87ed74b25a69300b6c86429b46..01dcaea9ee4ca398c9db6a69ba824b2c2bbbdfce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -553,6 +553,25 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "auto_update" +version = "0.1.0" +dependencies = [ + "anyhow", + "client", + "gpui", + "lazy_static", + "log", + "serde", + "serde_json", + "settings", + "smol", + "surf", + "tempdir", + "theme", + "workspace", +] + [[package]] name = "autocfg" version = "0.1.7" @@ -6382,13 +6401,14 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "zed" -version = "0.28.1" +version = "0.29.0" dependencies = [ "anyhow", "assets", "async-compression", "async-recursion", "async-trait", + "auto_update", "breadcrumbs", "chat_panel", "cli", diff --git a/assets/themes/dark.json b/assets/themes/dark.json index 42785b123c47244f7567d3ff33ed3fb817c989ee..5003d23d4dfc91f4ab3f19f27f73a119b4a6a669 100644 --- a/assets/themes/dark.json +++ b/assets/themes/dark.json @@ -239,6 +239,16 @@ "family": "Zed Sans", "color": "#808080", "size": 14 + }, + "auto_update_progress_message": { + "family": "Zed Sans", + "color": "#808080", + "size": 14 + }, + "auto_update_done_message": { + "family": "Zed Sans", + "color": "#808080", + "size": 14 } }, "titlebar": { diff --git a/assets/themes/light.json b/assets/themes/light.json index 9d810765e1bf799fe8f9be7c352d27990e17126e..5a370f4aa2b83b348f021beca379d499f01054f3 100644 --- a/assets/themes/light.json +++ b/assets/themes/light.json @@ -239,6 +239,16 @@ "family": "Zed Sans", "color": "#636363", "size": 14 + }, + "auto_update_progress_message": { + "family": "Zed Sans", + "color": "#636363", + "size": 14 + }, + "auto_update_done_message": { + "family": "Zed Sans", + "color": "#636363", + "size": 14 } }, "titlebar": { diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..dcb08186b8bfa1c595f23525d551740ce8c46b0c --- /dev/null +++ b/crates/auto_update/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "auto_update" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/auto_update.rs" +doctest = false + +[dependencies] +client = { path = "../client" } +gpui = { path = "../gpui" } +settings = { path = "../settings" } +theme = { path = "../theme" } +workspace = { path = "../workspace" } +anyhow = "1.0.38" +lazy_static = "1.4" +log = "0.4" +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1.0.64", features = ["preserve_order"] } +smol = "1.2.5" +surf = "2.2" +tempdir = "0.3.7" diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs new file mode 100644 index 0000000000000000000000000000000000000000..a40788f4c41b402154a3fe8ffec0b2ab42d846ea --- /dev/null +++ b/crates/auto_update/src/auto_update.rs @@ -0,0 +1,304 @@ +use anyhow::{anyhow, Result}; +use client::http::{self, HttpClient}; +use gpui::{ + actions, + elements::{Empty, MouseEventHandler, Text}, + platform::AppVersion, + AsyncAppContext, Element, Entity, ModelContext, ModelHandle, MutableAppContext, Task, View, + ViewContext, +}; +use lazy_static::lazy_static; +use serde::Deserialize; +use settings::Settings; +use smol::{fs::File, io::AsyncReadExt, process::Command}; +use std::{env, ffi::OsString, path::PathBuf, sync::Arc, time::Duration}; +use surf::Request; +use workspace::{ItemHandle, StatusItemView}; + +const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60); +const ACCESS_TOKEN: &'static str = "618033988749894"; + +lazy_static! { + pub static ref ZED_APP_VERSION: Option = env::var("ZED_APP_VERSION") + .ok() + .and_then(|v| v.parse().ok()); + pub static ref ZED_APP_PATH: Option = env::var("ZED_APP_PATH").ok().map(PathBuf::from); +} + +actions!(auto_update, [Check, DismissErrorMessage]); + +#[derive(Clone, PartialEq, Eq)] +pub enum AutoUpdateStatus { + Idle, + Checking, + Downloading, + Installing, + Updated, + Errored, +} + +pub struct AutoUpdater { + status: AutoUpdateStatus, + current_version: AppVersion, + http_client: Arc, + pending_poll: Option>, + server_url: String, +} + +pub struct AutoUpdateIndicator { + updater: Option>, +} + +#[derive(Deserialize)] +struct JsonRelease { + version: String, + url: http::Url, +} + +impl Entity for AutoUpdater { + type Event = (); +} + +pub fn init(http_client: Arc, server_url: String, cx: &mut MutableAppContext) { + if let Some(version) = ZED_APP_VERSION.clone().or(cx.platform().app_version().ok()) { + let auto_updater = cx.add_model(|cx| { + let updater = AutoUpdater::new(version, http_client, server_url); + updater.start_polling(cx).detach(); + updater + }); + cx.set_global(Some(auto_updater)); + cx.add_global_action(|_: &Check, cx| { + if let Some(updater) = AutoUpdater::get(cx) { + updater.update(cx, |updater, cx| updater.poll(cx)); + } + }); + cx.add_action(AutoUpdateIndicator::dismiss_error_message); + } +} + +impl AutoUpdater { + fn get(cx: &mut MutableAppContext) -> Option> { + cx.default_global::>>().clone() + } + + fn new( + current_version: AppVersion, + http_client: Arc, + server_url: String, + ) -> Self { + Self { + status: AutoUpdateStatus::Idle, + current_version, + http_client, + server_url, + pending_poll: None, + } + } + + pub fn start_polling(&self, cx: &mut ModelContext) -> Task<()> { + cx.spawn(|this, mut cx| async move { + loop { + this.update(&mut cx, |this, cx| this.poll(cx)); + cx.background().timer(POLL_INTERVAL).await; + } + }) + } + + pub fn poll(&mut self, cx: &mut ModelContext) { + if self.pending_poll.is_some() || self.status == AutoUpdateStatus::Updated { + return; + } + + self.status = AutoUpdateStatus::Checking; + cx.notify(); + + self.pending_poll = Some(cx.spawn(|this, mut cx| async move { + let result = Self::update(this.clone(), cx.clone()).await; + this.update(&mut cx, |this, cx| { + this.pending_poll = None; + if let Err(error) = result { + log::error!("auto-update failed: error:{:?}", error); + this.status = AutoUpdateStatus::Errored; + cx.notify(); + } + }); + })); + } + + async fn update(this: ModelHandle, mut cx: AsyncAppContext) -> Result<()> { + let (client, server_url, current_version) = this.read_with(&cx, |this, _| { + ( + this.http_client.clone(), + this.server_url.clone(), + this.current_version, + ) + }); + let mut response = client + .send(Request::new( + http::Method::Get, + http::Url::parse(&format!( + "{server_url}/api/releases/latest?token={ACCESS_TOKEN}&asset=Zed.dmg" + ))?, + )) + .await?; + let release = response + .body_json::() + .await + .map_err(|err| anyhow!("error deserializing release {:?}", err))?; + let latest_version = release.version.parse::()?; + if latest_version <= current_version { + this.update(&mut cx, |this, cx| { + this.status = AutoUpdateStatus::Idle; + cx.notify(); + }); + return Ok(()); + } + + this.update(&mut cx, |this, cx| { + this.status = AutoUpdateStatus::Downloading; + cx.notify(); + }); + + let temp_dir = tempdir::TempDir::new("zed-auto-update")?; + let dmg_path = temp_dir.path().join("Zed.dmg"); + let mount_path = temp_dir.path().join("Zed"); + let mut mounted_app_path: OsString = mount_path.join("Zed.app").into(); + mounted_app_path.push("/"); + let running_app_path = ZED_APP_PATH + .clone() + .map_or_else(|| cx.platform().app_path(), Ok)?; + + let mut dmg_file = File::create(&dmg_path).await?; + let response = client + .send(Request::new(http::Method::Get, release.url)) + .await?; + smol::io::copy(response.bytes(), &mut dmg_file).await?; + log::info!("downloaded update. path:{:?}", dmg_path); + + this.update(&mut cx, |this, cx| { + this.status = AutoUpdateStatus::Installing; + cx.notify(); + }); + + let output = Command::new("hdiutil") + .args(&["attach", "-nobrowse"]) + .arg(&dmg_path) + .arg("-mountroot") + .arg(&temp_dir.path()) + .output() + .await?; + if !output.status.success() { + Err(anyhow!( + "failed to mount: {:?}", + String::from_utf8_lossy(&output.stderr) + ))?; + } + + let output = Command::new("rsync") + .args(&["-av", "--delete"]) + .arg(&mounted_app_path) + .arg(&running_app_path) + .output() + .await?; + if !output.status.success() { + Err(anyhow!( + "failed to copy app: {:?}", + String::from_utf8_lossy(&output.stderr) + ))?; + } + + let output = Command::new("hdiutil") + .args(&["detach"]) + .arg(&mount_path) + .output() + .await?; + if !output.status.success() { + Err(anyhow!( + "failed to unmount: {:?}", + String::from_utf8_lossy(&output.stderr) + ))?; + } + + this.update(&mut cx, |this, cx| { + this.status = AutoUpdateStatus::Updated; + cx.notify(); + }); + Ok(()) + } +} + +impl Entity for AutoUpdateIndicator { + type Event = (); +} + +impl View for AutoUpdateIndicator { + fn ui_name() -> &'static str { + "AutoUpdateIndicator" + } + + fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox { + if let Some(updater) = &self.updater { + let theme = &cx.global::().theme.workspace.status_bar; + match &updater.read(cx).status { + AutoUpdateStatus::Checking => Text::new( + "Checking for updates…".to_string(), + theme.auto_update_progress_message.clone(), + ) + .boxed(), + AutoUpdateStatus::Downloading => Text::new( + "Downloading update…".to_string(), + theme.auto_update_progress_message.clone(), + ) + .boxed(), + AutoUpdateStatus::Installing => Text::new( + "Installing update…".to_string(), + theme.auto_update_progress_message.clone(), + ) + .boxed(), + AutoUpdateStatus::Updated => Text::new( + "Restart to update Zed".to_string(), + theme.auto_update_done_message.clone(), + ) + .boxed(), + AutoUpdateStatus::Errored => { + MouseEventHandler::new::(0, cx, |_, cx| { + let theme = &cx.global::().theme.workspace.status_bar; + Text::new( + "Auto update failed".to_string(), + theme.auto_update_done_message.clone(), + ) + .boxed() + }) + .on_click(|cx| cx.dispatch_action(DismissErrorMessage)) + .boxed() + } + AutoUpdateStatus::Idle => Empty::new().boxed(), + } + } else { + Empty::new().boxed() + } + } +} + +impl StatusItemView for AutoUpdateIndicator { + fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext) {} +} + +impl AutoUpdateIndicator { + pub fn new(cx: &mut ViewContext) -> Self { + let updater = AutoUpdater::get(cx); + if let Some(updater) = &updater { + cx.observe(updater, |_, _, cx| cx.notify()).detach(); + } + Self { updater } + } + + fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext) { + if let Some(updater) = &self.updater { + updater.update(cx, |updater, cx| { + updater.status = AutoUpdateStatus::Idle; + cx.notify(); + }); + } + } +} diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 2a563914c923511e9228d4b0aa56295905285b35..93cbba48a190f1df50fb97c8b7be78103c40626a 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -43,7 +43,7 @@ pub use rpc::*; pub use user::*; lazy_static! { - static ref ZED_SERVER_URL: String = + pub static ref ZED_SERVER_URL: String = std::env::var("ZED_SERVER_URL").unwrap_or("https://zed.dev".to_string()); pub static ref IMPERSONATE_LOGIN: Option = std::env::var("ZED_IMPERSONATE") .ok() diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 1e7ab84d50a19c5b7064febf2f9836671a2c93d7..7e67d8b75292475606e41cb2b1f1c63ef8b22a3d 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -17,7 +17,7 @@ use crate::{ text_layout::{LineLayout, RunStyle}, Action, ClipboardItem, Menu, Scene, }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use async_task::Runnable; pub use event::{Event, NavigationDirection}; use postage::oneshot; @@ -25,6 +25,7 @@ use std::{ any::Any, path::{Path, PathBuf}, rc::Rc, + str::FromStr, sync::Arc, }; use time::UtcOffset; @@ -54,7 +55,10 @@ pub trait Platform: Send + Sync { fn set_cursor_style(&self, style: CursorStyle); fn local_timezone(&self) -> UtcOffset; + fn path_for_auxiliary_executable(&self, name: &str) -> Result; + fn app_path(&self) -> Result; + fn app_version(&self) -> Result; } pub(crate) trait ForegroundPlatform { @@ -128,6 +132,38 @@ pub enum CursorStyle { PointingHand, } +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct AppVersion { + major: usize, + minor: usize, + patch: usize, +} + +impl FromStr for AppVersion { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let mut components = s.trim().split('.'); + let major = components + .next() + .ok_or_else(|| anyhow!("missing major version number"))? + .parse()?; + let minor = components + .next() + .ok_or_else(|| anyhow!("missing minor version number"))? + .parse()?; + let patch = components + .next() + .ok_or_else(|| anyhow!("missing patch version number"))? + .parse()?; + Ok(Self { + major, + minor, + patch, + }) + } +} + #[derive(Copy, Clone, Debug)] pub enum RasterizationOptions { Alpha, diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index e637ebfa3e44395fce61873282856785b7782660..5554a96dbb209d23f14c68b0bfd5fb9626d3aa3d 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -14,7 +14,9 @@ use cocoa::{ NSPasteboardTypeString, NSSavePanel, NSWindow, }, base::{id, nil, selector, YES}, - foundation::{NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSString, NSURL}, + foundation::{ + NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSString, NSUInteger, NSURL, + }, }; use core_foundation::{ base::{CFType, CFTypeRef, OSStatus, TCFType as _}, @@ -46,6 +48,9 @@ use std::{ }; use time::UtcOffset; +#[allow(non_upper_case_globals)] +const NSUTF8StringEncoding: NSUInteger = 4; + const MAC_PLATFORM_IVAR: &'static str = "platform"; static mut APP_CLASS: *const Class = ptr::null(); static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); @@ -607,6 +612,39 @@ impl platform::Platform for MacPlatform { } } } + + fn app_path(&self) -> Result { + unsafe { + let bundle: id = NSBundle::mainBundle(); + if bundle.is_null() { + Err(anyhow!("app is not running inside a bundle")) + } else { + Ok(path_from_objc(msg_send![bundle, bundlePath])) + } + } + } + + fn app_version(&self) -> Result { + unsafe { + let bundle: id = NSBundle::mainBundle(); + if bundle.is_null() { + Err(anyhow!("app is not running inside a bundle")) + } else { + let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")]; + let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; + let bytes = version.UTF8String() as *const u8; + let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap(); + version.parse() + } + } + } +} + +unsafe fn path_from_objc(path: id) -> PathBuf { + let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; + let bytes = path.UTF8String() as *const u8; + let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap(); + PathBuf::from(path) } unsafe fn get_foreground_platform(object: &mut Object) -> &MacForegroundPlatform { diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 85ef26cce35abfee39befa1ac5360474a487d065..6804b39f7cea3147cf441a70f9a4807a25283303 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -1,4 +1,4 @@ -use super::{CursorStyle, WindowBounds}; +use super::{AppVersion, CursorStyle, WindowBounds}; use crate::{ geometry::vector::{vec2f, Vector2F}, Action, ClipboardItem, @@ -164,6 +164,18 @@ impl super::Platform for Platform { fn path_for_auxiliary_executable(&self, _name: &str) -> Result { Err(anyhow!("app not running inside a bundle")) } + + fn app_path(&self) -> Result { + Err(anyhow!("app not running inside a bundle")) + } + + fn app_version(&self) -> Result { + Ok(AppVersion { + major: 1, + minor: 0, + patch: 0, + }) + } } impl Window { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index c831738423af54b16dada92589838667dc819938..dd6a7a79c71326e3a3772d331bdc95394145ea72 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -153,6 +153,8 @@ pub struct StatusBar { pub cursor_position: TextStyle, pub diagnostic_message: TextStyle, pub lsp_message: TextStyle, + pub auto_update_progress_message: TextStyle, + pub auto_update_done_message: TextStyle, } #[derive(Deserialize, Default)] diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 62a79ff706474d97c86bc3b31a87060ce65be4d4..5be00513a5d07c68bfc4ab8fc683859740970ee1 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -182,12 +182,9 @@ pub struct AppState { pub user_store: ModelHandle, pub fs: Arc, pub channel_list: ModelHandle, - pub build_window_options: &'static dyn Fn() -> WindowOptions<'static>, - pub build_workspace: &'static dyn Fn( - ModelHandle, - &Arc, - &mut ViewContext, - ) -> Workspace, + pub build_window_options: fn() -> WindowOptions<'static>, + pub build_workspace: + fn(ModelHandle, &Arc, &mut ViewContext) -> Workspace, } pub trait Item: View { diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d07a030d34b33ccc9def3815ceebb6ce9bdb047e..0d4d43c4148781cbf065c0fb1f48ae04d3450199 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -14,22 +14,9 @@ doctest = false name = "Zed" path = "src/main.rs" -[features] -test-support = [ - "text/test-support", - "client/test-support", - "editor/test-support", - "gpui/test-support", - "language/test-support", - "lsp/test-support", - "project/test-support", - "rpc/test-support", - "tempdir", - "workspace/test-support", -] - [dependencies] assets = { path = "../assets" } +auto_update = { path = "../auto_update" } breadcrumbs = { path = "../breadcrumbs" } chat_panel = { path = "../chat_panel" } cli = { path = "../cli" } @@ -93,7 +80,7 @@ simplelog = "0.9" smallvec = { version = "1.6", features = ["union"] } smol = "1.2.5" surf = "2.2" -tempdir = { version = "0.3.7", optional = true } +tempdir = { version = "0.3.7" } thiserror = "1.0.29" tiny_http = "0.8" toml = "0.5" @@ -119,7 +106,6 @@ util = { path = "../util", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] } env_logger = "0.8" serde_json = { version = "1.0.64", features = ["preserve_order"] } -tempdir = { version = "0.3.7" } unindent = "0.1.7" [package.metadata.bundle] diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 5fbf634262f1124ab47c80aed654d6c9847618ee..371cb5a5062babd0c2fda82697b8177ea3c24639 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -114,6 +114,7 @@ fn main() { let channel_list = cx.add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx)); + auto_update::init(http, client::ZED_SERVER_URL.clone(), cx); project::Project::init(&client); client::Channel::init(&client); client::init(client.clone(), cx); @@ -178,8 +179,8 @@ fn main() { client, user_store, fs, - build_window_options: &build_window_options, - build_workspace: &build_workspace, + build_window_options, + build_workspace, }); journal::init(app_state.clone(), cx); theme_selector::init(cx); diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 48883d5c05c71619afa1e57d055c9aab62bf0a67..3f19dcbdac04e4153ccb916a13e0a087cb1c5be8 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -13,6 +13,11 @@ pub fn menus(state: &Arc) -> Vec> { keystroke: None, action: Box::new(super::About), }, + MenuItem::Action { + name: "Check for Updates", + keystroke: None, + action: Box::new(auto_update::Check), + }, MenuItem::Separator, MenuItem::Action { name: "Install CLI", diff --git a/crates/zed/src/test.rs b/crates/zed/src/test.rs index 0b81c4361b6c9eb70e1df67febde1bebce59663d..d4f24297e4d2dc26b0ab6f432acf20e4f64aafa7 100644 --- a/crates/zed/src/test.rs +++ b/crates/zed/src/test.rs @@ -40,7 +40,7 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { client, user_store, fs: FakeFs::new(cx.background().clone()), - build_window_options: &build_window_options, - build_workspace: &build_workspace, + build_window_options, + build_workspace, }) } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 4619979e7b2a60e0c3c7558fe1f0b6a5e9d86345..14816d22b091fbebd95962eccd1b7a11a7e318f9 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -216,10 +216,12 @@ pub fn build_workspace( workspace::lsp_status::LspStatus::new(workspace.project(), app_state.languages.clone(), cx) }); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); + let auto_update = cx.add_view(|cx| auto_update::AutoUpdateIndicator::new(cx)); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(diagnostic_message, cx); status_bar.add_left_item(lsp_status, cx); + status_bar.add_right_item(auto_update, cx); status_bar.add_right_item(cursor_position, cx); }); diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 75e5e75fb079431240572ca2c25f4faa7a95372c..57de2f01f4e829b9ea918efc4922dcde980f19f7 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -98,6 +98,8 @@ export default function workspace(theme: Theme) { cursorPosition: text(theme, "sans", "muted"), diagnosticMessage: text(theme, "sans", "muted"), lspMessage: text(theme, "sans", "muted"), + autoUpdateProgressMessage: text(theme, "sans", "muted"), + autoUpdateDoneMessage: text(theme, "sans", "muted"), }, titlebar: { avatarWidth: 18,