diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 08de0eb138b6695eb65bc30acbfa9688bb38fe00..5ea9824916520cfb53673f82f17c1d0e5d31ede3 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -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> = 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> = 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)); + 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 (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)), + 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)), + 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(); - - 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}\""); - } + )?, + }, + 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 args = shell_kind.args_for_shell(false, arg); + let args = shell_kind.args_for_shell(false, arg); - ( - Shell::WithArguments { - program: shell, - args, - title_override: None, + ( + Shell::WithArguments { + program: shell, + args, + title_override: None, + }, + env, + ) + } + _ => ( + 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)); + + 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; - terminal_handle + 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(); - 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), - } - }; - 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(); + 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), } - }) - .detach(); + }; + 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 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; - terminal_handle + if let Some(index) = handles + .iter() + .position(|terminal| terminal.entity_id() == id) + { + handles.remove(index); + cx.notify(); + } }) - })? + .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>( diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 41c51c38f7b1163229bb6570679ac873bd36474b..2a73ce05579a95a1211fc0289aac334650abcb6e 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -402,6 +402,7 @@ impl TerminalBuilder { window_id, }, child_exited: None, + event_loop_task: Task::ready(Ok(())), }; Ok(TerminalBuilder { @@ -423,236 +424,236 @@ impl TerminalBuilder { completion_tx: Option>>, cx: &App, activation_script: Vec, - ) -> Result { - // If the parent environment doesn't have a locale set - // (As is the case when launched from a .app on MacOS), - // and the Project doesn't have a locale set, then - // set a fallback for our child environment to use. - if std::env::var("LANG").is_err() { - env.entry("LANG".to_string()) - .or_insert_with(|| "en_US.UTF-8".to_string()); - } - - env.insert("ZED_TERM".to_string(), "true".to_string()); - env.insert("TERM_PROGRAM".to_string(), "zed".to_string()); - env.insert("TERM".to_string(), "xterm-256color".to_string()); - env.insert("COLORTERM".to_string(), "truecolor".to_string()); - env.insert( - "TERM_PROGRAM_VERSION".to_string(), - release_channel::AppVersion::global(cx).to_string(), - ); - - #[derive(Default)] - struct ShellParams { - program: String, - args: Option>, - title_override: Option, - } - - impl ShellParams { - fn new( + ) -> Task> { + let version = release_channel::AppVersion::global(cx); + cx.background_spawn(async move { + // If the parent environment doesn't have a locale set + // (As is the case when launched from a .app on MacOS), + // and the Project doesn't have a locale set, then + // set a fallback for our child environment to use. + if std::env::var("LANG").is_err() { + env.entry("LANG".to_string()) + .or_insert_with(|| "en_US.UTF-8".to_string()); + } + + env.insert("ZED_TERM".to_string(), "true".to_string()); + env.insert("TERM_PROGRAM".to_string(), "zed".to_string()); + env.insert("TERM".to_string(), "xterm-256color".to_string()); + env.insert("COLORTERM".to_string(), "truecolor".to_string()); + env.insert("TERM_PROGRAM_VERSION".to_string(), version.to_string()); + + #[derive(Default)] + struct ShellParams { program: String, args: Option>, title_override: Option, - ) -> Self { - log::debug!("Using {program} as shell"); - Self { - program, - args, - title_override, - } } - } - let shell_params = match shell.clone() { - Shell::System => { - if cfg!(windows) { - Some(ShellParams::new( - util::shell::get_windows_system_shell(), - None, - None, - )) - } else { - None + impl ShellParams { + fn new( + program: String, + args: Option>, + title_override: Option, + ) -> Self { + log::debug!("Using {program} as shell"); + Self { + program, + args, + title_override, + } } } - Shell::Program(program) => Some(ShellParams::new(program, None, None)), - Shell::WithArguments { - program, - args, - title_override, - } => Some(ShellParams::new(program, Some(args), title_override)), - }; - let terminal_title_override = shell_params.as_ref().and_then(|e| e.title_override.clone()); - #[cfg(windows)] - let shell_program = shell_params.as_ref().map(|params| { - use util::ResultExt; + let shell_params = match shell.clone() { + Shell::System => { + if cfg!(windows) { + Some(ShellParams::new( + util::shell::get_windows_system_shell(), + None, + None, + )) + } else { + None + } + } + Shell::Program(program) => Some(ShellParams::new(program, None, None)), + Shell::WithArguments { + program, + args, + title_override, + } => Some(ShellParams::new(program, Some(args), title_override)), + }; + let terminal_title_override = + shell_params.as_ref().and_then(|e| e.title_override.clone()); - Self::resolve_path(¶ms.program) - .log_err() - .unwrap_or(params.program.clone()) - }); + #[cfg(windows)] + let shell_program = shell_params.as_ref().map(|params| { + use util::ResultExt; - // Note: when remoting, this shell_kind will scrutinize `ssh` or - // `wsl.exe` as a shell and fall back to posix or powershell based on - // the compilation target. This is fine right now due to the restricted - // way we use the return value, but would become incorrect if we - // supported remoting into windows. - let shell_kind = shell.shell_kind(cfg!(windows)); - - let pty_options = { - let alac_shell = shell_params.as_ref().map(|params| { - alacritty_terminal::tty::Shell::new( - params.program.clone(), - params.args.clone().unwrap_or_default(), - ) + Self::resolve_path(¶ms.program) + .log_err() + .unwrap_or(params.program.clone()) }); - alacritty_terminal::tty::Options { - shell: alac_shell, - working_directory: working_directory.clone(), - drain_on_exit: true, - env: env.clone().into_iter().collect(), - #[cfg(windows)] - escape_args: shell_kind.tty_escape_args(), - } - }; + // Note: when remoting, this shell_kind will scrutinize `ssh` or + // `wsl.exe` as a shell and fall back to posix or powershell based on + // the compilation target. This is fine right now due to the restricted + // way we use the return value, but would become incorrect if we + // supported remoting into windows. + let shell_kind = shell.shell_kind(cfg!(windows)); + + let pty_options = { + let alac_shell = shell_params.as_ref().map(|params| { + alacritty_terminal::tty::Shell::new( + params.program.clone(), + params.args.clone().unwrap_or_default(), + ) + }); - let default_cursor_style = AlacCursorStyle::from(cursor_shape); - let scrolling_history = if task.is_some() { - // Tasks like `cargo build --all` may produce a lot of output, ergo allow maximum scrolling. - // After the task finishes, we do not allow appending to that terminal, so small tasks output should not - // cause excessive memory usage over time. - MAX_SCROLL_HISTORY_LINES - } else { - max_scroll_history_lines - .unwrap_or(DEFAULT_SCROLL_HISTORY_LINES) - .min(MAX_SCROLL_HISTORY_LINES) - }; - let config = Config { - scrolling_history, - default_cursor_style, - ..Config::default() - }; + alacritty_terminal::tty::Options { + shell: alac_shell, + working_directory: working_directory.clone(), + drain_on_exit: true, + env: env.clone().into_iter().collect(), + #[cfg(windows)] + escape_args: shell_kind.tty_escape_args(), + } + }; - //Spawn a task so the Alacritty EventLoop can communicate with us - //TODO: Remove with a bounded sender which can be dispatched on &self - let (events_tx, events_rx) = unbounded(); - //Set up the terminal... - let mut term = Term::new( - config.clone(), - &TerminalBounds::default(), - ZedListener(events_tx.clone()), - ); + let default_cursor_style = AlacCursorStyle::from(cursor_shape); + let scrolling_history = if task.is_some() { + // Tasks like `cargo build --all` may produce a lot of output, ergo allow maximum scrolling. + // After the task finishes, we do not allow appending to that terminal, so small tasks output should not + // cause excessive memory usage over time. + MAX_SCROLL_HISTORY_LINES + } else { + max_scroll_history_lines + .unwrap_or(DEFAULT_SCROLL_HISTORY_LINES) + .min(MAX_SCROLL_HISTORY_LINES) + }; + let config = Config { + scrolling_history, + default_cursor_style, + ..Config::default() + }; - //Alacritty defaults to alternate scrolling being on, so we just need to turn it off. - if let AlternateScroll::Off = alternate_scroll { - term.unset_private_mode(PrivateMode::Named(NamedPrivateMode::AlternateScroll)); - } + //Setup the pty... + let pty = match tty::new(&pty_options, TerminalBounds::default().into(), window_id) { + Ok(pty) => pty, + Err(error) => { + bail!(TerminalError { + directory: working_directory, + program: shell_params.as_ref().map(|params| params.program.clone()), + args: shell_params.as_ref().and_then(|params| params.args.clone()), + title_override: terminal_title_override, + source: error, + }); + } + }; - let term = Arc::new(FairMutex::new(term)); + //Spawn a task so the Alacritty EventLoop can communicate with us + //TODO: Remove with a bounded sender which can be dispatched on &self + let (events_tx, events_rx) = unbounded(); + //Set up the terminal... + let mut term = Term::new( + config.clone(), + &TerminalBounds::default(), + ZedListener(events_tx.clone()), + ); - //Setup the pty... - let pty = match tty::new(&pty_options, TerminalBounds::default().into(), window_id) { - Ok(pty) => pty, - Err(error) => { - bail!(TerminalError { - directory: working_directory, - program: shell_params.as_ref().map(|params| params.program.clone()), - args: shell_params.as_ref().and_then(|params| params.args.clone()), - title_override: terminal_title_override, - source: error, - }); + //Alacritty defaults to alternate scrolling being on, so we just need to turn it off. + if let AlternateScroll::Off = alternate_scroll { + term.unset_private_mode(PrivateMode::Named(NamedPrivateMode::AlternateScroll)); } - }; - let pty_info = PtyProcessInfo::new(&pty); + let term = Arc::new(FairMutex::new(term)); - //And connect them together - let event_loop = EventLoop::new( - term.clone(), - ZedListener(events_tx), - pty, - pty_options.drain_on_exit, - false, - ) - .context("failed to create event loop")?; + let pty_info = PtyProcessInfo::new(&pty); - //Kick things off - let pty_tx = event_loop.channel(); - let _io_thread = event_loop.spawn(); // DANGER + //And connect them together + let event_loop = EventLoop::new( + term.clone(), + ZedListener(events_tx), + pty, + pty_options.drain_on_exit, + false, + ) + .context("failed to create event loop")?; - let no_task = task.is_none(); + let pty_tx = event_loop.channel(); + let _io_thread = event_loop.spawn(); // DANGER - let terminal = Terminal { - task, - terminal_type: TerminalType::Pty { - pty_tx: Notifier(pty_tx), - info: pty_info, - }, - completion_tx, - term, - term_config: config, - title_override: terminal_title_override, - events: VecDeque::with_capacity(10), //Should never get this high. - last_content: Default::default(), - last_mouse: None, - matches: Vec::new(), - selection_head: None, - breadcrumb_text: String::new(), - scroll_px: px(0.), - next_link_id: 0, - selection_phase: SelectionPhase::Ended, - hyperlink_regex_searches: RegexSearches::new(), - vi_mode_enabled: false, - is_ssh_terminal, - last_mouse_move_time: Instant::now(), - last_hyperlink_search_position: None, - #[cfg(windows)] - shell_program, - activation_script: activation_script.clone(), - template: CopyTemplate { - shell, - env, - cursor_shape, - alternate_scroll, - max_scroll_history_lines, - window_id, - }, - child_exited: None, - }; + let no_task = task.is_none(); + let terminal = Terminal { + task, + terminal_type: TerminalType::Pty { + pty_tx: Notifier(pty_tx), + info: pty_info, + }, + completion_tx, + term, + term_config: config, + title_override: terminal_title_override, + events: VecDeque::with_capacity(10), //Should never get this high. + last_content: Default::default(), + last_mouse: None, + matches: Vec::new(), + selection_head: None, + breadcrumb_text: String::new(), + scroll_px: px(0.), + next_link_id: 0, + selection_phase: SelectionPhase::Ended, + hyperlink_regex_searches: RegexSearches::new(), + vi_mode_enabled: false, + is_ssh_terminal, + last_mouse_move_time: Instant::now(), + last_hyperlink_search_position: None, + #[cfg(windows)] + shell_program, + activation_script: activation_script.clone(), + template: CopyTemplate { + shell, + env, + cursor_shape, + alternate_scroll, + max_scroll_history_lines, + window_id, + }, + child_exited: None, + event_loop_task: Task::ready(Ok(())), + }; - if !activation_script.is_empty() && no_task { - for activation_script in activation_script { - terminal.write_to_pty(activation_script.into_bytes()); + if !activation_script.is_empty() && no_task { + for activation_script in activation_script { + terminal.write_to_pty(activation_script.into_bytes()); + // Simulate enter key press + // NOTE(PowerShell): using `\r\n` will put PowerShell in a continuation mode (infamous >> character) + // and generally mess up the rendering. + terminal.write_to_pty(b"\x0d"); + } + // In order to clear the screen at this point, we have two options: + // 1. We can send a shell-specific command such as "clear" or "cls" + // 2. We can "echo" a marker message that we will then catch when handling a Wakeup event + // and clear the screen using `terminal.clear()` method + // We cannot issue a `terminal.clear()` command at this point as alacritty is evented + // and while we have sent the activation script to the pty, it will be executed asynchronously. + // Therefore, we somehow need to wait for the activation script to finish executing before we + // can proceed with clearing the screen. + terminal.write_to_pty(shell_kind.clear_screen_command().as_bytes()); // Simulate enter key press - // NOTE(PowerShell): using `\r\n` will put PowerShell in a continuation mode (infamous >> character) - // and generally mess up the rendering. terminal.write_to_pty(b"\x0d"); } - // In order to clear the screen at this point, we have two options: - // 1. We can send a shell-specific command such as "clear" or "cls" - // 2. We can "echo" a marker message that we will then catch when handling a Wakeup event - // and clear the screen using `terminal.clear()` method - // We cannot issue a `terminal.clear()` command at this point as alacritty is evented - // and while we have sent the activation script to the pty, it will be executed asynchronously. - // Therefore, we somehow need to wait for the activation script to finish executing before we - // can proceed with clearing the screen. - terminal.write_to_pty(shell_kind.clear_screen_command().as_bytes()); - // Simulate enter key press - terminal.write_to_pty(b"\x0d"); - } - Ok(TerminalBuilder { - terminal, - events_rx, + Ok(TerminalBuilder { + terminal, + events_rx, + }) }) } pub fn subscribe(mut self, cx: &Context) -> Terminal { //Event loop - cx.spawn(async move |terminal, cx| { + self.terminal.event_loop_task = cx.spawn(async move |terminal, cx| { while let Some(event) = self.events_rx.next().await { terminal.update(cx, |terminal, cx| { //Process the first event immediately for lowered latency @@ -709,11 +710,8 @@ impl TerminalBuilder { smol::future::yield_now().await; } } - anyhow::Ok(()) - }) - .detach(); - + }); self.terminal } @@ -836,6 +834,7 @@ pub struct Terminal { template: CopyTemplate, activation_script: Vec, child_exited: Option, + event_loop_task: Task>, } struct CopyTemplate { @@ -1266,15 +1265,11 @@ impl Terminal { } pub fn total_lines(&self) -> usize { - let term = self.term.clone(); - let terminal = term.lock_unfair(); - terminal.total_lines() + self.term.lock_unfair().total_lines() } pub fn viewport_lines(&self) -> usize { - let term = self.term.clone(); - let terminal = term.lock_unfair(); - terminal.screen_lines() + self.term.lock_unfair().screen_lines() } //To test: @@ -2151,7 +2146,7 @@ impl Terminal { self.vi_mode_enabled } - pub fn clone_builder(&self, cx: &App, cwd: Option) -> Result { + pub fn clone_builder(&self, cx: &App, cwd: Option) -> Task> { let working_directory = self.working_directory().or_else(|| cwd); TerminalBuilder::new( working_directory, @@ -2387,28 +2382,30 @@ mod tests { let (completion_tx, completion_rx) = smol::channel::unbounded(); let (program, args) = ShellBuilder::new(&Shell::System, false) .build(Some("echo".to_owned()), &["hello".to_owned()]); - let terminal = cx.new(|cx| { - TerminalBuilder::new( - None, - None, - task::Shell::WithArguments { - program, - args, - title_override: None, - }, - HashMap::default(), - CursorShape::default(), - AlternateScroll::On, - None, - false, - 0, - Some(completion_tx), - cx, - vec![], - ) - .unwrap() - .subscribe(cx) - }); + let builder = cx + .update(|cx| { + TerminalBuilder::new( + None, + None, + task::Shell::WithArguments { + program, + args, + title_override: None, + }, + HashMap::default(), + CursorShape::default(), + AlternateScroll::On, + None, + false, + 0, + Some(completion_tx), + cx, + vec![], + ) + }) + .await + .unwrap(); + let terminal = cx.new(|cx| builder.subscribe(cx)); assert_eq!( completion_rx.recv().await.unwrap(), Some(ExitStatus::default()) @@ -2437,25 +2434,27 @@ mod tests { cx.executor().allow_parking(); let (completion_tx, completion_rx) = smol::channel::unbounded(); + let builder = cx + .update(|cx| { + TerminalBuilder::new( + None, + None, + task::Shell::System, + HashMap::default(), + CursorShape::default(), + AlternateScroll::On, + None, + false, + 0, + Some(completion_tx), + cx, + Vec::new(), + ) + }) + .await + .unwrap(); // Build an empty command, which will result in a tty shell spawned. - let terminal = cx.new(|cx| { - TerminalBuilder::new( - None, - None, - task::Shell::System, - HashMap::default(), - CursorShape::default(), - AlternateScroll::On, - None, - false, - 0, - Some(completion_tx), - cx, - Vec::new(), - ) - .unwrap() - .subscribe(cx) - }); + let terminal = cx.new(|cx| builder.subscribe(cx)); let (event_tx, event_rx) = smol::channel::unbounded::(); cx.update(|cx| { @@ -2506,28 +2505,30 @@ mod tests { let (completion_tx, completion_rx) = smol::channel::unbounded(); let (program, args) = ShellBuilder::new(&Shell::System, false) .build(Some("asdasdasdasd".to_owned()), &["@@@@@".to_owned()]); - let terminal = cx.new(|cx| { - TerminalBuilder::new( - None, - None, - task::Shell::WithArguments { - program, - args, - title_override: None, - }, - HashMap::default(), - CursorShape::default(), - AlternateScroll::On, - None, - false, - 0, - Some(completion_tx), - cx, - Vec::new(), - ) - .unwrap() - .subscribe(cx) - }); + let builder = cx + .update(|cx| { + TerminalBuilder::new( + None, + None, + task::Shell::WithArguments { + program, + args, + title_override: None, + }, + HashMap::default(), + CursorShape::default(), + AlternateScroll::On, + None, + false, + 0, + Some(completion_tx), + cx, + Vec::new(), + ) + }) + .await + .unwrap(); + let terminal = cx.new(|cx| builder.subscribe(cx)); let (event_tx, event_rx) = smol::channel::unbounded::(); cx.update(|cx| { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index ff169e48e53b01f29ca1ab1682927ea116f320fc..63d6c503b29d1eec6500bd4acb5c2f0f6ef36e33 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1223,26 +1223,26 @@ impl Item for TerminalView { window: &mut Window, cx: &mut Context, ) -> Task>> { - let Some(terminal_task) = self - .project - .update(cx, |project, cx| { - let cwd = project - .active_project_directory(cx) - .map(|it| it.to_path_buf()); - project.clone_terminal(self.terminal(), cx, cwd) - }) - .ok() - else { + let Ok(terminal) = self.project.update(cx, |project, cx| { + let cwd = project + .active_project_directory(cx) + .map(|it| it.to_path_buf()); + project.clone_terminal(self.terminal(), cx, cwd) + }) else { return Task::ready(None); }; - - let workspace = self.workspace.clone(); - let project = self.project.clone(); - cx.spawn_in(window, async move |_, cx| { - let terminal = terminal_task.await.log_err()?; - cx.update(|window, cx| { + cx.spawn_in(window, async move |this, cx| { + let terminal = terminal.await.log_err()?; + this.update_in(cx, |this, window, cx| { cx.new(|cx| { - TerminalView::new(terminal, workspace, workspace_id, project, window, cx) + TerminalView::new( + terminal, + this.workspace.clone(), + workspace_id, + this.project.clone(), + window, + cx, + ) }) }) .ok()