Preserve --user-data-dir across restarts (#62022)
# Objective Fixes #57701. When Zed is launched with `--user-data-dir`, a Zed-initiated restart—such as `workspace: clear trusted worktrees` or Restart to Update—can relaunch Zed without that argument. The restarted process then uses the default data directory instead of the directory selected by the user. ## Solution - Store the resolved custom data directory as GPUI restart arguments alongside the existing optional restart executable path. - Forward those arguments when relaunching on Linux, macOS, and Windows. - Preserve argument boundaries and native `OsString` values, including paths containing spaces. - On Windows updates, carry the arguments through `auto_update_helper.exe` when it launches the updated `Zed.exe`. Normal launches without `--user-data-dir` continue to restart without additional arguments. This does not attempt to preserve unrelated CLI arguments or general launch state. ## Testing - `cargo fmt --all -- --check` - `cargo test -p gpui` - `cargo test -p auto_update --features workspace/test-support` - `cargo check -p auto_update_helper --tests --target x86_64-pc-windows-msvc` - `./script/clippy -p gpui -p gpui_macos -p auto_update -p zed` - `./script/clippy -p auto_update_helper --target x86_64-pc-windows-msvc` The regression tests cover simultaneous restart executable and argument forwarding, Windows paths containing spaces and trailing backslashes, updater-helper argument parsing, and the final `Zed.exe` command. Compilation and unit tests were run on macOS. Linux and Windows runtime behavior was reviewed but not run locally. A full `gpui_windows` cross-target check was attempted, but its `psm` dependency requires the MSVC `lib.exe` tool, which is not available on this macOS host; the standalone Windows updater helper and its tests compile successfully for the Windows target. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards (no UI changes) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Fixed Zed forgetting an explicitly passed `--user-data-dir` argument after restarting (e.g. clicking "Restart to Update") --------- Co-authored-by: Tom Houlé <tom@tomhoule.com>
This commit is contained in:
1 parent
2dd39da2b0
commit
d7f85eed4e
13 files changed
+243
-62
No files matched your search
@@ -1492,7 +1492,9 @@ mod tests {
|
||||
);
|
||||
let will_restart = cx.expect_restart();
|
||||
cx.update(|cx| cx.restart());
|
||||
let path = will_restart.await.unwrap().unwrap();
|
||||
let (path, arguments) = will_restart.await.unwrap();
|
||||
assert!(arguments.is_empty());
|
||||
let path = path.unwrap();
|
||||
assert_eq!(path, tmp_dir.path().join("zed"));
|
||||
assert_eq!(std::fs::read_to_string(path).unwrap(), "<fake-zed-update>");
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ fn main() {}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows_impl {
|
||||
use std::{borrow::Cow, path::Path};
|
||||
use std::{
|
||||
ffi::{OsStr, OsString},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use super::dialog::create_dialog_window;
|
||||
use super::updater::perform_update;
|
||||
@@ -37,9 +40,10 @@ mod windows_impl {
|
||||
pub(crate) const WM_JOB_UPDATED: u32 = WM_USER + 1;
|
||||
pub(crate) const WM_TERMINATE: u32 = WM_USER + 2;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct Args {
|
||||
launch: bool,
|
||||
launch_arguments: Vec<OsString>,
|
||||
}
|
||||
|
||||
pub(crate) fn run() -> Result<()> {
|
||||
@@ -56,9 +60,14 @@ mod windows_impl {
|
||||
log::info!("======= Starting Zed update =======");
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let hwnd = create_dialog_window(rx)?.0 as isize;
|
||||
let args = parse_args(std::env::args().skip(1));
|
||||
let args = parse_args(std::env::args_os().skip(1));
|
||||
std::thread::spawn(move || {
|
||||
let result = perform_update(app_dir.as_path(), Some(hwnd), args.launch);
|
||||
let result = perform_update(
|
||||
app_dir.as_path(),
|
||||
Some(hwnd),
|
||||
args.launch,
|
||||
&args.launch_arguments,
|
||||
);
|
||||
tx.send(result).ok();
|
||||
unsafe { PostMessageW(Some(HWND(hwnd as _)), WM_TERMINATE, WPARAM(0), LPARAM(0)) }.ok();
|
||||
});
|
||||
@@ -83,24 +92,23 @@ mod windows_impl {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_args(input: impl IntoIterator<Item = String>) -> Args {
|
||||
let mut args: Args = Args { launch: true };
|
||||
fn parse_args(input: impl IntoIterator<Item = OsString>) -> Args {
|
||||
let mut args = Args {
|
||||
launch: true,
|
||||
launch_arguments: Vec::new(),
|
||||
};
|
||||
|
||||
let mut input = input.into_iter();
|
||||
if let Some(arg) = input.next() {
|
||||
let launch_arg;
|
||||
|
||||
if arg == "--launch" {
|
||||
launch_arg = input.next().map(Cow::Owned);
|
||||
} else if let Some(rest) = arg.strip_prefix("--launch=") {
|
||||
launch_arg = Some(Cow::Borrowed(rest));
|
||||
if arg == OsStr::new("--launch") {
|
||||
args.launch = input.next().as_deref() != Some(OsStr::new("false"));
|
||||
} else if let Some(launch) = arg.to_str().and_then(|arg| arg.strip_prefix("--launch="))
|
||||
{
|
||||
args.launch = launch != "false";
|
||||
} else {
|
||||
launch_arg = None;
|
||||
}
|
||||
|
||||
if launch_arg.as_deref() == Some("false") {
|
||||
args.launch = false;
|
||||
args.launch_arguments.push(arg);
|
||||
}
|
||||
args.launch_arguments.extend(input);
|
||||
}
|
||||
|
||||
args
|
||||
@@ -123,6 +131,8 @@ mod windows_impl {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::OsString;
|
||||
|
||||
use crate::windows_impl::parse_args;
|
||||
|
||||
#[test]
|
||||
@@ -143,5 +153,17 @@ mod windows_impl {
|
||||
assert!(parse_args(["--launch=".into()]).launch);
|
||||
assert!(parse_args(["--launch=invalid".into()]).launch);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_args_preserves_launch_arguments() {
|
||||
let launch_arguments = vec![
|
||||
OsString::from("--user-data-dir"),
|
||||
OsString::from(r"C:\Zed Data"),
|
||||
];
|
||||
assert_eq!(
|
||||
parse_args(launch_arguments.clone()).launch_arguments,
|
||||
launch_arguments
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
ffi::{OsStr, OsString},
|
||||
os::windows::ffi::OsStrExt,
|
||||
path::Path,
|
||||
sync::LazyLock,
|
||||
@@ -363,7 +363,19 @@ fn release_file_handles(app_dir: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn perform_update(app_dir: &Path, hwnd: Option<isize>, launch: bool) -> Result<()> {
|
||||
#[allow(clippy::disallowed_methods, reason = "doesn't run in the main binary")]
|
||||
fn zed_launch_command(app_dir: &Path, launch_arguments: &[OsString]) -> std::process::Command {
|
||||
let mut command = std::process::Command::new(app_dir.join("Zed.exe"));
|
||||
command.args(launch_arguments);
|
||||
command
|
||||
}
|
||||
|
||||
pub(crate) fn perform_update(
|
||||
app_dir: &Path,
|
||||
hwnd: Option<isize>,
|
||||
launch: bool,
|
||||
launch_arguments: &[OsString],
|
||||
) -> Result<()> {
|
||||
let hwnd = hwnd.map(|ptr| HWND(ptr as _));
|
||||
|
||||
// Try to release file handles before starting the update
|
||||
@@ -428,7 +440,9 @@ pub(crate) fn perform_update(app_dir: &Path, hwnd: Option<isize>, launch: bool)
|
||||
|
||||
if launch {
|
||||
#[allow(clippy::disallowed_methods, reason = "doesn't run in the main binary")]
|
||||
let _ = std::process::Command::new(app_dir.join("Zed.exe")).spawn();
|
||||
let _child = zed_launch_command(app_dir, launch_arguments)
|
||||
.spawn()
|
||||
.context("Failed to launch Zed after update")?;
|
||||
}
|
||||
log::info!("Update completed successfully");
|
||||
Ok(())
|
||||
@@ -436,19 +450,42 @@ pub(crate) fn perform_update(app_dir: &Path, hwnd: Option<isize>, launch: bool)
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::perform_update;
|
||||
use std::{ffi::OsString, path::Path};
|
||||
|
||||
use super::{perform_update, zed_launch_command};
|
||||
|
||||
#[test]
|
||||
fn test_zed_launch_command_preserves_arguments() {
|
||||
let arguments = vec![
|
||||
OsString::from("--user-data-dir"),
|
||||
OsString::from(r"C:\Zed Data"),
|
||||
];
|
||||
let command = zed_launch_command(Path::new(r"C:\Program Files\Zed"), &arguments);
|
||||
|
||||
assert_eq!(
|
||||
command.get_program(),
|
||||
Path::new(r"C:\Program Files\Zed\Zed.exe").as_os_str()
|
||||
);
|
||||
assert_eq!(
|
||||
command.get_args().collect::<Vec<_>>(),
|
||||
arguments
|
||||
.iter()
|
||||
.map(OsString::as_os_str)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_perform_update() {
|
||||
let app_dir = tempfile::tempdir().unwrap();
|
||||
let app_dir = app_dir.path();
|
||||
assert!(perform_update(app_dir, None, false).is_ok());
|
||||
assert!(perform_update(app_dir, None, false, &[]).is_ok());
|
||||
|
||||
let app_dir = tempfile::tempdir().unwrap();
|
||||
let app_dir = app_dir.path();
|
||||
// Simulate a timeout
|
||||
unsafe { std::env::set_var("ZED_AUTO_UPDATE", "err1") };
|
||||
let ret = perform_update(app_dir, None, false);
|
||||
let ret = perform_update(app_dir, None, false, &[]);
|
||||
assert!(
|
||||
ret.is_err_and(|e| e.to_string().as_str() == "Autoupdate failed, nothing to rollback")
|
||||
);
|
||||
@@ -457,7 +494,7 @@ mod test {
|
||||
let app_dir = app_dir.path();
|
||||
// Simulate a timeout
|
||||
unsafe { std::env::set_var("ZED_AUTO_UPDATE", "err2") };
|
||||
let ret = perform_update(app_dir, None, false);
|
||||
let ret = perform_update(app_dir, None, false, &[]);
|
||||
assert!(
|
||||
ret.is_err_and(|e| e.to_string().as_str() == "Autoupdate failed, rollback successful")
|
||||
);
|
||||
|
||||
+39
-2
@@ -2,6 +2,7 @@ use scheduler::Instant;
|
||||
use std::{
|
||||
any::{TypeId, type_name},
|
||||
cell::{BorrowMutError, Cell, Ref, RefCell, RefMut},
|
||||
ffi::OsString,
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
@@ -206,6 +207,12 @@ impl Application {
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures arguments to pass when restarting the application.
|
||||
pub fn with_restart_arguments(self, arguments: Vec<OsString>) -> Self {
|
||||
self.0.borrow_mut().restart_arguments = arguments;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the HTTP client for the application.
|
||||
pub fn with_http_client(self, http_client: Arc<dyn HttpClient>) -> Self {
|
||||
let mut context_lock = self.0.borrow_mut();
|
||||
@@ -729,6 +736,7 @@ pub struct App {
|
||||
pub(crate) pending_notifications: FxHashSet<EntityId>,
|
||||
pub(crate) pending_global_notifications: TypeIdHashSet,
|
||||
pub(crate) restart_path: Option<PathBuf>,
|
||||
pub(crate) restart_arguments: Vec<OsString>,
|
||||
pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
|
||||
pub(crate) propagate_event: bool,
|
||||
pub(crate) prompt_builder: Option<PromptBuilder>,
|
||||
@@ -834,6 +842,7 @@ impl App {
|
||||
quit_observers: SubscriberSet::new(),
|
||||
restart_observers: SubscriberSet::new(),
|
||||
restart_path: None,
|
||||
restart_arguments: Vec::new(),
|
||||
window_closed_observers: SubscriberSet::new(),
|
||||
layout_id_buffer: Default::default(),
|
||||
propagate_event: true,
|
||||
@@ -1587,7 +1596,10 @@ impl App {
|
||||
self.restart_observers
|
||||
.clone()
|
||||
.retain(&(), |observer| observer(self));
|
||||
self.platform.restart(self.restart_path.take())
|
||||
self.platform.restart(
|
||||
self.restart_path.take(),
|
||||
std::mem::take(&mut self.restart_arguments),
|
||||
)
|
||||
}
|
||||
|
||||
/// Sets the path to use when restarting the application.
|
||||
@@ -3050,7 +3062,10 @@ impl<'a, T> Drop for GpuiBorrow<'a, T> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{cell::RefCell, ffi::OsString, path::PathBuf, rc::Rc};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
|
||||
use crate::{AppContext, TestAppContext};
|
||||
|
||||
@@ -3084,4 +3099,26 @@ mod test {
|
||||
|
||||
assert_eq!(*observation_count.borrow(), 2);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_restart_preserves_path_and_arguments(cx: &mut TestAppContext) {
|
||||
#[cfg(unix)]
|
||||
let user_data_dir = OsString::from_vec(b"/tmp/zed data/\xff".to_vec());
|
||||
#[cfg(not(unix))]
|
||||
let user_data_dir = OsString::from("C:\\zed data");
|
||||
let arguments = vec![OsString::from("--user-data-dir"), user_data_dir];
|
||||
let restart_path = PathBuf::from("updated-zed");
|
||||
let _application =
|
||||
super::Application(cx.app.clone()).with_restart_arguments(arguments.clone());
|
||||
let restart = cx.expect_restart();
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.set_restart_path(restart_path.clone());
|
||||
cx.restart();
|
||||
});
|
||||
|
||||
let (path, restart_arguments) = restart.await.expect("restart was not requested");
|
||||
assert_eq!(path, Some(restart_path));
|
||||
assert_eq!(restart_arguments, arguments);
|
||||
}
|
||||
}
|
||||
@@ -404,7 +404,7 @@ impl TestAppContext {
|
||||
}
|
||||
|
||||
/// Returns true if there's an alert dialog open.
|
||||
pub fn expect_restart(&self) -> oneshot::Receiver<Option<PathBuf>> {
|
||||
pub fn expect_restart(&self) -> oneshot::Receiver<(Option<PathBuf>, Vec<std::ffi::OsString>)> {
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
self.test_platform.expect_restart.borrow_mut().replace(tx);
|
||||
rx
|
||||
|
||||
@@ -64,6 +64,7 @@ use std::io::Cursor;
|
||||
use std::ops;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
fmt::{self, Debug},
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
@@ -129,7 +130,7 @@ pub trait Platform: 'static {
|
||||
|
||||
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
|
||||
fn quit(&self);
|
||||
fn restart(&self, binary_path: Option<PathBuf>);
|
||||
fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<OsString>);
|
||||
fn activate(&self, ignoring_other_apps: bool);
|
||||
fn hide(&self);
|
||||
fn hide_other_apps(&self);
|
||||
|
||||
@@ -35,7 +35,8 @@ pub(crate) struct TestPlatform {
|
||||
pub opened_url: RefCell<Option<String>>,
|
||||
pub(crate) system_notifications: RefCell<TestSystemNotifications>,
|
||||
pub text_system: Arc<dyn PlatformTextSystem>,
|
||||
pub expect_restart: RefCell<Option<oneshot::Sender<Option<PathBuf>>>>,
|
||||
pub expect_restart:
|
||||
RefCell<Option<oneshot::Sender<(Option<PathBuf>, Vec<std::ffi::OsString>)>>>,
|
||||
headless_renderer_factory: Option<Box<dyn Fn() -> Option<Box<dyn PlatformHeadlessRenderer>>>>,
|
||||
weak: Weak<Self>,
|
||||
}
|
||||
@@ -340,9 +341,9 @@ impl Platform for TestPlatform {
|
||||
|
||||
fn quit(&self) {}
|
||||
|
||||
fn restart(&self, path: Option<PathBuf>) {
|
||||
fn restart(&self, path: Option<PathBuf>, arguments: Vec<std::ffi::OsString>) {
|
||||
if let Some(tx) = self.expect_restart.take() {
|
||||
tx.send(path).unwrap();
|
||||
tx.send((path, arguments)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ impl Platform for VisualTestPlatform {
|
||||
|
||||
fn quit(&self) {}
|
||||
|
||||
fn restart(&self, _binary_path: Option<PathBuf>) {}
|
||||
fn restart(&self, _binary_path: Option<PathBuf>, _arguments: Vec<std::ffi::OsString>) {}
|
||||
|
||||
fn activate(&self, _ignoring_other_apps: bool) {}
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ impl<P: LinuxClient + 'static> Platform for LinuxPlatform<P> {
|
||||
self.inner.compositor_name()
|
||||
}
|
||||
|
||||
fn restart(&self, binary_path: Option<PathBuf>) {
|
||||
fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<std::ffi::OsString>) {
|
||||
use std::os::unix::process::CommandExt as _;
|
||||
|
||||
// get the process id of the current process
|
||||
@@ -312,7 +312,9 @@ impl<P: LinuxClient + 'static> Platform for LinuxPlatform<P> {
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
"$1"
|
||||
app_path="$1"
|
||||
shift
|
||||
"$app_path" "$@"
|
||||
"#;
|
||||
|
||||
#[allow(
|
||||
@@ -325,6 +327,7 @@ impl<P: LinuxClient + 'static> Platform for LinuxPlatform<P> {
|
||||
.arg(script)
|
||||
.arg(&app_pid)
|
||||
.arg(&app_path)
|
||||
.args(arguments)
|
||||
.process_group(0)
|
||||
.spawn();
|
||||
|
||||
|
||||
@@ -537,7 +537,7 @@ impl Platform for MacPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
fn restart(&self, binary_path: Option<PathBuf>) {
|
||||
fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<std::ffi::OsString>) {
|
||||
use std::os::unix::process::CommandExt as _;
|
||||
|
||||
let app_pid = std::process::id().to_string();
|
||||
@@ -557,7 +557,13 @@ impl Platform for MacPlatform {
|
||||
while kill -0 $0 2> /dev/null; do
|
||||
sleep 0.1
|
||||
done
|
||||
open "$1"
|
||||
app_path="$1"
|
||||
shift
|
||||
if (($# > 0)); then
|
||||
open "$app_path" --args "$@"
|
||||
else
|
||||
open "$app_path"
|
||||
fi
|
||||
"#;
|
||||
|
||||
#[allow(
|
||||
@@ -569,6 +575,7 @@ impl Platform for MacPlatform {
|
||||
.arg(script)
|
||||
.arg(app_pid)
|
||||
.arg(app_path)
|
||||
.args(arguments)
|
||||
.process_group(0)
|
||||
.spawn();
|
||||
|
||||
|
||||
@@ -307,7 +307,7 @@ impl Platform for WebPlatform {
|
||||
log::warn!("WebPlatform::quit called, but quitting is not supported in the browser .");
|
||||
}
|
||||
|
||||
fn restart(&self, _binary_path: Option<PathBuf>) {}
|
||||
fn restart(&self, _binary_path: Option<PathBuf>, _arguments: Vec<std::ffi::OsString>) {}
|
||||
|
||||
fn activate(&self, _ignoring_other_apps: bool) {}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
ffi::OsStr,
|
||||
ffi::{OsStr, OsString},
|
||||
os::windows::ffi::{OsStrExt as _, OsStringExt as _},
|
||||
path::{Path, PathBuf},
|
||||
rc::{Rc, Weak},
|
||||
sync::{
|
||||
@@ -371,6 +372,39 @@ fn translate_accelerator(msg: &MSG) -> Option<()> {
|
||||
(result.0 == 0).then_some(())
|
||||
}
|
||||
|
||||
fn encode_restart_arguments(arguments: &[OsString]) -> OsString {
|
||||
// `Start-Process` accepts a single native command line, so quote each argument according to
|
||||
// the Windows argv parsing rules before passing the complete string through the environment.
|
||||
let mut encoded = Vec::new();
|
||||
|
||||
for (index, argument) in arguments.iter().enumerate() {
|
||||
if index > 0 {
|
||||
encoded.push(b' ' as u16);
|
||||
}
|
||||
encoded.push(b'"' as u16);
|
||||
|
||||
let mut backslash_count = 0;
|
||||
for code_unit in argument.encode_wide() {
|
||||
if code_unit == b'\\' as u16 {
|
||||
backslash_count += 1;
|
||||
} else {
|
||||
if code_unit == b'"' as u16 {
|
||||
encoded.extend(std::iter::repeat_n(b'\\' as u16, backslash_count * 2 + 1));
|
||||
} else {
|
||||
encoded.extend(std::iter::repeat_n(b'\\' as u16, backslash_count));
|
||||
}
|
||||
backslash_count = 0;
|
||||
encoded.push(code_unit);
|
||||
}
|
||||
}
|
||||
|
||||
encoded.extend(std::iter::repeat_n(b'\\' as u16, backslash_count * 2));
|
||||
encoded.push(b'"' as u16);
|
||||
}
|
||||
|
||||
OsString::from_wide(&encoded)
|
||||
}
|
||||
|
||||
impl Platform for WindowsPlatform {
|
||||
fn background_executor(&self) -> BackgroundExecutor {
|
||||
self.background_executor.clone()
|
||||
@@ -436,28 +470,33 @@ impl Platform for WindowsPlatform {
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn restart(&self, binary_path: Option<PathBuf>) {
|
||||
fn restart(&self, binary_path: Option<PathBuf>, arguments: Vec<OsString>) {
|
||||
let pid = std::process::id();
|
||||
let Some(app_path) = binary_path.or(self.app_path().log_err()) else {
|
||||
return;
|
||||
};
|
||||
let script = format!(
|
||||
r#"
|
||||
$pidToWaitFor = {}
|
||||
$exePath = "{}"
|
||||
let script = r#"
|
||||
$pidToWaitFor = $env:ZED_RESTART_PID
|
||||
$exePath = $env:ZED_RESTART_EXECUTABLE
|
||||
$argumentList = $env:ZED_RESTART_ARGUMENTS
|
||||
|
||||
while ($true) {{
|
||||
[Environment]::SetEnvironmentVariable("ZED_RESTART_PID", $null)
|
||||
[Environment]::SetEnvironmentVariable("ZED_RESTART_EXECUTABLE", $null)
|
||||
[Environment]::SetEnvironmentVariable("ZED_RESTART_ARGUMENTS", $null)
|
||||
|
||||
while ($true) {
|
||||
$process = Get-Process -Id $pidToWaitFor -ErrorAction SilentlyContinue
|
||||
if (-not $process) {{
|
||||
Start-Process -FilePath $exePath
|
||||
if (-not $process) {
|
||||
if ([string]::IsNullOrEmpty($argumentList)) {
|
||||
Start-Process -FilePath $exePath
|
||||
} else {
|
||||
Start-Process -FilePath $exePath -ArgumentList $argumentList
|
||||
}
|
||||
break
|
||||
}}
|
||||
}
|
||||
Start-Sleep -Seconds 0.1
|
||||
}}
|
||||
"#,
|
||||
pid,
|
||||
app_path.display(),
|
||||
);
|
||||
}
|
||||
"#;
|
||||
|
||||
// Defer spawning to the foreground executor so it runs after the
|
||||
// current `AppCell` borrow is released. On Windows, `Command::spawn()`
|
||||
@@ -466,14 +505,19 @@ impl Platform for WindowsPlatform {
|
||||
// borrow of the `AppCell` ending up with a double borrow panic
|
||||
self.foreground_executor
|
||||
.spawn(async move {
|
||||
let mut command = new_std_command(get_windows_system_shell());
|
||||
let arguments = encode_restart_arguments(&arguments);
|
||||
command
|
||||
.arg("-command")
|
||||
.arg(script)
|
||||
.env("ZED_RESTART_PID", pid.to_string())
|
||||
.env("ZED_RESTART_EXECUTABLE", app_path)
|
||||
.env("ZED_RESTART_ARGUMENTS", arguments);
|
||||
#[allow(
|
||||
clippy::disallowed_methods,
|
||||
reason = "We are restarting ourselves, using std command thus is fine"
|
||||
)]
|
||||
let restart_process = new_std_command(get_windows_system_shell())
|
||||
.arg("-command")
|
||||
.arg(script)
|
||||
.spawn();
|
||||
let restart_process = command.spawn();
|
||||
|
||||
match restart_process {
|
||||
Ok(_) => unsafe { PostQuitMessage(0) },
|
||||
@@ -1493,9 +1537,29 @@ unsafe extern "system" fn window_procedure(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
use crate::{read_from_clipboard, write_to_clipboard};
|
||||
use gpui::ClipboardItem;
|
||||
|
||||
use super::encode_restart_arguments;
|
||||
|
||||
#[test]
|
||||
fn test_encode_restart_arguments() {
|
||||
assert_eq!(encode_restart_arguments(&[]), OsStr::new(""));
|
||||
assert_eq!(
|
||||
encode_restart_arguments(&[
|
||||
OsString::from("--user-data-dir"),
|
||||
OsString::from(r"C:\Zed Data"),
|
||||
]),
|
||||
OsStr::new(r#""--user-data-dir" "C:\Zed Data""#)
|
||||
);
|
||||
assert_eq!(
|
||||
encode_restart_arguments(&[OsString::from(r"C:\")]),
|
||||
OsStr::new(r#""C:\\""#)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clipboard() {
|
||||
let item = ClipboardItem::new_string("你好,我是张小白".to_string());
|
||||
|
||||
+12
-5
@@ -268,10 +268,15 @@ fn main() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set custom data directory.
|
||||
if let Some(dir) = &args.user_data_dir {
|
||||
paths::set_custom_data_dir(dir);
|
||||
}
|
||||
let restart_arguments = if let Some(directory) = args.user_data_dir.as_deref() {
|
||||
let directory = paths::set_custom_data_dir(directory);
|
||||
vec![
|
||||
std::ffi::OsString::from("--user-data-dir"),
|
||||
directory.as_os_str().to_owned(),
|
||||
]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
match util::get_zed_cli_path() {
|
||||
@@ -340,7 +345,9 @@ fn main() {
|
||||
#[cfg(windows)]
|
||||
check_for_conpty_dll();
|
||||
|
||||
let app = build_application().with_assets(Assets);
|
||||
let app = build_application()
|
||||
.with_assets(Assets)
|
||||
.with_restart_arguments(restart_arguments);
|
||||
|
||||
let app_db = db::AppDatabase::new();
|
||||
let system_id = app.background_executor().spawn(system_id());
|
||||
|
||||
Reference in new issue
Block a user