Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/user-guide/src/installation/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,5 @@ For `zsh`, you must then add the following line in your `~/.zshrc` before
```zsh
fpath+=~/.zfunc
```

In Xonsh you can reuse Fish completion by installing [xontrib-fish-completer](https://github.com/xonsh/xontrib-fish-completer).
4 changes: 4 additions & 0 deletions src/cli/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ pub(crate) fn completions_help() -> String {
This installs the completion script. You may have to log out and
log back in to your shell session for the changes to take effect.

{SUBHEADER}Xonsh:{SUBHEADER:#}

In Xonsh you can reuse Fish completion by installing `xontrib-fish-completer`.

{SUBHEADER}Zsh:{SUBHEADER:#}

ZSH completions are commonly stored in any directory listed in
Expand Down
1 change: 1 addition & 0 deletions src/cli/self_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ This is usually done by running one of the following (note the leading DOT):
source $"{cargo_home_nushell}/env.nu" # For nushell
source "{cargo_home}/env.tcsh" # For tcsh
. "{cargo_home}/env.ps1" # For pwsh
source "{cargo_home}/env.xsh" # For xonsh
"#
};
}
Expand Down
1 change: 1 addition & 0 deletions src/cli/self_update/env.xsh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$PATH.append(_cargo_bin) if (_cargo_bin := '{cargo_bin}') not in $PATH else None
56 changes: 56 additions & 0 deletions src/cli/self_update/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ fn enumerate_shells() -> Vec<Shell> {
Box::new(Nu),
Box::new(Tcsh),
Box::new(Pwsh),
Box::new(Xonsh),
]
}

Expand Down Expand Up @@ -445,6 +446,61 @@ impl UnixShell for Pwsh {
}
}

struct Xonsh;

impl UnixShell for Xonsh {
fn does_exist(&self, process: &Process) -> bool {
process.var("XONSHRC").is_ok() || utils::find_cmd(&["xonsh"], process).is_some()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe mirroring fish's implementation would be better here. Is there a particular reason to check for XONSHRC instead?

Copy link
Author

@anki-code anki-code Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of xonsh is not POSIX compatible it keeps path to bash or zsh in the SHELL env var for compatibility (dependent on OS etc). So we need to detect here that we're in xonsh. Checking for XONSHRC env variable is good enough.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anki-code It should be noted however that $SHELL, which usually signifies the login shell, can be set to a non-POSIX shell. This is already the case for other shells such as fish in this file. What do you think about that?

PS: I agree that most of the time this won't matter since the second catch-all will usually trigger.

Copy link
Author

@anki-code anki-code Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rami3l The big picture:

  1. $SHELL can contain any shell and xonsh can be installed as a login shell as well and can set the path to xonsh to $SHELL.
  2. But most of tools in the world expect that $SHELL is POSIX. They blindfold grab the value of $SHELL and run POSIX commands there and have errors as result and this can follows to unexpected consequences. This is why it's preferred way to keep POSIX shell in the $SHELL forever.
  3. Some shells like zsh and maybe fish has POSIX-compatible mode. They detect POSIX commands and run them mostly without errors. This is why they set itselfs to the $SHELL.
  4. So this is why using $SHELL value is not the path for shell checking.

Copy link
Member

@rami3l rami3l Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most of tools in the world expect that $SHELL is POSIX

@anki-code I doubt whether that is the case.

I use fish as my login shell and it doesn't have a POSIX-compatibility mode, so I think myself can count as a data point. From my experience, shell script writers would trust sh as authentically POSIX rather than $SHELL.

Also, a quick GitHub global search seems to support this idea.

However, I guess it's okay to merge it while leaving this thread open. We can always come back when either approach is proven suboptimal, and then we can replace the existing with yours if applicable, for example.

Copy link
Author

@anki-code anki-code Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use fish as my login shell

You're lucky. My first result from search shows that there is no support for non-posix + support fish so I believe this code will fail or have consequences for non-posix.

Try next examples from search and you'll see that people have no thoughts about this could be another shell. So the probability to have fail with non POSIX in $SHELL exists. I read a report where someone catch Linux distro freezing after rebooting with new $SHELL. So if it's work to you you're lucky. Or may be fish as pretty old shell has more support.

}

fn rcfiles(&self, process: &Process) -> Vec<PathBuf> {
let mut paths = vec![];

if let Ok(p) = process.var("XDG_CONFIG_HOME") {
let mut p = PathBuf::from(p);
p.extend(["xonsh", "rc.xsh"]);
paths.push(p);
}

if let Some(mut p) = process.home_dir() {
p.extend([".config", "xonsh", "rc.xsh"]);
paths.push(p);
}

if let Some(home) = process.home_dir() {
paths.push(home.join(".xonshrc"));
}

paths
}

fn update_rcs(&self, process: &Process) -> Vec<PathBuf> {
// The first rcfile in XDG_CONFIG_HOME takes precedence.
match self.rcfiles(process).into_iter().next() {
Some(path) => vec![path],
None => vec![],
}
}

fn env_script(&self) -> ShellScript {
ShellScript {
name: "env.xsh",
content: include_str!("env.xsh"),
}
}

fn source_string(&self, process: &Process) -> Result<String> {
Ok(format!(
r#"source "{}/env.xsh""#,
self.cargo_home_str(process)?
))
}

fn cargo_home_str(&self, process: &Process) -> Result<Cow<'static, str>> {
cargo_home_str_with_home("$HOME", process)
}
}

pub(crate) fn legacy_paths(process: &Process) -> impl Iterator<Item = PathBuf> + '_ {
let zprofiles = Zsh::zdotdir(process)
.into_iter()
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.