project: Spawn terminal process on background executor (#41216)
Attempt 2 for https://github.com/zed-industries/zed/pull/40774 We were spawning the process on the foreground thread before which can block an arbitrary amount of time. Likewise we no longer block deserialization on the terminal loading. Release Notes: - Improved startup time on systems with slow process spawning capabilities
This commit is contained in:
+180
-174
@@ -139,142 +139,145 @@ impl Project {
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
project.update(cx, move |this, cx| {
|
||||
let format_to_run = || {
|
||||
if let Some(command) = &spawn_task.command {
|
||||
let mut command: Option<Cow<str>> = shell_kind.try_quote(command);
|
||||
if let Some(command) = &mut command
|
||||
&& command.starts_with('"')
|
||||
&& let Some(prefix) = shell_kind.command_prefix()
|
||||
{
|
||||
*command = Cow::Owned(format!("{prefix}{command}"));
|
||||
let builder = project
|
||||
.update(cx, move |_, cx| {
|
||||
let format_to_run = || {
|
||||
if let Some(command) = &spawn_task.command {
|
||||
let mut command: Option<Cow<str>> = shell_kind.try_quote(command);
|
||||
if let Some(command) = &mut command
|
||||
&& command.starts_with('"')
|
||||
&& let Some(prefix) = shell_kind.command_prefix()
|
||||
{
|
||||
*command = Cow::Owned(format!("{prefix}{command}"));
|
||||
}
|
||||
|
||||
let args = spawn_task
|
||||
.args
|
||||
.iter()
|
||||
.filter_map(|arg| shell_kind.try_quote(&arg));
|
||||
|
||||
command.into_iter().chain(args).join(" ")
|
||||
} else {
|
||||
// todo: this breaks for remotes to windows
|
||||
format!("exec {shell} -l")
|
||||
}
|
||||
};
|
||||
|
||||
let args = spawn_task
|
||||
.args
|
||||
.iter()
|
||||
.filter_map(|arg| shell_kind.try_quote(&arg));
|
||||
let (shell, env) = {
|
||||
env.extend(spawn_task.env);
|
||||
match remote_client {
|
||||
Some(remote_client) => match activation_script.clone() {
|
||||
activation_script if !activation_script.is_empty() => {
|
||||
let separator = shell_kind.sequential_commands_separator();
|
||||
let activation_script =
|
||||
activation_script.join(&format!("{separator} "));
|
||||
let to_run = format_to_run();
|
||||
let shell = remote_client
|
||||
.read(cx)
|
||||
.shell()
|
||||
.unwrap_or_else(get_default_system_shell);
|
||||
let arg = format!("{activation_script}{separator} {to_run}");
|
||||
let args = shell_kind.args_for_shell(false, arg);
|
||||
|
||||
command.into_iter().chain(args).join(" ")
|
||||
} else {
|
||||
// todo: this breaks for remotes to windows
|
||||
format!("exec {shell} -l")
|
||||
}
|
||||
};
|
||||
|
||||
let (shell, env) = {
|
||||
env.extend(spawn_task.env);
|
||||
match remote_client {
|
||||
Some(remote_client) => match activation_script.clone() {
|
||||
activation_script if !activation_script.is_empty() => {
|
||||
let separator = shell_kind.sequential_commands_separator();
|
||||
let activation_script =
|
||||
activation_script.join(&format!("{separator} "));
|
||||
let to_run = format_to_run();
|
||||
let shell = remote_client
|
||||
.read(cx)
|
||||
.shell()
|
||||
.unwrap_or_else(get_default_system_shell);
|
||||
let arg = format!("{activation_script}{separator} {to_run}");
|
||||
let args = shell_kind.args_for_shell(false, arg);
|
||||
|
||||
create_remote_shell(
|
||||
Some((&shell, &args)),
|
||||
create_remote_shell(
|
||||
Some((&shell, &args)),
|
||||
env,
|
||||
path,
|
||||
remote_client,
|
||||
cx,
|
||||
)?
|
||||
}
|
||||
_ => create_remote_shell(
|
||||
spawn_task
|
||||
.command
|
||||
.as_ref()
|
||||
.map(|command| (command, &spawn_task.args)),
|
||||
env,
|
||||
path,
|
||||
remote_client,
|
||||
cx,
|
||||
)?
|
||||
}
|
||||
_ => create_remote_shell(
|
||||
spawn_task
|
||||
.command
|
||||
.as_ref()
|
||||
.map(|command| (command, &spawn_task.args)),
|
||||
env,
|
||||
path,
|
||||
remote_client,
|
||||
cx,
|
||||
)?,
|
||||
},
|
||||
None => match activation_script.clone() {
|
||||
activation_script if !activation_script.is_empty() => {
|
||||
let separator = shell_kind.sequential_commands_separator();
|
||||
let activation_script =
|
||||
activation_script.join(&format!("{separator} "));
|
||||
let to_run = format_to_run();
|
||||
)?,
|
||||
},
|
||||
None => match activation_script.clone() {
|
||||
activation_script if !activation_script.is_empty() => {
|
||||
let separator = shell_kind.sequential_commands_separator();
|
||||
let activation_script =
|
||||
activation_script.join(&format!("{separator} "));
|
||||
let to_run = format_to_run();
|
||||
|
||||
let mut arg = format!("{activation_script}{separator} {to_run}");
|
||||
if shell_kind == ShellKind::Cmd {
|
||||
// We need to put the entire command in quotes since otherwise CMD tries to execute them
|
||||
// as separate commands rather than chaining one after another.
|
||||
arg = format!("\"{arg}\"");
|
||||
let mut arg =
|
||||
format!("{activation_script}{separator} {to_run}");
|
||||
if shell_kind == ShellKind::Cmd {
|
||||
// We need to put the entire command in quotes since otherwise CMD tries to execute them
|
||||
// as separate commands rather than chaining one after another.
|
||||
arg = format!("\"{arg}\"");
|
||||
}
|
||||
|
||||
let args = shell_kind.args_for_shell(false, arg);
|
||||
|
||||
(
|
||||
Shell::WithArguments {
|
||||
program: shell,
|
||||
args,
|
||||
title_override: None,
|
||||
},
|
||||
env,
|
||||
)
|
||||
}
|
||||
|
||||
let args = shell_kind.args_for_shell(false, arg);
|
||||
|
||||
(
|
||||
Shell::WithArguments {
|
||||
program: shell,
|
||||
args,
|
||||
title_override: None,
|
||||
_ => (
|
||||
if let Some(program) = spawn_task.command {
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args: spawn_task.args,
|
||||
title_override: None,
|
||||
}
|
||||
} else {
|
||||
Shell::System
|
||||
},
|
||||
env,
|
||||
)
|
||||
}
|
||||
_ => (
|
||||
if let Some(program) = spawn_task.command {
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args: spawn_task.args,
|
||||
title_override: None,
|
||||
}
|
||||
} else {
|
||||
Shell::System
|
||||
},
|
||||
env,
|
||||
),
|
||||
},
|
||||
}
|
||||
};
|
||||
TerminalBuilder::new(
|
||||
local_path.map(|path| path.to_path_buf()),
|
||||
task_state,
|
||||
shell,
|
||||
env,
|
||||
settings.cursor_shape,
|
||||
settings.alternate_scroll,
|
||||
settings.max_scroll_history_lines,
|
||||
is_via_remote,
|
||||
cx.entity_id().as_u64(),
|
||||
Some(completion_tx),
|
||||
cx,
|
||||
activation_script,
|
||||
)
|
||||
.map(|builder| {
|
||||
let terminal_handle = cx.new(|cx| builder.subscribe(cx));
|
||||
|
||||
this.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
|
||||
let id = terminal_handle.entity_id();
|
||||
cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
|
||||
let handles = &mut project.terminals.local_handles;
|
||||
|
||||
if let Some(index) = handles
|
||||
.iter()
|
||||
.position(|terminal| terminal.entity_id() == id)
|
||||
{
|
||||
handles.remove(index);
|
||||
cx.notify();
|
||||
),
|
||||
},
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
};
|
||||
anyhow::Ok(TerminalBuilder::new(
|
||||
local_path.map(|path| path.to_path_buf()),
|
||||
task_state,
|
||||
shell,
|
||||
env,
|
||||
settings.cursor_shape,
|
||||
settings.alternate_scroll,
|
||||
settings.max_scroll_history_lines,
|
||||
is_via_remote,
|
||||
cx.entity_id().as_u64(),
|
||||
Some(completion_tx),
|
||||
cx,
|
||||
activation_script,
|
||||
))
|
||||
})??
|
||||
.await?;
|
||||
project.update(cx, move |this, cx| {
|
||||
let terminal_handle = cx.new(|cx| builder.subscribe(cx));
|
||||
|
||||
terminal_handle
|
||||
this.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
|
||||
let id = terminal_handle.entity_id();
|
||||
cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
|
||||
let handles = &mut project.terminals.local_handles;
|
||||
|
||||
if let Some(index) = handles
|
||||
.iter()
|
||||
.position(|terminal| terminal.entity_id() == id)
|
||||
{
|
||||
handles.remove(index);
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
})?
|
||||
.detach();
|
||||
|
||||
terminal_handle
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -355,53 +358,55 @@ impl Project {
|
||||
})
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let builder = project
|
||||
.update(cx, move |_, cx| {
|
||||
let (shell, env) = {
|
||||
match remote_client {
|
||||
Some(remote_client) => {
|
||||
create_remote_shell(None, env, path, remote_client, cx)?
|
||||
}
|
||||
None => (settings.shell, env),
|
||||
}
|
||||
};
|
||||
anyhow::Ok(TerminalBuilder::new(
|
||||
local_path.map(|path| path.to_path_buf()),
|
||||
None,
|
||||
shell,
|
||||
env,
|
||||
settings.cursor_shape,
|
||||
settings.alternate_scroll,
|
||||
settings.max_scroll_history_lines,
|
||||
is_via_remote,
|
||||
cx.entity_id().as_u64(),
|
||||
None,
|
||||
cx,
|
||||
activation_script,
|
||||
))
|
||||
})??
|
||||
.await?;
|
||||
project.update(cx, move |this, cx| {
|
||||
let (shell, env) = {
|
||||
match remote_client {
|
||||
Some(remote_client) => {
|
||||
create_remote_shell(None, env, path, remote_client, cx)?
|
||||
}
|
||||
None => (settings.shell, env),
|
||||
let terminal_handle = cx.new(|cx| builder.subscribe(cx));
|
||||
|
||||
this.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
|
||||
let id = terminal_handle.entity_id();
|
||||
cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
|
||||
let handles = &mut project.terminals.local_handles;
|
||||
|
||||
if let Some(index) = handles
|
||||
.iter()
|
||||
.position(|terminal| terminal.entity_id() == id)
|
||||
{
|
||||
handles.remove(index);
|
||||
cx.notify();
|
||||
}
|
||||
};
|
||||
TerminalBuilder::new(
|
||||
local_path.map(|path| path.to_path_buf()),
|
||||
None,
|
||||
shell,
|
||||
env,
|
||||
settings.cursor_shape,
|
||||
settings.alternate_scroll,
|
||||
settings.max_scroll_history_lines,
|
||||
is_via_remote,
|
||||
cx.entity_id().as_u64(),
|
||||
None,
|
||||
cx,
|
||||
activation_script,
|
||||
)
|
||||
.map(|builder| {
|
||||
let terminal_handle = cx.new(|cx| builder.subscribe(cx));
|
||||
|
||||
this.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
|
||||
let id = terminal_handle.entity_id();
|
||||
cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
|
||||
let handles = &mut project.terminals.local_handles;
|
||||
|
||||
if let Some(index) = handles
|
||||
.iter()
|
||||
.position(|terminal| terminal.entity_id() == id)
|
||||
{
|
||||
handles.remove(index);
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
terminal_handle
|
||||
})
|
||||
})?
|
||||
.detach();
|
||||
|
||||
terminal_handle
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -422,13 +427,14 @@ impl Project {
|
||||
cwd
|
||||
};
|
||||
|
||||
let new_terminal = terminal
|
||||
.read(cx)
|
||||
.clone_builder(cx, local_path)
|
||||
.map(|builder| {
|
||||
let terminal_handle = cx.new(|cx| builder.subscribe(cx));
|
||||
let builder = terminal.read(cx).clone_builder(cx, local_path);
|
||||
cx.spawn(async |project, cx| {
|
||||
let terminal = builder.await?;
|
||||
project.update(cx, |project, cx| {
|
||||
let terminal_handle = cx.new(|cx| terminal.subscribe(cx));
|
||||
|
||||
self.terminals
|
||||
project
|
||||
.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
|
||||
@@ -447,8 +453,8 @@ impl Project {
|
||||
.detach();
|
||||
|
||||
terminal_handle
|
||||
});
|
||||
Task::ready(new_terminal)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn terminal_settings<'a>(
|
||||
|
||||
Reference in New Issue
Block a user