Skip to content
Open
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
6 changes: 5 additions & 1 deletion docs/developers/shell-integration-local.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ In order to develop locally on the shell integration scripts, here are a couple
```sh
just generate-dev-shell-integration zsh
```
(or `fish`, `bash`, etc. depending on the shell you are using)
(or `fish`, `bash`, `xonsh`, etc. depending on the shell you are using)
3. Source the generated script in your shell:
```sh
source dev_shell_integration.zsh
```
For xonsh, run:
```xonsh
source dev_shell_integration.xonsh
```
4. Test the changes by using the shell integration keybindings or commands in your terminal.
22 changes: 22 additions & 0 deletions docs/user-guide/04-shell-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ mkdir ($nu.data-dir | path join "vendor/autoload")
tv init nu | save -f ($nu.data-dir | path join "vendor/autoload/tv.nu")
```

### Xonsh

To enable shell integration for xonsh, add this to your `~/.xonshrc` file:

```xonsh
execx($(tv init xonsh), "exec", __xonsh__.ctx, filename="television")
```

Then restart your shell.

### PowerShell

To enable shell integration for PowerShell, run:
Expand Down Expand Up @@ -162,6 +172,18 @@ Then add to your PowerShell profile:
. $HOME/.config/television/shell/integration.ps1
```

#### Xonsh

```shell
tv init xonsh > ~/.config/television/shell/integration.xsh
```

Then add to your `~/.xonshrc` file:

```xonsh
source ~/.config/television/shell/integration.xsh
```

For all shells you'll have to restart it (or similar) to integrate the changes.

### Recipes
Expand Down
1 change: 1 addition & 0 deletions television/cli/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,7 @@ pub enum Shell {
PowerShell,
Cmd,
Nu,
Xonsh,
}

#[derive(Debug, Clone, Copy, PartialEq, ValueEnum)]
Expand Down
76 changes: 76 additions & 0 deletions television/utils/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub enum Shell {
Psh,
Cmd,
Nu,
Xonsh,
}

impl Default for Shell {
Expand Down Expand Up @@ -54,6 +55,10 @@ impl TryFrom<Shell> for clap_complete::Shell {
Shell::Nu => Err(ShellError::UnsupportedShell(
"Nu shell is not supported for completion scripts".to_string(),
)),
Shell::Xonsh => Err(ShellError::UnsupportedShell(
"Xonsh shell is not supported for completion scripts"
.to_string(),
)),
}
}
}
Expand All @@ -67,6 +72,7 @@ impl Display for Shell {
Shell::Psh => write!(f, "powershell"),
Shell::Cmd => write!(f, "cmd"),
Shell::Nu => write!(f, "nu"),
Shell::Xonsh => write!(f, "xonsh"),
}
}
}
Expand All @@ -87,6 +93,8 @@ impl TryFrom<&str> for Shell {
Ok(Shell::Psh)
} else if value.contains("cmd") {
Ok(Shell::Cmd)
} else if value.contains("xonsh") {
Ok(Shell::Xonsh)
} else if value.contains("nu") {
Ok(Shell::Nu)
} else {
Expand Down Expand Up @@ -140,6 +148,7 @@ impl Shell {
Shell::Psh => "powershell",
Shell::Cmd => "cmd",
Shell::Nu => "nu",
Shell::Xonsh => "xonsh",
}
}
}
Expand All @@ -153,6 +162,7 @@ impl From<CliShell> for Shell {
CliShell::PowerShell => Shell::Psh,
CliShell::Cmd => Shell::Cmd,
CliShell::Nu => Shell::Nu,
CliShell::Xonsh => Shell::Xonsh,
}
}
}
Expand All @@ -166,6 +176,7 @@ impl From<&CliShell> for Shell {
CliShell::PowerShell => Shell::Psh,
CliShell::Cmd => Shell::Cmd,
CliShell::Nu => Shell::Nu,
CliShell::Xonsh => Shell::Xonsh,
}
}
}
Expand All @@ -175,6 +186,7 @@ const COMPLETION_BASH: &str = include_str!("shell/completion.bash");
const COMPLETION_FISH: &str = include_str!("shell/completion.fish");
const COMPLETION_NU: &str = include_str!("shell/completion.nu");
const COMPLETION_POWERSHELL: &str = include_str!("shell/completion.ps1");
const COMPLETION_XONSH: &str = include_str!("shell/completion.xsh");

// create the appropriate key binding for each supported shell
pub fn ctrl_keybinding(shell: Shell, character: char) -> Result<String> {
Expand All @@ -190,6 +202,14 @@ pub fn ctrl_keybinding(shell: Shell, character: char) -> Result<String> {
}
}
Shell::Nu => Ok(format!(r"Ctrl-{character}")),
Shell::Xonsh => {
if character == ' ' {
Ok("c-space".to_string())
} else {
let lower_char = character.to_ascii_lowercase();
Ok(format!("c-{lower_char}"))
}
}
Shell::Psh => Ok(format!(r"Ctrl+{}", character.to_ascii_lowercase())),
Shell::Cmd => {
anyhow::bail!("This shell is not yet supported: {:?}", shell)
Expand All @@ -204,6 +224,7 @@ pub fn completion_script(shell: Shell) -> Result<&'static str> {
Shell::Fish => Ok(COMPLETION_FISH),
Shell::Nu => Ok(COMPLETION_NU),
Shell::Psh => Ok(COMPLETION_POWERSHELL),
Shell::Xonsh => Ok(COMPLETION_XONSH),
Shell::Cmd => {
anyhow::bail!("This shell is not yet supported: {:?}", shell)
}
Expand Down Expand Up @@ -319,6 +340,20 @@ mod tests {
assert_eq!(result.unwrap(), "Ctrl-s");
}

#[test]
fn test_xonsh_ctrl_keybinding() {
let shell = Shell::Xonsh;
assert_eq!(ctrl_keybinding(shell, 'T').unwrap(), "c-t");
assert_eq!(ctrl_keybinding(shell, ' ').unwrap(), "c-space");
}

#[test]
fn test_xonsh_shell_metadata() {
assert_eq!(Shell::try_from("/usr/bin/xonsh").unwrap(), Shell::Xonsh);
assert_eq!(Shell::Xonsh.executable(), "xonsh");
assert_eq!(Shell::Xonsh.to_string(), "xonsh");
}

#[test]
fn test_zsh_clap_completion() {
let shell = Shell::Zsh;
Expand All @@ -345,6 +380,17 @@ mod tests {
assert!(result.is_empty());
}

#[test]
fn test_xonsh_clap_completion_unsupported() {
let result = render_autocomplete_script_template(
Shell::Xonsh,
"",
&ShellIntegrationConfig::default(),
);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}

#[test]
fn test_powershell_clap_completion() {
let shell = Shell::Psh;
Expand All @@ -369,4 +415,34 @@ mod tests {
assert!(script.contains("Invoke-TvShellHistory"));
assert!(script.contains("Set-PSReadLineKeyHandler"));
}

#[test]
fn test_xonsh_completion_script() {
let script = completion_script(Shell::Xonsh).unwrap();
assert!(script.contains("prompt_toolkit"));
assert!(script.contains(r#""best""#));
assert!(script.contains("from xonsh.events import events"));
assert!(script.contains("@events.on_ptk_create"));
assert!(script.contains("eager=True"));
assert!(script.contains("responds_to_cpr = False"));
assert!(script.contains("run_in_terminal"));
assert!(script.contains("_tv_unquote_token"));
assert!(!script.contains("stderr=subprocess.DEVNULL"));
assert!(!script.contains(r#""--inline""#));
assert!(script.contains("__xonsh__.history"));
assert!(script.contains("all_items(newest_first=True)"));
assert!(script.contains("right = text[cursor:]"));
assert!(!script.contains("xonsh-history"));
assert!(script.contains("tv_smart_autocomplete"));
assert!(script.contains("tv_shell_history"));

let rendered = render_autocomplete_script_template(
Shell::Xonsh,
script,
&ShellIntegrationConfig::default(),
)
.unwrap();
assert!(!rendered.contains("{tv_smart_autocomplete_keybinding}"));
assert!(!rendered.contains("{tv_shell_history_keybinding}"));
}
}
Loading