diff --git a/crates/agent2/src/tools/terminal_tool.rs b/crates/agent2/src/tools/terminal_tool.rs index 18f4571d65..9bd2a9b58a 100644 --- a/crates/agent2/src/tools/terminal_tool.rs +++ b/crates/agent2/src/tools/terminal_tool.rs @@ -141,18 +141,28 @@ impl AgentTool for TerminalTool { let program = program.await; let env = env.await; - let terminal = self.project.update(cx, |project, cx| { - project.create_terminal_task( - task::SpawnInTerminal { - command: Some(program), - args, - cwd: working_dir.clone(), - env, - ..Default::default() - }, - cx, - ) - })??; + let terminal = self + .project + .update(cx, |project, cx| { + project.create_terminal_task( + task::SpawnInTerminal { + command: Some(program), + args, + cwd: working_dir.clone(), + env, + ..Default::default() + }, + cx, + project + .active_entry() + .and_then(|entry_id| project.worktree_id_for_entry(entry_id, cx)) + .map(|worktree_id| project::ProjectPath { + worktree_id, + path: Arc::from(Path::new("")), + }), + ) + })? + .await?; let acp_terminal = cx.new(|cx| { acp_thread::Terminal::new( input.command.clone(), diff --git a/crates/assistant_tools/src/terminal_tool.rs b/crates/assistant_tools/src/terminal_tool.rs index b8f09012cb..49584d3c9c 100644 --- a/crates/assistant_tools/src/terminal_tool.rs +++ b/crates/assistant_tools/src/terminal_tool.rs @@ -213,18 +213,27 @@ impl Tool for TerminalTool { async move |cx| { let program = program.await; let env = env.await; - project.update(cx, |project, cx| { - project.create_terminal_task( - task::SpawnInTerminal { - command: Some(program), - args, - cwd, - env, - ..Default::default() - }, - cx, - ) - })? + project + .update(cx, |project, cx| { + project.create_terminal_task( + task::SpawnInTerminal { + command: Some(program), + args, + cwd, + env, + ..Default::default() + }, + cx, + project + .active_entry() + .and_then(|entry_id| project.worktree_id_for_entry(entry_id, cx)) + .map(|worktree_id| project::ProjectPath { + worktree_id, + path: Arc::from(Path::new("")), + }), + ) + })? + .await } }); diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index fd135596eb..0881cdcb22 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -1018,8 +1018,15 @@ impl RunningState { project.create_terminal_task( task_with_shell.clone(), cx, + project + .active_entry() + .and_then(|entry_id| project.worktree_id_for_entry(entry_id, cx)) + .map(|worktree_id| project::ProjectPath { + worktree_id, + path: Arc::from(std::path::Path::new("")), + }), ) - })??; + })?.await?; let terminal_view = cx.new_window_entity(|window, cx| { TerminalView::new( @@ -1186,10 +1193,21 @@ impl RunningState { let workspace = self.workspace.clone(); let weak_project = project.downgrade(); - let terminal_task = - project.update(cx, |project, cx| project.create_terminal_task(kind, cx)); + let terminal_task = project.update(cx, |project, cx| { + project.create_terminal_task( + kind, + cx, + project + .active_entry() + .and_then(|entry_id| project.worktree_id_for_entry(entry_id, cx)) + .map(|worktree_id| project::ProjectPath { + worktree_id, + path: Arc::from(std::path::Path::new("")), + }), + ) + }); let terminal_task = cx.spawn_in(window, async move |_, cx| { - let terminal = terminal_task?; + let terminal = terminal_task.await?; let terminal_view = cx.new_window_entity(|window, cx| { TerminalView::new(terminal.clone(), workspace, None, weak_project, window, cx) diff --git a/crates/language/src/toolchain.rs b/crates/language/src/toolchain.rs index 96f417aa86..5798863a00 100644 --- a/crates/language/src/toolchain.rs +++ b/crates/language/src/toolchain.rs @@ -28,6 +28,9 @@ pub struct Toolchain { pub as_json: serde_json::Value, /// shell -> script pub activation_script: FxHashMap, + // Option + // sh activate -c "user shell -l" + // check if this work with powershell } impl std::hash::Hash for Toolchain { diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index a0ab1a4bff..149db2c51c 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -94,7 +94,8 @@ impl Project { &mut self, spawn_task: SpawnInTerminal, cx: &mut Context, - ) -> Result> { + project_path_context: Option, + ) -> Task>> { let this = &mut *self; let ssh_details = this.ssh_details(cx); let path: Option> = if let Some(cwd) = &spawn_task.cwd { @@ -135,8 +136,14 @@ impl Project { env.extend(settings.env); let local_path = if is_ssh_terminal { None } else { path.clone() }; - - let (spawn_task, shell) = { + let toolchain = + project_path_context.map(|p| self.active_toolchain(p, LanguageName::new("Python"), cx)); + cx.spawn(async move |project, cx| { + let scripts = maybe!(async { + let toolchain = toolchain?.await?; + Some(toolchain.activation_script) + }) + .await; let task_state = Some(TaskState { id: spawn_task.id, full_label: spawn_task.full_label, @@ -150,94 +157,132 @@ impl Project { completion_rx, }); - env.extend(spawn_task.env); + let shell = { + env.extend(spawn_task.env); + // todo(lw): Use shell builder + let shell = match &ssh_details { + Some(ssh) => ssh.shell.clone(), + None => match &settings.shell { + Shell::Program(program) => program.clone(), + Shell::WithArguments { + program, + args: _, + title_override: _, + } => program.clone(), + Shell::System => get_system_shell(), + }, + }; + let shell_kind = ShellKind::new(&shell); + let activation_script = scripts.as_ref().and_then(|it| it.get(&shell_kind)); - match ssh_details { - Some(SshDetails { - host, - ssh_command, - envs, - path_style, - shell, - }) => { - log::debug!("Connecting to a remote server: {ssh_command:?}"); - env.entry("TERM".to_string()) - .or_insert_with(|| "xterm-256color".to_string()); - let (program, args) = wrap_for_ssh( - &shell, - &ssh_command, - spawn_task - .command - .as_ref() - .map(|command| (command, &spawn_task.args)), - path.as_deref(), - env, + match ssh_details { + Some(SshDetails { + host, + ssh_command, + envs, path_style, - None, - ); - env = HashMap::default(); - if let Some(envs) = envs { - env.extend(envs); - } - ( - task_state, + shell, + }) => { + log::debug!("Connecting to a remote server: {ssh_command:?}"); + env.entry("TERM".to_string()) + .or_insert_with(|| "xterm-256color".to_string()); + + let (program, args) = wrap_for_ssh( + &shell, + &ssh_command, + spawn_task + .command + .as_ref() + .map(|command| (command, &spawn_task.args)), + path.as_deref(), + env, + path_style, + activation_script.map(String::as_str), + ); + env = HashMap::default(); + if let Some(envs) = envs { + env.extend(envs); + } Shell::WithArguments { program, args, title_override: Some(format!("{} — Terminal", host).into()), - }, - ) - } - None => { - let shell = if let Some(program) = spawn_task.command { - Shell::WithArguments { - program, - args: spawn_task.args, - title_override: None, } - } else { - Shell::System - }; - (task_state, shell) + } + None => match activation_script { + Some(activation_script) => { + let to_run = if let Some(command) = spawn_task.command { + let command: Option> = shlex::try_quote(&command).ok(); + let args = spawn_task + .args + .iter() + .filter_map(|arg| shlex::try_quote(arg).ok()); + command.into_iter().chain(args).join(" ") + } else { + format!("exec {shell} -l") + }; + Shell::WithArguments { + program: shell, + args: vec![ + "-c".to_owned(), + format!("{activation_script}; {to_run}",), + ], + title_override: None, + } + } + None => { + if let Some(program) = spawn_task.command { + Shell::WithArguments { + program, + args: spawn_task.args, + title_override: None, + } + } else { + Shell::System + } + } + }, } - } - }; - TerminalBuilder::new( - local_path.map(|path| path.to_path_buf()), - spawn_task, - shell, - env, - settings.cursor_shape.unwrap_or_default(), - settings.alternate_scroll, - settings.max_scroll_history_lines, - is_ssh_terminal, - cx.entity_id().as_u64(), - Some(completion_tx), - cx, - None, - ) - .map(|builder| { - let terminal_handle = cx.new(|cx| builder.subscribe(cx)); + }; + project.update(cx, move |this, cx| { + TerminalBuilder::new( + local_path.map(|path| path.to_path_buf()), + task_state, + shell, + env, + settings.cursor_shape.unwrap_or_default(), + settings.alternate_scroll, + settings.max_scroll_history_lines, + is_ssh_terminal, + cx.entity_id().as_u64(), + Some(completion_tx), + cx, + None, + ) + .map(|builder| { + let terminal_handle = cx.new(|cx| builder.subscribe(cx)); - this.terminals - .local_handles - .push(terminal_handle.downgrade()); + 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; + 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(); + if let Some(index) = handles + .iter() + .position(|terminal| terminal.entity_id() == id) + { + handles.remove(index); + cx.notify(); + } + }) + .detach(); - terminal_handle + terminal_handle + }) + })? }) } @@ -278,6 +323,7 @@ impl Project { let toolchain = project_path_context.map(|p| self.active_toolchain(p, LanguageName::new("Python"), cx)); cx.spawn(async move |project, cx| { + // todo(lw): Use shell builder let shell = match &ssh_details { Some(ssh) => ssh.shell.clone(), None => match &settings.shell { diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 635e3e2ca5..133be9e623 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -95,6 +95,7 @@ pub enum VenvSettings { /// to the current working directory. We recommend overriding this /// in your project's settings, rather than globally. activate_script: Option, + // deprecate but use venv_name: Option, directories: Option>, }, diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index a673e321db..7d8377579a 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -592,7 +592,17 @@ impl TerminalPanel { .workspace .update(cx, |workspace, cx| { Self::add_center_terminal(workspace, window, cx, |project, cx| { - Task::ready(project.create_terminal_task(spawn_task, cx)) + project.create_terminal_task( + spawn_task, + cx, + project + .active_entry() + .and_then(|entry_id| project.worktree_id_for_entry(entry_id, cx)) + .map(|worktree_id| project::ProjectPath { + worktree_id, + path: Arc::from(Path::new("")), + }), + ) }) }) .unwrap_or_else(|e| Task::ready(Err(e))), @@ -731,8 +741,21 @@ impl TerminalPanel { terminal_panel.active_pane.clone() })?; let project = workspace.read_with(cx, |workspace, _| workspace.project().clone())?; - let terminal = - project.update(cx, |project, cx| project.create_terminal_task(task, cx))??; + let terminal = project + .update(cx, |project, cx| { + project.create_terminal_task( + task, + cx, + project + .active_entry() + .and_then(|entry_id| project.worktree_id_for_entry(entry_id, cx)) + .map(|worktree_id| project::ProjectPath { + worktree_id, + path: Arc::from(Path::new("")), + }), + ) + })? + .await?; let result = workspace.update_in(cx, |workspace, window, cx| { let terminal_view = Box::new(cx.new(|cx| { TerminalView::new( @@ -909,9 +932,21 @@ impl TerminalPanel { this.workspace .update(cx, |workspace, _| workspace.project().clone()) })??; - let new_terminal = project.update(cx, |project, cx| { - project.create_terminal_task(spawn_task, cx) - })??; + let new_terminal = project + .update(cx, |project, cx| { + project.create_terminal_task( + spawn_task, + cx, + project + .active_entry() + .and_then(|entry_id| project.worktree_id_for_entry(entry_id, cx)) + .map(|worktree_id| project::ProjectPath { + worktree_id, + path: Arc::from(Path::new("")), + }), + ) + })? + .await?; terminal_to_replace.update_in(cx, |terminal_to_replace, window, cx| { terminal_to_replace.set_terminal(new_terminal.clone(), window, cx); })?; diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 28f0153d9e..c293bd0e49 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -1389,7 +1389,6 @@ impl WorkspaceDb { relative_path: String, language_name: LanguageName, ) -> Result> { - return Ok(None); self.write(move |this| { let mut select = this .select_bound(sql!( @@ -1415,7 +1414,6 @@ impl WorkspaceDb { &self, workspace_id: WorkspaceId, ) -> Result)>> { - return Ok(vec![]); self.write(move |this| { let mut select = this .select_bound(sql!( @@ -1426,6 +1424,7 @@ impl WorkspaceDb { let toolchain: Vec<(String, String, u64, String, String, String)> = select(workspace_id)?; + // todo look into re-serializing these if we fix up Ok(toolchain.into_iter().filter_map(|(name, path, worktree_id, relative_worktree_path, language_name, raw_json)| Some((Toolchain { name: name.into(), path: path.into(),