Populate project path context on shell creation
This commit is contained in:
parent
8e11e6a03e
commit
c9eb9a9e41
9 changed files with 311 additions and 37 deletions
|
@ -13,6 +13,7 @@ use async_trait::async_trait;
|
|||
use collections::{FxHashMap, HashMap};
|
||||
use gpui::{AsyncApp, SharedString};
|
||||
use settings::WorktreeId;
|
||||
use task::ShellKind;
|
||||
|
||||
use crate::{LanguageName, ManifestName};
|
||||
|
||||
|
@ -26,7 +27,7 @@ pub struct Toolchain {
|
|||
/// Full toolchain data (including language-specific details)
|
||||
pub as_json: serde_json::Value,
|
||||
/// shell -> script
|
||||
pub startup_script: FxHashMap<String, String>,
|
||||
pub activation_script: FxHashMap<ShellKind, String>,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Toolchain {
|
||||
|
@ -36,7 +37,7 @@ impl std::hash::Hash for Toolchain {
|
|||
path,
|
||||
language_name,
|
||||
as_json: _,
|
||||
startup_script: _,
|
||||
activation_script: _,
|
||||
} = self;
|
||||
name.hash(state);
|
||||
path.hash(state);
|
||||
|
@ -51,7 +52,7 @@ impl PartialEq for Toolchain {
|
|||
path,
|
||||
language_name,
|
||||
as_json: _,
|
||||
startup_script,
|
||||
activation_script: startup_script,
|
||||
} = self;
|
||||
// Do not use as_json for comparisons; it shouldn't impact equality, as it's not user-surfaced.
|
||||
// Thus, there could be multiple entries that look the same in the UI.
|
||||
|
@ -59,7 +60,7 @@ impl PartialEq for Toolchain {
|
|||
&other.name,
|
||||
&other.path,
|
||||
&other.language_name,
|
||||
&other.startup_script,
|
||||
&other.activation_script,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use task::{TaskTemplate, TaskTemplates, VariableName};
|
||||
use task::{ShellKind, TaskTemplate, TaskTemplates, VariableName};
|
||||
use util::ResultExt;
|
||||
|
||||
pub(crate) struct PyprojectTomlManifestProvider;
|
||||
|
@ -872,14 +872,17 @@ impl ToolchainLister for PythonToolchainProvider {
|
|||
if let Some(nk) = name_and_kind {
|
||||
_ = write!(name, " {nk}");
|
||||
}
|
||||
|
||||
Some(Toolchain {
|
||||
name: name.into(),
|
||||
path: toolchain.executable.as_ref()?.to_str()?.to_owned().into(),
|
||||
language_name: LanguageName::new("Python"),
|
||||
as_json: serde_json::to_value(toolchain).ok()?,
|
||||
startup_script: std::iter::once(("fish".to_owned(), "test".to_owned()))
|
||||
.collect(),
|
||||
activation_script: std::iter::once((
|
||||
ShellKind::Fish,
|
||||
"echo python recognized a venv and injected a fish startup command"
|
||||
.to_owned(),
|
||||
))
|
||||
.collect(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
@ -1691,3 +1694,180 @@ mod tests {
|
|||
});
|
||||
}
|
||||
}
|
||||
/*
|
||||
fn python_venv_directory(
|
||||
abs_path: Arc<Path>,
|
||||
venv_settings: VenvSettings,
|
||||
cx: &Context<Project>,
|
||||
) -> Task<Option<PathBuf>> {
|
||||
cx.spawn(async move |this, cx| {
|
||||
if let Some((worktree, relative_path)) = this
|
||||
.update(cx, |this, cx| this.find_worktree(&abs_path, cx))
|
||||
.ok()?
|
||||
{
|
||||
let toolchain = this
|
||||
.update(cx, |this, cx| {
|
||||
this.active_toolchain(
|
||||
ProjectPath {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
path: relative_path.into(),
|
||||
},
|
||||
LanguageName::new("Python"),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok()?
|
||||
.await;
|
||||
|
||||
if let Some(toolchain) = toolchain {
|
||||
let toolchain_path = Path::new(toolchain.path.as_ref());
|
||||
return Some(toolchain_path.parent()?.parent()?.to_path_buf());
|
||||
}
|
||||
}
|
||||
let venv_settings = venv_settings.as_option()?;
|
||||
this.update(cx, move |this, cx| {
|
||||
if let Some(path) = this.find_venv_in_worktree(&abs_path, &venv_settings, cx) {
|
||||
return Some(path);
|
||||
}
|
||||
this.find_venv_on_filesystem(&abs_path, &venv_settings, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
})
|
||||
}
|
||||
|
||||
fn find_venv_in_worktree(
|
||||
&self,
|
||||
abs_path: &Path,
|
||||
venv_settings: &terminal_settings::VenvSettingsContent,
|
||||
cx: &App,
|
||||
) -> Option<PathBuf> {
|
||||
venv_settings
|
||||
.directories
|
||||
.iter()
|
||||
.map(|name| abs_path.join(name))
|
||||
.find(|venv_path| {
|
||||
let bin_path = venv_path.join(PYTHON_VENV_BIN_DIR);
|
||||
self.find_worktree(&bin_path, cx)
|
||||
.and_then(|(worktree, relative_path)| {
|
||||
worktree.read(cx).entry_for_path(&relative_path)
|
||||
})
|
||||
.is_some_and(|entry| entry.is_dir())
|
||||
})
|
||||
}
|
||||
|
||||
fn find_venv_on_filesystem(
|
||||
&self,
|
||||
abs_path: &Path,
|
||||
venv_settings: &terminal_settings::VenvSettingsContent,
|
||||
cx: &App,
|
||||
) -> Option<PathBuf> {
|
||||
let (worktree, _) = self.find_worktree(abs_path, cx)?;
|
||||
let fs = worktree.read(cx).as_local()?.fs();
|
||||
venv_settings
|
||||
.directories
|
||||
.iter()
|
||||
.map(|name| abs_path.join(name))
|
||||
.find(|venv_path| {
|
||||
let bin_path = venv_path.join(PYTHON_VENV_BIN_DIR);
|
||||
// One-time synchronous check is acceptable for terminal/task initialization
|
||||
smol::block_on(fs.metadata(&bin_path))
|
||||
.ok()
|
||||
.flatten()
|
||||
.map_or(false, |meta| meta.is_dir)
|
||||
})
|
||||
}
|
||||
|
||||
fn activate_script_kind(shell: Option<&str>) -> ActivateScript {
|
||||
let shell_env = std::env::var("SHELL").ok();
|
||||
let shell_path = shell.or_else(|| shell_env.as_deref());
|
||||
let shell = std::path::Path::new(shell_path.unwrap_or(""))
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("");
|
||||
match shell {
|
||||
"fish" => ActivateScript::Fish,
|
||||
"tcsh" => ActivateScript::Csh,
|
||||
"nu" => ActivateScript::Nushell,
|
||||
"powershell" | "pwsh" => ActivateScript::PowerShell,
|
||||
_ => ActivateScript::Default,
|
||||
}
|
||||
}
|
||||
|
||||
fn python_activate_command(
|
||||
&self,
|
||||
venv_base_directory: &Path,
|
||||
venv_settings: &VenvSettings,
|
||||
shell: &Shell,
|
||||
cx: &mut App,
|
||||
) -> Task<Option<String>> {
|
||||
let Some(venv_settings) = venv_settings.as_option() else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
let activate_keyword = match venv_settings.activate_script {
|
||||
terminal_settings::ActivateScript::Default => match std::env::consts::OS {
|
||||
"windows" => ".",
|
||||
_ => ".",
|
||||
},
|
||||
terminal_settings::ActivateScript::Nushell => "overlay use",
|
||||
terminal_settings::ActivateScript::PowerShell => ".",
|
||||
terminal_settings::ActivateScript::Pyenv => "pyenv",
|
||||
_ => "source",
|
||||
};
|
||||
let script_kind = if venv_settings.activate_script == terminal_settings::ActivateScript::Default
|
||||
{
|
||||
match shell {
|
||||
Shell::Program(program) => Self::activate_script_kind(Some(program)),
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args: _,
|
||||
title_override: _,
|
||||
} => Self::activate_script_kind(Some(program)),
|
||||
Shell::System => Self::activate_script_kind(None),
|
||||
}
|
||||
} else {
|
||||
venv_settings.activate_script
|
||||
};
|
||||
|
||||
let activate_script_name = match script_kind {
|
||||
terminal_settings::ActivateScript::Default | terminal_settings::ActivateScript::Pyenv => {
|
||||
"activate"
|
||||
}
|
||||
terminal_settings::ActivateScript::Csh => "activate.csh",
|
||||
terminal_settings::ActivateScript::Fish => "activate.fish",
|
||||
terminal_settings::ActivateScript::Nushell => "activate.nu",
|
||||
terminal_settings::ActivateScript::PowerShell => "activate.ps1",
|
||||
};
|
||||
|
||||
let line_ending = match std::env::consts::OS {
|
||||
"windows" => "\r",
|
||||
_ => "\n",
|
||||
};
|
||||
|
||||
if venv_settings.venv_name.is_empty() {
|
||||
let path = venv_base_directory
|
||||
.join(PYTHON_VENV_BIN_DIR)
|
||||
.join(activate_script_name)
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
let is_valid_path = self.resolve_abs_path(path.as_ref(), cx);
|
||||
cx.background_spawn(async move {
|
||||
let quoted = shlex::try_quote(&path).ok()?;
|
||||
if is_valid_path.await.is_some_and(|meta| meta.is_file()) {
|
||||
Some(format!(
|
||||
"{} {} ; clear{}",
|
||||
activate_keyword, quoted, line_ending
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Task::ready(Some(format!(
|
||||
"{activate_keyword} {activate_script_name} {name}; clear{line_ending}",
|
||||
name = venv_settings.venv_name
|
||||
)))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -9204,7 +9204,7 @@ fn python_lang(fs: Arc<FakeFs>) -> Arc<Language> {
|
|||
path: venv_path.to_string_lossy().into_owned().into(),
|
||||
language_name: LanguageName(SharedString::new_static("Python")),
|
||||
as_json: serde_json::Value::Null,
|
||||
startup_script: Default::default(),
|
||||
activation_script: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use task::{Shell, ShellBuilder, SpawnInTerminal, system_shell};
|
||||
use task::{Shell, ShellBuilder, ShellKind, SpawnInTerminal, system_shell};
|
||||
use terminal::{
|
||||
TaskState, TaskStatus, Terminal, TerminalBuilder, terminal_settings::TerminalSettings,
|
||||
};
|
||||
|
@ -293,10 +293,12 @@ impl Project {
|
|||
|
||||
let scripts = maybe!(async {
|
||||
let toolchain = toolchain?.await?;
|
||||
Some(toolchain.startup_script)
|
||||
Some(toolchain.activation_script)
|
||||
})
|
||||
.await;
|
||||
let activation_script = scripts.as_ref().and_then(|it| it.get(&shell));
|
||||
let activation_script = scripts
|
||||
.as_ref()
|
||||
.and_then(|it| it.get(&ShellKind::new(&shell)));
|
||||
let shell = {
|
||||
match ssh_details {
|
||||
Some(SshDetails {
|
||||
|
|
|
@ -20,6 +20,7 @@ use rpc::{
|
|||
proto::{self, FromProto, ToProto},
|
||||
};
|
||||
use settings::WorktreeId;
|
||||
use task::ShellKind;
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::{
|
||||
|
@ -138,7 +139,11 @@ impl ToolchainStore {
|
|||
// Do we need to convert path to native string?
|
||||
path: PathBuf::from(toolchain.path).to_proto().into(),
|
||||
as_json: serde_json::Value::from_str(&toolchain.raw_json)?,
|
||||
startup_script: toolchain.activation_script.into_iter().collect(),
|
||||
activation_script: toolchain
|
||||
.activation_script
|
||||
.into_iter()
|
||||
.map(|(k, v)| (ShellKind::new(&k), v))
|
||||
.collect(),
|
||||
language_name,
|
||||
};
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
|
@ -179,7 +184,11 @@ impl ToolchainStore {
|
|||
name: toolchain.name.into(),
|
||||
path: path.to_proto(),
|
||||
raw_json: toolchain.as_json.to_string(),
|
||||
activation_script: toolchain.startup_script.into_iter().collect(),
|
||||
activation_script: toolchain
|
||||
.activation_script
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v))
|
||||
.collect(),
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
@ -223,7 +232,11 @@ impl ToolchainStore {
|
|||
name: toolchain.name.to_string(),
|
||||
path: path.to_proto(),
|
||||
raw_json: toolchain.as_json.to_string(),
|
||||
activation_script: toolchain.startup_script.into_iter().collect(),
|
||||
activation_script: toolchain
|
||||
.activation_script
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v))
|
||||
.collect(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -452,7 +465,11 @@ impl RemoteToolchainStore {
|
|||
name: toolchain.name.into(),
|
||||
path: path.to_proto(),
|
||||
raw_json: toolchain.as_json.to_string(),
|
||||
activation_script: toolchain.startup_script.into_iter().collect(),
|
||||
activation_script: toolchain
|
||||
.activation_script
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v))
|
||||
.collect(),
|
||||
}),
|
||||
path: Some(project_path.path.to_string_lossy().into_owned()),
|
||||
})
|
||||
|
@ -505,7 +522,11 @@ impl RemoteToolchainStore {
|
|||
.to_string()
|
||||
.into(),
|
||||
as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
|
||||
startup_script: toolchain.activation_script.into_iter().collect(),
|
||||
activation_script: toolchain
|
||||
.activation_script
|
||||
.into_iter()
|
||||
.map(|(k, v)| (ShellKind::new(&k), v))
|
||||
.collect(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
@ -562,7 +583,11 @@ impl RemoteToolchainStore {
|
|||
.to_string()
|
||||
.into(),
|
||||
as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
|
||||
startup_script: toolchain.activation_script.into_iter().collect(),
|
||||
activation_script: toolchain
|
||||
.activation_script
|
||||
.into_iter()
|
||||
.map(|(k, v)| (ShellKind::new(&k), v))
|
||||
.collect(),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::fmt;
|
||||
|
||||
use crate::Shell;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
@ -11,6 +13,19 @@ pub enum ShellKind {
|
|||
Cmd,
|
||||
}
|
||||
|
||||
impl fmt::Display for ShellKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ShellKind::Posix => write!(f, "sh"),
|
||||
ShellKind::Csh => write!(f, "csh"),
|
||||
ShellKind::Fish => write!(f, "fish"),
|
||||
ShellKind::Powershell => write!(f, "powershell"),
|
||||
ShellKind::Nushell => write!(f, "nu"),
|
||||
ShellKind::Cmd => write!(f, "cmd"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellKind {
|
||||
pub fn system() -> Self {
|
||||
Self::new(&system_shell())
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
use std::{cmp, ops::ControlFlow, path::PathBuf, process::ExitStatus, sync::Arc, time::Duration};
|
||||
use std::{
|
||||
cmp,
|
||||
ops::ControlFlow,
|
||||
path::{Path, PathBuf},
|
||||
process::ExitStatus,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
TerminalView, default_working_directory,
|
||||
|
@ -414,22 +421,36 @@ impl TerminalPanel {
|
|||
let weak_workspace = self.workspace.clone();
|
||||
let project = workspace.project().clone();
|
||||
let active_pane = &self.active_pane;
|
||||
let working_directory = active_pane
|
||||
let terminal_view = active_pane
|
||||
.read(cx)
|
||||
.active_item()
|
||||
.and_then(|item| item.downcast::<TerminalView>())
|
||||
.map(|terminal_view| {
|
||||
let terminal = terminal_view.read(cx).terminal().read(cx);
|
||||
terminal
|
||||
.working_directory()
|
||||
.or_else(|| default_working_directory(workspace, cx))
|
||||
})
|
||||
.unwrap_or(None);
|
||||
.and_then(|item| item.downcast::<TerminalView>());
|
||||
let working_directory = terminal_view.as_ref().and_then(|terminal_view| {
|
||||
let terminal = terminal_view.read(cx).terminal().read(cx);
|
||||
terminal
|
||||
.working_directory()
|
||||
.or_else(|| default_working_directory(workspace, cx))
|
||||
});
|
||||
let is_zoomed = active_pane.read(cx).is_zoomed();
|
||||
cx.spawn_in(window, async move |panel, cx| {
|
||||
let terminal = project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_terminal_shell(working_directory, cx, None)
|
||||
.update(cx, |project, cx| match terminal_view {
|
||||
Some(view) => Task::ready(project.clone_terminal(
|
||||
&view.read(cx).terminal.clone(),
|
||||
cx,
|
||||
|| working_directory,
|
||||
)),
|
||||
None => project.create_terminal_shell(
|
||||
working_directory,
|
||||
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("")),
|
||||
}),
|
||||
),
|
||||
})
|
||||
.ok()?
|
||||
.await
|
||||
|
@ -770,7 +791,17 @@ impl TerminalPanel {
|
|||
let project = workspace.read_with(cx, |workspace, _| workspace.project().clone())?;
|
||||
let terminal = project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_terminal_shell(cwd, cx, None)
|
||||
project.create_terminal_shell(
|
||||
cwd,
|
||||
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| {
|
||||
|
|
|
@ -205,7 +205,17 @@ impl TerminalView {
|
|||
) {
|
||||
let working_directory = default_working_directory(workspace, cx);
|
||||
TerminalPanel::add_center_terminal(workspace, window, cx, |project, cx| {
|
||||
project.create_terminal_shell(working_directory, cx, None)
|
||||
project.create_terminal_shell(
|
||||
working_directory,
|
||||
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("")),
|
||||
}),
|
||||
)
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
@ -1490,7 +1500,17 @@ impl SerializableItem for TerminalView {
|
|||
|
||||
let terminal = project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_terminal_shell(cwd, cx, None)
|
||||
project.create_terminal_shell(
|
||||
cwd,
|
||||
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?;
|
||||
cx.update(|window, cx| {
|
||||
|
|
|
@ -1389,6 +1389,7 @@ impl WorkspaceDb {
|
|||
relative_path: String,
|
||||
language_name: LanguageName,
|
||||
) -> Result<Option<Toolchain>> {
|
||||
return Ok(None);
|
||||
self.write(move |this| {
|
||||
let mut select = this
|
||||
.select_bound(sql!(
|
||||
|
@ -1404,8 +1405,7 @@ impl WorkspaceDb {
|
|||
path: path.into(),
|
||||
language_name,
|
||||
as_json: serde_json::Value::from_str(&raw_json).ok()?,
|
||||
// todo refresh?
|
||||
startup_script: Default::default(),
|
||||
activation_script: Default::default(),
|
||||
})))
|
||||
})
|
||||
.await
|
||||
|
@ -1415,6 +1415,7 @@ impl WorkspaceDb {
|
|||
&self,
|
||||
workspace_id: WorkspaceId,
|
||||
) -> Result<Vec<(Toolchain, WorktreeId, Arc<Path>)>> {
|
||||
return Ok(vec![]);
|
||||
self.write(move |this| {
|
||||
let mut select = this
|
||||
.select_bound(sql!(
|
||||
|
@ -1430,8 +1431,7 @@ impl WorkspaceDb {
|
|||
path: path.into(),
|
||||
language_name: LanguageName::new(&language_name),
|
||||
as_json: serde_json::Value::from_str(&raw_json).ok()?,
|
||||
// todo refresh?
|
||||
startup_script: Default::default(),
|
||||
activation_script: Default::default(),
|
||||
}, WorktreeId::from_proto(worktree_id), Arc::from(relative_worktree_path.as_ref())))).collect())
|
||||
})
|
||||
.await
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue