feat: implement tv option autocompletion

This commit is contained in:
Aiden Scandella 2025-07-22 13:15:59 -07:00 committed by Alex Pasmantier
parent 39610e145d
commit e588610b18
3 changed files with 93 additions and 3 deletions

10
Cargo.lock generated
View File

@ -305,6 +305,15 @@ dependencies = [
"strsim",
]
[[package]]
name = "clap_complete"
version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a"
dependencies = [
"clap",
]
[[package]]
name = "clap_derive"
version = "4.5.40"
@ -1989,6 +1998,7 @@ dependencies = [
"base64",
"better-panic",
"clap",
"clap_complete",
"clap_mangen",
"clipboard-win",
"colored",

View File

@ -51,6 +51,7 @@ serde_json = "1.0.140"
colored = "3.0.0"
serde_with = "3.13.0"
which = "8.0.0"
clap_complete = "4.5.55"
# target specific dependencies

View File

@ -1,10 +1,11 @@
use crate::{
cli::args::Shell as CliShell,
cli::args::{Cli, Shell as CliShell},
config::shell_integration::ShellIntegrationConfig,
};
use anyhow::Result;
use clap::CommandFactory;
use std::fmt::Display;
use tracing::debug;
use tracing::{debug, warn};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Shell {
@ -28,6 +29,31 @@ impl Default for Shell {
}
}
#[derive(Debug)]
pub enum ShellError {
UnsupportedShell(String),
}
impl TryFrom<Shell> for clap_complete::Shell {
type Error = ShellError;
fn try_from(value: Shell) -> std::result::Result<Self, Self::Error> {
match value {
Shell::Bash => Ok(clap_complete::Shell::Bash),
Shell::Zsh => Ok(clap_complete::Shell::Zsh),
Shell::Fish => Ok(clap_complete::Shell::Fish),
Shell::PowerShell => Ok(clap_complete::Shell::PowerShell),
Shell::Cmd => Err(ShellError::UnsupportedShell(
"Cmd shell is not supported for completion scripts"
.to_string(),
)),
Shell::Nu => Err(ShellError::UnsupportedShell(
"Nu shell is not supported for completion scripts".to_string(),
)),
}
}
}
impl Display for Shell {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@ -143,6 +169,7 @@ pub fn render_autocomplete_script_template(
template: &str,
config: &ShellIntegrationConfig,
) -> Result<String> {
// Custom autocomplete
let script = template
.replace(
"{tv_smart_autocomplete_keybinding}",
@ -158,7 +185,33 @@ pub fn render_autocomplete_script_template(
config.get_command_history_keybinding_character(),
)?,
);
Ok(script)
let clap_autocomplete =
render_clap_autocomplete(shell).unwrap_or_default();
Ok(clap_autocomplete + &script)
}
fn render_clap_autocomplete(shell: Shell) -> Option<String> {
// Clap autocomplete
let mut clap_autocomplete = vec![];
let mut cmd = Cli::command();
let clap_shell: clap_complete::Shell = match shell.try_into() {
Ok(shell) => shell,
Err(err) => {
warn!("Failed to convert shell {:?}: {:?}", shell, err);
return None;
}
};
clap_complete::aot::generate(
clap_shell,
&mut cmd,
"tv", // the command defines the name as "television"
&mut clap_autocomplete,
);
String::from_utf8(clap_autocomplete).ok()
}
#[cfg(test)]
@ -208,4 +261,30 @@ mod tests {
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Ctrl-s");
}
#[test]
fn test_zsh_clap_completion() {
let shell = Shell::Zsh;
let result = render_autocomplete_script_template(
shell,
"",
&ShellIntegrationConfig::default(),
);
assert!(result.is_ok());
let result = result.unwrap();
assert!(result.contains("compdef _tv tv"));
}
#[test]
fn test_unsupported_clap_completion() {
let shell = Shell::Nu;
let result = render_autocomplete_script_template(
shell,
"",
&ShellIntegrationConfig::default(),
);
assert!(result.is_ok());
let result = result.unwrap();
assert!(result.is_empty());
}
}