refactor(shell): improve shell integration configuration syntax

This commit is contained in:
Alexandre Pasmantier 2025-02-02 12:24:30 +01:00
parent c74b47d07c
commit d3725f2cc3
5 changed files with 78 additions and 47 deletions

View File

@ -149,52 +149,30 @@ toggle_preview = "ctrl-o"
# E.g. typing `git checkout <CTRL-T>` will open television with a list of # E.g. typing `git checkout <CTRL-T>` will open television with a list of
# branches to choose from. # branches to choose from.
[shell_integration.commands] [shell_integration.channel_triggers]
# Add your commands here. Each key is a command that will trigger tv with the # Add your channel triggers here. Each key is a channel that will be triggered
# corresponding channel as value. # by the corresponding commands.
# Example: say you want the following prompts to trigger the following channels # Example: say you want the following commands to trigger the following channels
# when pressing <CTRL-T>: # when pressing <CTRL-T>:
# `git checkout` should trigger the `git-branches` channel # `git checkout` should trigger the `git-branches` channel
# `ls` should trigger the `dirs` channel # `ls` should trigger the `dirs` channel
# `cat` should trigger the `files` channel # `cat` and `cp` should trigger the `files` channel
# #
# You would add the following to your configuration file: # You would add the following to your configuration file:
# ``` # ```
# [shell_integration.commands] # [shell_integration.channel_triggers]
# "git checkout" = "git-branch" # "git-branches" = ["git checkout"]
# "ls" = "dirs" # "dirs" = ["ls"]
# "cat" = "files" # "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"]
# 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"
[shell_integration.keybindings] [shell_integration.keybindings]
# controls which key binding should trigger tv # controls which key binding should trigger tv
@ -203,4 +181,3 @@ toggle_preview = "ctrl-o"
# controls which keybinding should trigger tv # controls which keybinding should trigger tv
# for command history # for command history
"command_history" = "ctrl-r" "command_history" = "ctrl-r"

View File

@ -89,7 +89,7 @@ impl Config {
#[allow(clippy::missing_panics_doc, clippy::missing_errors_doc)] #[allow(clippy::missing_panics_doc, clippy::missing_errors_doc)]
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
// Load the default_config values as base defaults // Load the default_config values as base defaults
let default_config: Config = toml::from_str(DEFAULT_CONFIG) let mut default_config: Config = toml::from_str(DEFAULT_CONFIG)
.expect("Error parsing default config"); .expect("Error parsing default config");
// initialize the config builder // initialize the config builder
@ -107,22 +107,35 @@ impl Config {
let path = config_dir.join(CONFIG_FILE_NAME); let path = config_dir.join(CONFIG_FILE_NAME);
let contents = std::fs::read_to_string(&path)?; let contents = std::fs::read_to_string(&path)?;
let cfg: Config = toml::from_str(&contents).unwrap_or_else(|e| { let mut user_cfg: Config = toml::from_str(&contents).unwrap_or_else(|e| {
warn!( warn!(
"Error parsing config file, using default configuration: {}" , e "Error parsing config file, using default configuration: {}" , e
); );
default_config.clone() default_config.clone()
}); });
// merge shell integration triggers with commands
default_config.shell_integration.merge_triggers();
user_cfg.shell_integration.merge_triggers();
// merge shell integration commands with default commands
let mut merged_commands =
default_config.shell_integration.commands.clone();
merged_commands
.extend(user_cfg.shell_integration.commands.clone());
user_cfg.shell_integration.commands = merged_commands;
// merge keybindings with default keybindings // merge keybindings with default keybindings
let keybindings = merge_keybindings( let keybindings = merge_keybindings(
default_config.keybindings, default_config.keybindings,
&cfg.keybindings, &user_cfg.keybindings,
); );
let cfg = Config { keybindings, ..cfg }; let final_cfg = Config {
keybindings,
..user_cfg
};
debug!("Config: {:?}", cfg); debug!("Config: {:?}", final_cfg);
Ok(cfg) Ok(final_cfg)
} else { } else {
warn!("No config file found at {:?}, creating default configuration file at that location.", config_dir); warn!("No config file found at {:?}, creating default configuration file at that location.", config_dir);
// create the default configuration file in the user's config directory // create the default configuration file in the user's config directory

View File

@ -2,13 +2,19 @@ use std::hash::Hash;
use crate::config::parse_key; use crate::config::parse_key;
use crate::event::Key; use crate::event::Key;
use crate::utils::hashmaps;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::Deserialize; use serde::Deserialize;
#[derive(Clone, Debug, Deserialize, Default, PartialEq)] #[derive(Clone, Debug, Deserialize, Default, PartialEq)]
#[serde(default)] #[serde(default)]
pub struct ShellIntegrationConfig { pub struct ShellIntegrationConfig {
/// DEPRECATED: This is a legacy configuration option that is no longer used.
/// It is kept here for backwards compatibility.
/// {command: channel}
pub commands: FxHashMap<String, String>, pub commands: FxHashMap<String, String>,
/// {channel: [commands]}
pub channel_triggers: FxHashMap<String, Vec<String>>,
pub keybindings: FxHashMap<String, String>, pub keybindings: FxHashMap<String, String>,
} }
@ -19,6 +25,21 @@ impl Hash for ShellIntegrationConfig {
} }
} }
impl ShellIntegrationConfig {
/// Merge the channel triggers into the commands hashmap
/// This is done to maintain backwards compatibility with the old configuration
/// format.
///
/// {command: channel} + {channel: [commands]} => {command: channel}
pub fn merge_triggers(&mut self) {
// invert the hashmap to get {command: channel}
let inverted_triggers =
hashmaps::invert_hashmap(&self.channel_triggers);
// merge the inverted hashmap with the existing commands hashmap
self.commands.extend(inverted_triggers);
}
}
const SMART_AUTOCOMPLETE_CONFIGURATION_KEY: &str = "smart_autocomplete"; const SMART_AUTOCOMPLETE_CONFIGURATION_KEY: &str = "smart_autocomplete";
const COMMAND_HISTORY_CONFIGURATION_KEY: &str = "command_history"; const COMMAND_HISTORY_CONFIGURATION_KEY: &str = "command_history";
const DEFAULT_SHELL_AUTOCOMPLETE_KEY: char = 'T'; const DEFAULT_SHELL_AUTOCOMPLETE_KEY: char = 'T';

View File

@ -0,0 +1,19 @@
use std::collections::HashMap;
use std::hash::Hash;
pub fn invert_hashmap<K, V, I, S: ::std::hash::BuildHasher>(
hashmap: &HashMap<K, V, S>,
) -> HashMap<I, K>
where
K: Eq + Hash + Clone,
V: Eq + Hash + Clone + IntoIterator<Item = I>,
I: Eq + Hash + Clone,
{
let mut inverted = HashMap::new();
for (key, values) in hashmap {
for value in values.clone() {
inverted.insert(value, key.clone());
}
}
inverted
}

View File

@ -1,6 +1,7 @@
pub mod cache; pub mod cache;
pub mod command; pub mod command;
pub mod files; pub mod files;
pub mod hashmaps;
pub mod indices; pub mod indices;
pub mod input; pub mod input;
pub mod metadata; pub mod metadata;