tmux-sessionizer 0.6.1

Tmux Sessionizer is a cli tool to fuzzy find all git repositories in a list of specified folders and open them as a new tmux session. Also, for git worktrees, this tool opens all checked out worktrees as new windows.
Documentation
use std::collections::BTreeMap;
use std::fmt::Display;
use std::{collections::HashMap, fmt::Debug};

use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use serde::de::Error as DeError;
use serde::{Deserialize, Serialize, Serializer};

use crate::error::TmsError;

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Key {
    code: KeyCode,
    modifiers: KeyModifiers,
}

impl PartialOrd for Key {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Key {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        let a = self.to_string();
        let b = other.to_string();
        a.cmp(&b)
    }
}

impl Display for Key {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let modifiers = self
            .modifiers
            .iter()
            .filter_map(modifier_to_string)
            .collect::<Vec<&str>>()
            .join("-");
        let code = keycode_to_string(self.code).unwrap();
        let formatted = if modifiers.is_empty() {
            code
        } else {
            format!("{}-{}", modifiers, code)
        };
        write!(f, "{formatted}")
    }
}

impl Serialize for Key {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

fn modifier_to_string<'a>(modifier: KeyModifiers) -> Option<&'a str> {
    match modifier {
        KeyModifiers::SHIFT => Some("shift"),
        KeyModifiers::CONTROL => Some("ctrl"),
        KeyModifiers::ALT => Some("alt"),
        KeyModifiers::SUPER => Some("super"),
        KeyModifiers::HYPER => Some("hyper"),
        KeyModifiers::META => Some("meta"),
        _ => None,
    }
}

fn keycode_to_string(code: KeyCode) -> Option<String> {
    match code {
        KeyCode::Esc => Some("esc".to_owned()),
        KeyCode::Enter => Some("enter".to_owned()),
        KeyCode::Left => Some("left".to_owned()),
        KeyCode::Right => Some("right".to_owned()),
        KeyCode::Up => Some("up".to_owned()),
        KeyCode::Down => Some("down".to_owned()),
        KeyCode::Home => Some("home".to_owned()),
        KeyCode::End => Some("end".to_owned()),
        KeyCode::PageUp => Some("pageup".to_owned()),
        KeyCode::PageDown => Some("pagedown".to_owned()),
        KeyCode::BackTab => Some("backtab".to_owned()),
        KeyCode::Backspace => Some("backspace".to_owned()),
        KeyCode::Delete => Some("delete".to_owned()),
        KeyCode::Insert => Some("insert".to_owned()),
        KeyCode::F(1) => Some("f1".to_owned()),
        KeyCode::F(2) => Some("f2".to_owned()),
        KeyCode::F(3) => Some("f3".to_owned()),
        KeyCode::F(4) => Some("f4".to_owned()),
        KeyCode::F(5) => Some("f5".to_owned()),
        KeyCode::F(6) => Some("f6".to_owned()),
        KeyCode::F(7) => Some("f7".to_owned()),
        KeyCode::F(8) => Some("f8".to_owned()),
        KeyCode::F(9) => Some("f9".to_owned()),
        KeyCode::F(10) => Some("f10".to_owned()),
        KeyCode::F(11) => Some("f11".to_owned()),
        KeyCode::F(12) => Some("f12".to_owned()),
        KeyCode::Char(' ') => Some("space".to_owned()),
        KeyCode::Tab => Some("tab".to_owned()),
        KeyCode::Char(c) => Some(String::from(c)),
        _ => None,
    }
}

impl<'de> Deserialize<'de> for Key {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let value: String = Deserialize::deserialize(deserializer)?;
        let tokens = value.split('-').collect::<Vec<&str>>();

        let mut modifiers = KeyModifiers::empty();

        for modifier in tokens.iter().take(tokens.len() - 1) {
            match modifier.to_ascii_lowercase().as_ref() {
                "shift" => modifiers.insert(KeyModifiers::SHIFT),
                "ctrl" => modifiers.insert(KeyModifiers::CONTROL),
                "alt" => modifiers.insert(KeyModifiers::ALT),
                "super" => modifiers.insert(KeyModifiers::SUPER),
                "hyper" => modifiers.insert(KeyModifiers::HYPER),
                "meta" => modifiers.insert(KeyModifiers::META),
                _ => {}
            };
        }

        let last = tokens
            .last()
            .ok_or(TmsError::ConfigError)
            .map_err(D::Error::custom)?;

        let code = match last.to_ascii_lowercase().as_ref() {
            "esc" => KeyCode::Esc,
            "enter" => KeyCode::Enter,
            "left" => KeyCode::Left,
            "right" => KeyCode::Right,
            "up" => KeyCode::Up,
            "down" => KeyCode::Down,
            "home" => KeyCode::Home,
            "end" => KeyCode::End,
            "pageup" => KeyCode::PageUp,
            "pagedown" => KeyCode::PageDown,
            "backtab" => KeyCode::BackTab,
            "backspace" => KeyCode::Backspace,
            "del" => KeyCode::Delete,
            "delete" => KeyCode::Delete,
            "insert" => KeyCode::Insert,
            "ins" => KeyCode::Insert,
            "f1" => KeyCode::F(1),
            "f2" => KeyCode::F(2),
            "f3" => KeyCode::F(3),
            "f4" => KeyCode::F(4),
            "f5" => KeyCode::F(5),
            "f6" => KeyCode::F(6),
            "f7" => KeyCode::F(7),
            "f8" => KeyCode::F(8),
            "f9" => KeyCode::F(9),
            "f10" => KeyCode::F(10),
            "f11" => KeyCode::F(11),
            "f12" => KeyCode::F(12),
            "space" => KeyCode::Char(' '),
            "tab" => KeyCode::Tab,
            c if c.len() == 1 => KeyCode::Char(c.chars().next().unwrap()),
            _ => {
                return Err(D::Error::custom(TmsError::ConfigError));
            }
        };
        Ok(Key { code, modifiers })
    }
}

impl From<KeyEvent> for Key {
    fn from(value: KeyEvent) -> Self {
        Self {
            code: value.code,
            modifiers: value.modifiers,
        }
    }
}

#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Keymap(#[serde(serialize_with = "sorted_map")] pub HashMap<Key, PickerAction>);

fn sorted_map<S: Serializer, K: Serialize + Ord + Debug, V: Serialize + Debug>(
    value: &HashMap<K, V>,
    serializer: S,
) -> Result<S::Ok, S::Error> {
    let mut items: Vec<(_, _)> = value.iter().collect();
    items.sort_by(|a, b| a.0.cmp(b.0));
    BTreeMap::from_iter(items).serialize(serializer)
}

impl Default for Keymap {
    fn default() -> Self {
        Keymap(HashMap::from([
            (
                Key {
                    code: KeyCode::Char('c'),
                    modifiers: KeyModifiers::CONTROL,
                },
                PickerAction::Cancel,
            ),
            (
                Key {
                    code: KeyCode::Esc,
                    modifiers: KeyModifiers::empty(),
                },
                PickerAction::Cancel,
            ),
            (
                Key {
                    code: KeyCode::Enter,
                    modifiers: KeyModifiers::empty(),
                },
                PickerAction::Confirm,
            ),
            (
                Key {
                    code: KeyCode::Delete,
                    modifiers: KeyModifiers::empty(),
                },
                PickerAction::Delete,
            ),
            (
                Key {
                    code: KeyCode::Char('d'),
                    modifiers: KeyModifiers::CONTROL,
                },
                PickerAction::Delete,
            ),
            (
                Key {
                    code: KeyCode::Backspace,
                    modifiers: KeyModifiers::empty(),
                },
                PickerAction::Backspace,
            ),
            (
                Key {
                    code: KeyCode::Down,
                    modifiers: KeyModifiers::empty(),
                },
                PickerAction::MoveDown,
            ),
            (
                Key {
                    code: KeyCode::Char('j'),
                    modifiers: KeyModifiers::CONTROL,
                },
                PickerAction::MoveDown,
            ),
            (
                Key {
                    code: KeyCode::Char('n'),
                    modifiers: KeyModifiers::CONTROL,
                },
                PickerAction::MoveDown,
            ),
            (
                Key {
                    code: KeyCode::Up,
                    modifiers: KeyModifiers::empty(),
                },
                PickerAction::MoveUp,
            ),
            (
                Key {
                    code: KeyCode::Char('k'),
                    modifiers: KeyModifiers::CONTROL,
                },
                PickerAction::MoveUp,
            ),
            (
                Key {
                    code: KeyCode::Char('p'),
                    modifiers: KeyModifiers::CONTROL,
                },
                PickerAction::MoveUp,
            ),
            (
                Key {
                    code: KeyCode::Left,
                    modifiers: KeyModifiers::empty(),
                },
                PickerAction::CursorLeft,
            ),
            (
                Key {
                    code: KeyCode::Right,
                    modifiers: KeyModifiers::empty(),
                },
                PickerAction::CursorRight,
            ),
            (
                Key {
                    code: KeyCode::Char('w'),
                    modifiers: KeyModifiers::CONTROL,
                },
                PickerAction::DeleteWord,
            ),
            (
                Key {
                    code: KeyCode::Char('u'),
                    modifiers: KeyModifiers::CONTROL,
                },
                PickerAction::DeleteToLineStart,
            ),
            (
                Key {
                    code: KeyCode::Char('a'),
                    modifiers: KeyModifiers::CONTROL,
                },
                PickerAction::MoveToLineStart,
            ),
            (
                Key {
                    code: KeyCode::Char('e'),
                    modifiers: KeyModifiers::CONTROL,
                },
                PickerAction::MoveToLineEnd,
            ),
        ]))
    }
}

impl Keymap {
    pub fn with_defaults(keymap: &Keymap) -> Self {
        let mut default = Self::default();
        keymap.0.iter().for_each(|(event, action)| {
            default.0.insert(*event, *action);
        });
        default
    }
}

#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum PickerAction {
    #[serde(rename = "")]
    Noop,
    #[serde(rename = "cancel")]
    Cancel,
    #[serde(rename = "confirm")]
    Confirm,
    #[serde(rename = "backspace")]
    Backspace,
    #[serde(rename = "delete")]
    Delete,
    #[serde(rename = "move_up")]
    MoveUp,
    #[serde(rename = "move_down")]
    MoveDown,
    #[serde(rename = "cursor_left")]
    CursorLeft,
    #[serde(rename = "cursor_right")]
    CursorRight,
    #[serde(rename = "delete_word")]
    DeleteWord,
    #[serde(rename = "delete_to_line_start")]
    DeleteToLineStart,
    #[serde(rename = "delete_to_line_end")]
    DeleteToLineEnd,
    #[serde(rename = "move_to_line_start")]
    MoveToLineStart,
    #[serde(rename = "move_to_line_end")]
    MoveToLineEnd,
}