Alexandre Pasmantier a6a73c5bb3
refactor(shell): improve shell integration configuration syntax (#334)
Before:
```toml
[shell_integration.commands]
# Add your commands here. Each key is a command that will trigger tv with the
# corresponding channel as value.
# Example: say you want the following prompts to trigger the following channels
# when pressing <CTRL-T>:
#          `git checkout` should trigger the `git-branches` channel
#          `ls`           should trigger the `dirs` channel
#          `cat`          should trigger the `files` channel
#
# You would add the following to your configuration file:
# ```
# [shell_integration.commands]
# "git checkout" = "git-branch"
# "ls" = "dirs"
# "cat" = "files"
# ```

# environment variables
"export" = "env"
"unset" = "env"

# dirs channel
"cd" = "dirs"
"ls" = "dirs"
"rmdir" = "dirs"

# files channel
"cat" = "files"
"less" = "files"
"head" = "files"
"tail" = "files"
"vim" = "files"
"bat" = "files"

# git-diff channel
"git add" = "git-diff"

# git-branch channel
"git checkout" = "git-branch"
"git branch -d" = "git-branch"

# docker-images channel
"docker run" = "docker-images"

# gitrepos channel
"nvim" = "git-repos"

```

After

```toml
[shell_integration.channel_triggers]
# Add your channel triggers here. Each key is a channel that will be triggered
# by the corresponding commands.
# Example: say you want the following commands to trigger the following channels
# when pressing <CTRL-T>:
#          `git checkout`  should trigger the `git-branches` channel
#          `ls`            should trigger the `dirs` channel
#          `cat` and `cp`  should trigger the `files` channel
#
# You would add the following to your configuration file:
# ```
# [shell_integration.channel_triggers]
# "git-branches" = ["git checkout"]
# "dirs" = ["ls"]
# "files" = ["cat", "cp"]
# ```
"env" = ["export", "unset"]
"dirs" = ["cd", "ls", "rmdir"]
"files" = ["cat", "less", "head", "tail", "vim", "bat"]
"git-diff" = ["git add"]
"git-branch" = ["git checkout", "git branch -d"]
"docker-images" = ["docker run"]
"git-repos" = ["nvim"]
```
2025-02-03 00:03:09 +01:00

137 lines
4.4 KiB
Rust

use std::env;
use std::io::{stdout, BufWriter, IsTerminal, Write};
use std::path::Path;
use std::process::exit;
use anyhow::Result;
use clap::Parser;
use tracing::{debug, error, info};
use television::app::App;
use television::channels::{
entry::PreviewType, stdin::Channel as StdinChannel, TelevisionChannel,
};
use television::cli::{
guess_channel_from_prompt, list_channels, Cli, ParsedCliChannel,
PostProcessedCli,
};
use television::config::{Config, ConfigEnv};
use television::utils::shell::render_autocomplete_script_template;
use television::utils::{
shell::{completion_script, Shell},
stdin::is_readable_stdin,
};
#[tokio::main(flavor = "multi_thread")]
async fn main() -> Result<()> {
television::errors::init()?;
television::logging::init()?;
let args: PostProcessedCli = Cli::parse().into();
debug!("{:?}", args);
let mut config = Config::new(&ConfigEnv::init()?)?;
if let Some(command) = args.command {
match command {
television::cli::Command::ListChannels => {
list_channels();
exit(0);
}
television::cli::Command::InitShell { shell } => {
let target_shell = Shell::from(shell);
// the completion scripts for the various shells are templated
// so that it's possible to override the keybindings triggering
// shell autocomplete and command history in tv
let script = render_autocomplete_script_template(
target_shell,
completion_script(target_shell)?,
&config.shell_integration,
)?;
println!("{script}");
exit(0);
}
}
}
config.config.tick_rate =
args.tick_rate.unwrap_or(config.config.tick_rate);
if args.no_preview {
config.ui.show_preview_panel = false;
}
if let Some(working_directory) = args.working_directory {
let path = Path::new(&working_directory);
if !path.exists() {
error!(
"Working directory \"{}\" does not exist",
&working_directory
);
println!(
"Error: Working directory \"{}\" does not exist",
&working_directory
);
exit(1);
}
env::set_current_dir(path)?;
}
match App::new(
{
if is_readable_stdin() {
debug!("Using stdin channel");
TelevisionChannel::Stdin(StdinChannel::new(
args.preview_command.map(PreviewType::Command),
))
} else if let Some(prompt) = args.autocomplete_prompt {
let channel = guess_channel_from_prompt(
&prompt,
&config.shell_integration.commands,
)?;
debug!("Using guessed channel: {:?}", channel);
match channel {
ParsedCliChannel::Builtin(c) => c.to_channel(),
ParsedCliChannel::Cable(c) => {
TelevisionChannel::Cable(c.into())
}
}
} else {
debug!("Using {:?} channel", args.channel);
match args.channel {
ParsedCliChannel::Builtin(c) => c.to_channel(),
ParsedCliChannel::Cable(c) => {
TelevisionChannel::Cable(c.into())
}
}
}
},
config,
&args.passthrough_keybindings,
args.input,
) {
Ok(mut app) => {
stdout().flush()?;
let output = app.run(stdout().is_terminal()).await?;
info!("{:?}", output);
// lock stdout
let stdout_handle = stdout().lock();
let mut bufwriter = BufWriter::new(stdout_handle);
if let Some(passthrough) = output.passthrough {
writeln!(bufwriter, "{passthrough}")?;
}
if let Some(entries) = output.selected_entries {
for entry in &entries {
writeln!(bufwriter, "{}", entry.stdout_repr())?;
}
}
bufwriter.flush()?;
exit(0);
}
Err(err) => {
println!("{err:?}");
exit(1);
}
}
}