Skip to content

Commit 4302abf

Browse files
committed
Populate project path context on shell creation
1 parent 395abad commit 4302abf

File tree

9 files changed

+330
-45
lines changed

9 files changed

+330
-45
lines changed

crates/language/src/toolchain.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use async_trait::async_trait;
1313
use collections::{FxHashMap, HashMap};
1414
use gpui::{AsyncApp, SharedString};
1515
use settings::WorktreeId;
16+
use task::ShellKind;
1617

1718
use crate::{LanguageName, ManifestName};
1819

@@ -26,7 +27,7 @@ pub struct Toolchain {
2627
/// Full toolchain data (including language-specific details)
2728
pub as_json: serde_json::Value,
2829
/// shell -> script
29-
pub startup_script: FxHashMap<String, String>,
30+
pub activation_script: FxHashMap<ShellKind, String>,
3031
}
3132

3233
impl std::hash::Hash for Toolchain {
@@ -36,7 +37,7 @@ impl std::hash::Hash for Toolchain {
3637
path,
3738
language_name,
3839
as_json: _,
39-
startup_script: _,
40+
activation_script: _,
4041
} = self;
4142
name.hash(state);
4243
path.hash(state);
@@ -51,15 +52,15 @@ impl PartialEq for Toolchain {
5152
path,
5253
language_name,
5354
as_json: _,
54-
startup_script,
55+
activation_script: startup_script,
5556
} = self;
5657
// Do not use as_json for comparisons; it shouldn't impact equality, as it's not user-surfaced.
5758
// Thus, there could be multiple entries that look the same in the UI.
5859
(name, path, language_name, startup_script).eq(&(
5960
&other.name,
6061
&other.path,
6162
&other.language_name,
62-
&other.startup_script,
63+
&other.activation_script,
6364
))
6465
}
6566
}

crates/languages/src/python.rs

Lines changed: 184 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use std::{
3535
path::{Path, PathBuf},
3636
sync::Arc,
3737
};
38-
use task::{TaskTemplate, TaskTemplates, VariableName};
38+
use task::{ShellKind, TaskTemplate, TaskTemplates, VariableName};
3939
use util::ResultExt;
4040

4141
pub(crate) struct PyprojectTomlManifestProvider;
@@ -872,14 +872,17 @@ impl ToolchainLister for PythonToolchainProvider {
872872
if let Some(nk) = name_and_kind {
873873
_ = write!(name, " {nk}");
874874
}
875-
876875
Some(Toolchain {
877876
name: name.into(),
878877
path: toolchain.executable.as_ref()?.to_str()?.to_owned().into(),
879878
language_name: LanguageName::new("Python"),
880879
as_json: serde_json::to_value(toolchain).ok()?,
881-
startup_script: std::iter::once(("fish".to_owned(), "test".to_owned()))
882-
.collect(),
880+
activation_script: std::iter::once((
881+
ShellKind::Fish,
882+
"echo python recognized a venv and injected a fish startup command"
883+
.to_owned(),
884+
))
885+
.collect(),
883886
})
884887
})
885888
.collect();
@@ -1691,3 +1694,180 @@ mod tests {
16911694
});
16921695
}
16931696
}
1697+
/*
1698+
fn python_venv_directory(
1699+
abs_path: Arc<Path>,
1700+
venv_settings: VenvSettings,
1701+
cx: &Context<Project>,
1702+
) -> Task<Option<PathBuf>> {
1703+
cx.spawn(async move |this, cx| {
1704+
if let Some((worktree, relative_path)) = this
1705+
.update(cx, |this, cx| this.find_worktree(&abs_path, cx))
1706+
.ok()?
1707+
{
1708+
let toolchain = this
1709+
.update(cx, |this, cx| {
1710+
this.active_toolchain(
1711+
ProjectPath {
1712+
worktree_id: worktree.read(cx).id(),
1713+
path: relative_path.into(),
1714+
},
1715+
LanguageName::new("Python"),
1716+
cx,
1717+
)
1718+
})
1719+
.ok()?
1720+
.await;
1721+
1722+
if let Some(toolchain) = toolchain {
1723+
let toolchain_path = Path::new(toolchain.path.as_ref());
1724+
return Some(toolchain_path.parent()?.parent()?.to_path_buf());
1725+
}
1726+
}
1727+
let venv_settings = venv_settings.as_option()?;
1728+
this.update(cx, move |this, cx| {
1729+
if let Some(path) = this.find_venv_in_worktree(&abs_path, &venv_settings, cx) {
1730+
return Some(path);
1731+
}
1732+
this.find_venv_on_filesystem(&abs_path, &venv_settings, cx)
1733+
})
1734+
.ok()
1735+
.flatten()
1736+
})
1737+
}
1738+
1739+
fn find_venv_in_worktree(
1740+
&self,
1741+
abs_path: &Path,
1742+
venv_settings: &terminal_settings::VenvSettingsContent,
1743+
cx: &App,
1744+
) -> Option<PathBuf> {
1745+
venv_settings
1746+
.directories
1747+
.iter()
1748+
.map(|name| abs_path.join(name))
1749+
.find(|venv_path| {
1750+
let bin_path = venv_path.join(PYTHON_VENV_BIN_DIR);
1751+
self.find_worktree(&bin_path, cx)
1752+
.and_then(|(worktree, relative_path)| {
1753+
worktree.read(cx).entry_for_path(&relative_path)
1754+
})
1755+
.is_some_and(|entry| entry.is_dir())
1756+
})
1757+
}
1758+
1759+
fn find_venv_on_filesystem(
1760+
&self,
1761+
abs_path: &Path,
1762+
venv_settings: &terminal_settings::VenvSettingsContent,
1763+
cx: &App,
1764+
) -> Option<PathBuf> {
1765+
let (worktree, _) = self.find_worktree(abs_path, cx)?;
1766+
let fs = worktree.read(cx).as_local()?.fs();
1767+
venv_settings
1768+
.directories
1769+
.iter()
1770+
.map(|name| abs_path.join(name))
1771+
.find(|venv_path| {
1772+
let bin_path = venv_path.join(PYTHON_VENV_BIN_DIR);
1773+
// One-time synchronous check is acceptable for terminal/task initialization
1774+
smol::block_on(fs.metadata(&bin_path))
1775+
.ok()
1776+
.flatten()
1777+
.map_or(false, |meta| meta.is_dir)
1778+
})
1779+
}
1780+
1781+
fn activate_script_kind(shell: Option<&str>) -> ActivateScript {
1782+
let shell_env = std::env::var("SHELL").ok();
1783+
let shell_path = shell.or_else(|| shell_env.as_deref());
1784+
let shell = std::path::Path::new(shell_path.unwrap_or(""))
1785+
.file_name()
1786+
.and_then(|name| name.to_str())
1787+
.unwrap_or("");
1788+
match shell {
1789+
"fish" => ActivateScript::Fish,
1790+
"tcsh" => ActivateScript::Csh,
1791+
"nu" => ActivateScript::Nushell,
1792+
"powershell" | "pwsh" => ActivateScript::PowerShell,
1793+
_ => ActivateScript::Default,
1794+
}
1795+
}
1796+
1797+
fn python_activate_command(
1798+
&self,
1799+
venv_base_directory: &Path,
1800+
venv_settings: &VenvSettings,
1801+
shell: &Shell,
1802+
cx: &mut App,
1803+
) -> Task<Option<String>> {
1804+
let Some(venv_settings) = venv_settings.as_option() else {
1805+
return Task::ready(None);
1806+
};
1807+
let activate_keyword = match venv_settings.activate_script {
1808+
terminal_settings::ActivateScript::Default => match std::env::consts::OS {
1809+
"windows" => ".",
1810+
_ => ".",
1811+
},
1812+
terminal_settings::ActivateScript::Nushell => "overlay use",
1813+
terminal_settings::ActivateScript::PowerShell => ".",
1814+
terminal_settings::ActivateScript::Pyenv => "pyenv",
1815+
_ => "source",
1816+
};
1817+
let script_kind = if venv_settings.activate_script == terminal_settings::ActivateScript::Default
1818+
{
1819+
match shell {
1820+
Shell::Program(program) => Self::activate_script_kind(Some(program)),
1821+
Shell::WithArguments {
1822+
program,
1823+
args: _,
1824+
title_override: _,
1825+
} => Self::activate_script_kind(Some(program)),
1826+
Shell::System => Self::activate_script_kind(None),
1827+
}
1828+
} else {
1829+
venv_settings.activate_script
1830+
};
1831+
1832+
let activate_script_name = match script_kind {
1833+
terminal_settings::ActivateScript::Default | terminal_settings::ActivateScript::Pyenv => {
1834+
"activate"
1835+
}
1836+
terminal_settings::ActivateScript::Csh => "activate.csh",
1837+
terminal_settings::ActivateScript::Fish => "activate.fish",
1838+
terminal_settings::ActivateScript::Nushell => "activate.nu",
1839+
terminal_settings::ActivateScript::PowerShell => "activate.ps1",
1840+
};
1841+
1842+
let line_ending = match std::env::consts::OS {
1843+
"windows" => "\r",
1844+
_ => "\n",
1845+
};
1846+
1847+
if venv_settings.venv_name.is_empty() {
1848+
let path = venv_base_directory
1849+
.join(PYTHON_VENV_BIN_DIR)
1850+
.join(activate_script_name)
1851+
.to_string_lossy()
1852+
.to_string();
1853+
1854+
let is_valid_path = self.resolve_abs_path(path.as_ref(), cx);
1855+
cx.background_spawn(async move {
1856+
let quoted = shlex::try_quote(&path).ok()?;
1857+
if is_valid_path.await.is_some_and(|meta| meta.is_file()) {
1858+
Some(format!(
1859+
"{} {} ; clear{}",
1860+
activate_keyword, quoted, line_ending
1861+
))
1862+
} else {
1863+
None
1864+
}
1865+
})
1866+
} else {
1867+
Task::ready(Some(format!(
1868+
"{activate_keyword} {activate_script_name} {name}; clear{line_ending}",
1869+
name = venv_settings.venv_name
1870+
)))
1871+
}
1872+
}
1873+
*/

crates/project/src/terminals.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use std::{
1212
path::{Path, PathBuf},
1313
sync::Arc,
1414
};
15-
use task::{DEFAULT_REMOTE_SHELL, Shell, ShellBuilder, SpawnInTerminal};
15+
use task::{DEFAULT_REMOTE_SHELL, Shell, ShellBuilder, ShellKind, SpawnInTerminal};
1616
use terminal::{
1717
TaskState, TaskStatus, Terminal, TerminalBuilder, terminal_settings::TerminalSettings,
1818
};
@@ -283,13 +283,15 @@ impl Project {
283283
Shell::System => std::env::var("SHELL").ok(),
284284
},
285285
};
286+
let shell_kind = shell.as_deref().map(ShellKind::new);
286287

287288
let scripts = maybe!(async {
288289
let toolchain = toolchain?.await?;
289-
Some(toolchain.startup_script)
290+
Some(toolchain.activation_script)
290291
})
291292
.await;
292-
let activation_script = scripts.as_ref().and_then(|it| it.get(shell.as_ref()?));
293+
let activation_script = scripts.as_ref().and_then(|it| it.get(&shell_kind?));
294+
293295
let shell = {
294296
match ssh_details {
295297
Some(SshDetails {

crates/project/src/toolchain_store.rs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use rpc::{
2020
proto::{self, FromProto, ToProto},
2121
};
2222
use settings::WorktreeId;
23+
use task::ShellKind;
2324
use util::ResultExt as _;
2425

2526
use crate::{
@@ -138,7 +139,11 @@ impl ToolchainStore {
138139
// Do we need to convert path to native string?
139140
path: PathBuf::from(toolchain.path).to_proto().into(),
140141
as_json: serde_json::Value::from_str(&toolchain.raw_json)?,
141-
startup_script: toolchain.activation_script.into_iter().collect(),
142+
activation_script: toolchain
143+
.activation_script
144+
.into_iter()
145+
.map(|(k, v)| (ShellKind::new(&k), v))
146+
.collect(),
142147
language_name,
143148
};
144149
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
@@ -179,7 +184,11 @@ impl ToolchainStore {
179184
name: toolchain.name.into(),
180185
path: path.to_proto(),
181186
raw_json: toolchain.as_json.to_string(),
182-
activation_script: toolchain.startup_script.into_iter().collect(),
187+
activation_script: toolchain
188+
.activation_script
189+
.into_iter()
190+
.map(|(k, v)| (k.to_string(), v))
191+
.collect(),
183192
}
184193
}),
185194
})
@@ -223,7 +232,11 @@ impl ToolchainStore {
223232
name: toolchain.name.to_string(),
224233
path: path.to_proto(),
225234
raw_json: toolchain.as_json.to_string(),
226-
activation_script: toolchain.startup_script.into_iter().collect(),
235+
activation_script: toolchain
236+
.activation_script
237+
.into_iter()
238+
.map(|(k, v)| (k.to_string(), v))
239+
.collect(),
227240
}
228241
})
229242
.collect::<Vec<_>>();
@@ -452,7 +465,11 @@ impl RemoteToolchainStore {
452465
name: toolchain.name.into(),
453466
path: path.to_proto(),
454467
raw_json: toolchain.as_json.to_string(),
455-
activation_script: toolchain.startup_script.into_iter().collect(),
468+
activation_script: toolchain
469+
.activation_script
470+
.into_iter()
471+
.map(|(k, v)| (k.to_string(), v))
472+
.collect(),
456473
}),
457474
path: Some(project_path.path.to_string_lossy().into_owned()),
458475
})
@@ -505,7 +522,11 @@ impl RemoteToolchainStore {
505522
.to_string()
506523
.into(),
507524
as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
508-
startup_script: toolchain.activation_script.into_iter().collect(),
525+
activation_script: toolchain
526+
.activation_script
527+
.into_iter()
528+
.map(|(k, v)| (ShellKind::new(&k), v))
529+
.collect(),
509530
})
510531
})
511532
.collect();
@@ -562,7 +583,11 @@ impl RemoteToolchainStore {
562583
.to_string()
563584
.into(),
564585
as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
565-
startup_script: toolchain.activation_script.into_iter().collect(),
586+
activation_script: toolchain
587+
.activation_script
588+
.into_iter()
589+
.map(|(k, v)| (ShellKind::new(&k), v))
590+
.collect(),
566591
})
567592
})
568593
})

0 commit comments

Comments
 (0)