mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-06 03:25:23 +00:00
feat(shell): add fallback channel to the config for smart autocomplete
This commit is contained in:
parent
97314d629a
commit
f78795cbb8
@ -109,6 +109,10 @@ toggle_preview = "ctrl-o"
|
||||
# E.g. typing `git checkout <CTRL-T>` will open television with a list of
|
||||
# branches to choose from.
|
||||
|
||||
[shell_integration]
|
||||
# This specifies the default fallback channel if no other channel is matched.
|
||||
fallback_channel = "files"
|
||||
|
||||
[shell_integration.channel_triggers]
|
||||
# Add your channel triggers here. Each key is a channel that will be triggered
|
||||
# by the corresponding commands.
|
||||
|
@ -136,6 +136,15 @@ pub enum ParsedCliChannel {
|
||||
Cable(CableChannelPrototype),
|
||||
}
|
||||
|
||||
impl ParsedCliChannel {
|
||||
pub fn name(&self) -> String {
|
||||
match self {
|
||||
Self::Builtin(c) => c.to_string(),
|
||||
Self::Cable(c) => c.name.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CLI_KEYBINDINGS_DELIMITER: char = ';';
|
||||
|
||||
/// Parse the keybindings string into a hashmap of key -> action.
|
||||
@ -154,7 +163,7 @@ fn parse_keybindings(cli_keybindings: &str) -> Result<KeyBindings> {
|
||||
toml::from_str(&toml_definition).map_err(|e| anyhow!(e))
|
||||
}
|
||||
|
||||
fn parse_channel(channel: &str) -> Result<ParsedCliChannel> {
|
||||
pub fn parse_channel(channel: &str) -> Result<ParsedCliChannel> {
|
||||
let cable_channels = cable::load_cable_channels().unwrap_or_default();
|
||||
// try to parse the channel as a cable channel
|
||||
cable_channels
|
||||
@ -165,7 +174,7 @@ fn parse_channel(channel: &str) -> Result<ParsedCliChannel> {
|
||||
// try to parse the channel as a builtin channel
|
||||
CliTvChannel::try_from(channel)
|
||||
.map(ParsedCliChannel::Builtin)
|
||||
.map_err(|_| anyhow!("Unknown channel: {}", channel))
|
||||
.map_err(|_| anyhow!("Unknown channel: '{}'", channel))
|
||||
},
|
||||
|(_, v)| Ok(ParsedCliChannel::Cable(v.clone())),
|
||||
)
|
||||
@ -223,15 +232,13 @@ pub fn list_channels() {
|
||||
pub fn guess_channel_from_prompt(
|
||||
prompt: &str,
|
||||
command_mapping: &FxHashMap<String, String>,
|
||||
fallback_channel: ParsedCliChannel,
|
||||
) -> Result<ParsedCliChannel> {
|
||||
debug!("Guessing channel from prompt: {}", prompt);
|
||||
// git checkout -qf
|
||||
// --- -------- --- <---------
|
||||
if prompt.trim().is_empty() {
|
||||
return match command_mapping.get("") {
|
||||
Some(channel) => parse_channel(channel),
|
||||
None => Err(anyhow!("No channel found for prompt: {}", prompt)),
|
||||
};
|
||||
return Ok(fallback_channel);
|
||||
}
|
||||
let rev_prompt_words = prompt.split_whitespace().rev();
|
||||
let mut stack = Vec::new();
|
||||
@ -259,7 +266,9 @@ pub fn guess_channel_from_prompt(
|
||||
// reset the stack
|
||||
stack.clear();
|
||||
}
|
||||
Err(anyhow!("No channel found for prompt: {}", prompt))
|
||||
|
||||
debug!("No match found, falling back to default channel");
|
||||
Ok(fallback_channel)
|
||||
}
|
||||
|
||||
const VERSION_MESSAGE: &str = env!("CARGO_PKG_VERSION");
|
||||
@ -458,4 +467,61 @@ mod tests {
|
||||
|
||||
assert_eq!(post_processed_cli.keybindings, Some(expected));
|
||||
}
|
||||
|
||||
fn guess_channel_from_prompt_setup(
|
||||
) -> (FxHashMap<String, String>, ParsedCliChannel) {
|
||||
let mut command_mapping = FxHashMap::default();
|
||||
command_mapping.insert("vim".to_string(), "files".to_string());
|
||||
command_mapping.insert("export".to_string(), "env".to_string());
|
||||
|
||||
(
|
||||
command_mapping,
|
||||
ParsedCliChannel::Builtin(CliTvChannel::Env),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guess_channel_from_prompt_present() {
|
||||
let prompt = "vim -d file1";
|
||||
|
||||
let (command_mapping, fallback) = guess_channel_from_prompt_setup();
|
||||
|
||||
let channel =
|
||||
guess_channel_from_prompt(prompt, &command_mapping, fallback)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(channel.name(), "files");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guess_channel_from_prompt_fallback() {
|
||||
let prompt = "git checkout ";
|
||||
|
||||
let (command_mapping, fallback) = guess_channel_from_prompt_setup();
|
||||
|
||||
let channel = guess_channel_from_prompt(
|
||||
prompt,
|
||||
&command_mapping,
|
||||
fallback.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(channel, fallback);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guess_channel_from_prompt_empty() {
|
||||
let prompt = "";
|
||||
|
||||
let (command_mapping, fallback) = guess_channel_from_prompt_setup();
|
||||
|
||||
let channel = guess_channel_from_prompt(
|
||||
prompt,
|
||||
&command_mapping,
|
||||
fallback.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(channel, fallback);
|
||||
}
|
||||
}
|
||||
|
@ -157,6 +157,12 @@ impl Config {
|
||||
mut default: Config,
|
||||
mut user: Config,
|
||||
) -> Config {
|
||||
// merge shell integration fallback channel with default fallback channel
|
||||
if user.shell_integration.fallback_channel.is_empty() {
|
||||
user.shell_integration
|
||||
.fallback_channel
|
||||
.clone_from(&default.shell_integration.fallback_channel);
|
||||
}
|
||||
// merge shell integration triggers with commands
|
||||
default.shell_integration.merge_triggers();
|
||||
user.shell_integration.merge_triggers();
|
||||
|
@ -15,6 +15,7 @@ pub struct ShellIntegrationConfig {
|
||||
pub commands: FxHashMap<String, String>,
|
||||
/// {channel: [commands]}
|
||||
pub channel_triggers: FxHashMap<String, Vec<String>>,
|
||||
pub fallback_channel: String,
|
||||
pub keybindings: FxHashMap<String, String>,
|
||||
}
|
||||
|
||||
|
@ -149,13 +149,28 @@ async fn poll_event(timeout: Duration) -> bool {
|
||||
PollFuture { timeout }.await
|
||||
}
|
||||
|
||||
fn flush_existing_events() {
|
||||
let mut counter = 0;
|
||||
while let Ok(true) = crossterm::event::poll(Duration::from_millis(0)) {
|
||||
if let Ok(crossterm::event::Event::Key(_)) = crossterm::event::read() {
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
if counter > 0 {
|
||||
debug!("Flushed {} existing events", counter);
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop {
|
||||
// FIXME: this init parameter doesn't seem to be used anymore
|
||||
pub fn new(tick_rate: f64, init: bool) -> Self {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let tick_interval = Duration::from_secs_f64(1.0 / tick_rate);
|
||||
|
||||
let (abort, mut abort_recv) = mpsc::unbounded_channel();
|
||||
|
||||
flush_existing_events();
|
||||
|
||||
if init {
|
||||
//let mut reader = crossterm::event::EventStream::new();
|
||||
tokio::spawn(async move {
|
||||
|
@ -6,6 +6,7 @@ use std::process::exit;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use television::channels::cable::PreviewKind;
|
||||
use television::cli::parse_channel;
|
||||
use television::utils::clipboard::CLIPBOARD;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
@ -31,28 +32,37 @@ async fn main() -> Result<()> {
|
||||
television::errors::init()?;
|
||||
television::logging::init()?;
|
||||
|
||||
// post-process the CLI arguments
|
||||
let args: PostProcessedCli = Cli::parse().into();
|
||||
debug!("{:?}", args);
|
||||
debug!("\n\n==== NEW SESSION =====\n");
|
||||
|
||||
// process the CLI arguments
|
||||
let cli = Cli::parse();
|
||||
debug!("CLI: {:?}", cli);
|
||||
let args: PostProcessedCli = cli.into();
|
||||
debug!("PostProcessedCli: {:?}", args);
|
||||
|
||||
// load the configuration file
|
||||
debug!("Loading configuration...");
|
||||
let mut config = Config::new(&ConfigEnv::init()?)?;
|
||||
|
||||
// optionally handle subcommands
|
||||
debug!("Handling subcommands...");
|
||||
args.command.as_ref().map(handle_subcommands);
|
||||
|
||||
// optionally change the working directory
|
||||
args.working_directory.as_ref().map(set_current_dir);
|
||||
|
||||
// optionally override configuration values with CLI arguments
|
||||
debug!("Applying CLI overrides...");
|
||||
apply_cli_overrides(&args, &mut config);
|
||||
|
||||
// determine the channel to use based on the CLI arguments and configuration
|
||||
debug!("Determining channel...");
|
||||
let channel =
|
||||
determine_channel(args.clone(), &config, is_readable_stdin())?;
|
||||
|
||||
CLIPBOARD.with(<_>::default);
|
||||
|
||||
debug!("Creating application...");
|
||||
let mut app =
|
||||
App::new(channel, config, &args.passthrough_keybindings, args.input);
|
||||
stdout().flush()?;
|
||||
@ -141,9 +151,11 @@ pub fn determine_channel(
|
||||
},
|
||||
)))
|
||||
} else if let Some(prompt) = args.autocomplete_prompt {
|
||||
debug!("Using autocomplete prompt: {:?}", prompt);
|
||||
let channel = guess_channel_from_prompt(
|
||||
&prompt,
|
||||
&config.shell_integration.commands,
|
||||
parse_channel(&config.shell_integration.fallback_channel)?,
|
||||
)?;
|
||||
debug!("Using guessed channel: {:?}", channel);
|
||||
match channel {
|
||||
@ -217,6 +229,7 @@ mod tests {
|
||||
let mut config = Config {
|
||||
shell_integration:
|
||||
television::config::shell_integration::ShellIntegrationConfig {
|
||||
fallback_channel: "files".to_string(),
|
||||
commands: FxHashMap::default(),
|
||||
channel_triggers: {
|
||||
let mut m = FxHashMap::default();
|
||||
|
Loading…
x
Reference in New Issue
Block a user