diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-11-02 14:06:45 +0100 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-11-02 14:06:45 +0100 |
| commit | ef08201a906a69ae25a5de0811d3840ae5cc970e (patch) | |
| tree | 238d85022b4eccc759618b0ba9e4b949d5dfe309 /pkgs/by-name/fi/fish-mk-maps/src/main.rs | |
| parent | modules/fish: Switch to keybindings defined as nix attribute set (diff) | |
| download | nixos-config-ef08201a906a69ae25a5de0811d3840ae5cc970e.zip | |
pkgs/fish-mk-maps: Commit the program used to convert the `binds` to nix attrs
Diffstat (limited to 'pkgs/by-name/fi/fish-mk-maps/src/main.rs')
| -rw-r--r-- | pkgs/by-name/fi/fish-mk-maps/src/main.rs | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/pkgs/by-name/fi/fish-mk-maps/src/main.rs b/pkgs/by-name/fi/fish-mk-maps/src/main.rs new file mode 100644 index 00000000..57d221db --- /dev/null +++ b/pkgs/by-name/fi/fish-mk-maps/src/main.rs @@ -0,0 +1,362 @@ +use std::{ + collections::HashMap, + env::args, + fs, + io::{Read, Write}, + mem, + os::unix::net::{UnixListener, UnixStream}, + path::PathBuf, + process, + str::FromStr, +}; + +use anyhow::{Context, bail}; +use clap::Parser; +use keymaps::map_tree::Trie; +use serde::{Deserialize, Serialize}; + +macro_rules! is_open { + ($is_open:ident, @set_element $value:ident[$idx:expr]) => {{ + if let Some(value) = $value.get($idx - 1) && $value.len() > $idx { + if let Some(io) = &mut $is_open { + io.truncate($idx); + io.push(value.clone()); + println!("{}\"{}\" = {{", " ".repeat($idx + 1), value); + + eprintln!("Setting io element {} to '{}' (io: '{}')", $idx, value, io.join(",")); + } else if $idx == 1 { + $is_open = Some(vec![value.clone()]); + println!(" \"{}\" = {{", value); + + eprintln!("Initializing io with element '{}'", value); + } else { + unreachable!() + } + } + }}; + ($is_open:ident, @query $value:ident [..$till:expr]) => {{ + if $value.len() <= $till { + false + } else { + if let Some(io) = &$is_open { + let mut output = true; + for (idx, val) in $value.iter().take($till).enumerate() { + output = output && io.get(idx).map(|got| got == val).unwrap_or(false); + } + if output { + eprintln!("Query for '{}' (io: '{}') matched at element {}", $value.join(","), io.join(","), $till); + } + output + } else { + false + } + } + }}; + ($is_open:ident, @close $num:expr) => {{ + if let Some(io) = &mut $is_open { + eprintln!("Performing close for {} elements (io: '{}') ..", $num, io.join(",")); + for idx in (0..io.len()).rev().take($num) { + io.pop(); + eprintln!(" Emitting close at element {} (io now: '{}')", idx + 1, io.join(",")); + println!("{}}};", " ".repeat(idx + 2)); + } + } else { + unreachable!() + } + }}; + ($is_open:ident, @close_till $value:ident $num:expr) => {{ + if let Some(io) = $is_open.clone() { + is_open!($is_open, @close io.len() - $num) + } else { + } + }}; + ($is_open:ident, @close_all) => {{ + if let Some(io) = $is_open.clone() { + is_open!($is_open, @close io.len()); + } else { + } + }}; +} + +fn print_key(key: Vec<String>, node: Mapping) { + print!( + "{}\"{}\" = {{ cmd = \"{}\"; ", + " ".repeat(key.len() + 1), + key.last().expect("That's the len"), + node.command.join(" "), + ); + if let Some(sets_mode_to) = node.sets_mode_to { + println!("sets_mode_to = \"{}\";}};", sets_mode_to); + } else { + println!("}};"); + } +} + +#[derive(clap::Parser, Debug)] +#[clap(rename_all = "snake_case")] +pub(crate) struct Cli { + #[arg(long)] + preset: bool, + + #[arg(short = 'M', default_value = "default")] + mode: MapMode, + + #[arg(short = 'm', default_value = "default")] + sets_mode_to: MapMode, + + key: String, + + command: Vec<String>, +} + +#[derive( + Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord, Hash, Clone, Copy, Debug, Default, +)] +#[expect(non_camel_case_types)] +enum MapMode { + #[default] + Default, + Insert, + Operator, + ReplaceOne, + Replace, + Visual, + f, + F, + t, + T, +} + +impl std::fmt::Display for MapMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MapMode::Default => f.write_str("default")?, + MapMode::Insert => f.write_str("insert")?, + MapMode::Operator => f.write_str("operator")?, + MapMode::ReplaceOne => f.write_str("replace_one")?, + MapMode::Replace => f.write_str("replace")?, + MapMode::Visual => f.write_str("visual")?, + MapMode::f => f.write_str("f")?, + MapMode::F => f.write_str("F")?, + MapMode::t => f.write_str("t")?, + MapMode::T => f.write_str("T")?, + } + Ok(()) + } +} + +impl FromStr for MapMode { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let me = match s { + "operator" => Self::Operator, + "insert" => Self::Insert, + "replace_one" => Self::ReplaceOne, + "replace" => Self::Replace, + "visual" => Self::Visual, + "default" => Self::Default, + "f" => Self::f, + "F" => Self::F, + "t" => Self::t, + "T" => Self::T, + other => bail!("Mapping mode '{other}' not yet supported"), + }; + Ok(me) + } +} + +enum Message { + NewBinding(usize), + End(usize), +} +type Key = String; +#[derive(Debug, Deserialize, Serialize)] +struct Binding { + key: Vec<Key>, + command: Vec<String>, + sets_mode_to: Option<MapMode>, + mode: MapMode, +} + +struct MapStore { + map: HashMap<MapMode, Trie<Key, Mapping>>, +} + +#[derive(Clone, PartialEq, PartialOrd, Debug)] +struct Mapping { + command: Vec<String>, + sets_mode_to: Option<MapMode>, +} + +fn main() -> anyhow::Result<()> { + let communication_socket: PathBuf = PathBuf::from("/tmp/fish_map_store.socket"); + + match PathBuf::from(args().next().unwrap()) + .file_name() + .unwrap() + .to_str() + .unwrap() + { + "bind-end" => { + let mut stream = UnixStream::connect(communication_socket)?; + let message_raw: [u8; size_of::<Message>()] = + unsafe { mem::transmute(Message::End(0)) }; + + stream.write_all(&message_raw)?; + stream.flush()?; + } + "bind" => { + let args = Cli::parse(); + assert!(args.preset); + + let binding = Binding { + key: args.key.split(',').map(|s| s.to_string()).collect(), + command: args.command, + sets_mode_to: if args.mode != args.sets_mode_to { + Some(args.sets_mode_to) + } else { + None + }, + mode: args.mode, + }; + let binding_msg = serde_json::to_string(&binding)?; + + let mut stream = UnixStream::connect(communication_socket)?; + + let length = binding_msg.len(); + + let message_raw: [u8; size_of::<Message>()] = + unsafe { mem::transmute(Message::NewBinding(length)) }; + + stream.write_all(&message_raw)?; + stream.write_all(binding_msg.as_bytes())?; + stream.flush()?; + drop(stream); + } + "bind-server" => bind_server_main(communication_socket)?, + other => { + eprintln!("Personality '{other}' not supported."); + process::exit(2); + } + } + + // let mappings = store.map.entry(args.mode).or_insert(Vec::default()); + // mappings.push(mapping); + // + // { + // let mut file = fs::OpenOptions::new() + // .create(true) + // .truncate(true) + // .append(false) + // .write(true) + // .open(storage_file)?; + // + // file.write_all(serde_json::to_string(&store)?.as_bytes())?; + // } + // + // { + // eprintln!("Added {}", args.key); + // } + + Ok(()) +} + +fn bind_server_main(communication_socket: PathBuf) -> anyhow::Result<()> { + if communication_socket.exists() { + fs::remove_file(&communication_socket)?; + } + + let mut store = MapStore { + map: HashMap::new(), + }; + + let listener = UnixListener::bind(communication_socket)?; + + for stream in listener.incoming() { + match stream { + Ok(mut stream) => { + let mut next_message_raw = [0; size_of::<Message>()]; + stream.read_exact(&mut next_message_raw)?; + let next_message: Message = unsafe { mem::transmute(next_message_raw) }; + + match next_message { + Message::NewBinding(length) => { + let mut binding_str = String::new(); + stream.read_to_string(&mut binding_str)?; + + let binding: Binding = serde_json::from_str(&binding_str[..length]) + .with_context(|| { + format!("Failed to parse '{binding_str:?}' as json") + })?; + + let trie = store.map.entry(binding.mode).or_default(); + trie.insert( + &binding.key, + Mapping { + command: binding.command, + sets_mode_to: binding.sets_mode_to, + }, + )?; + } + Message::End(_pointless_value) => { + println!("{{"); + for (mode, trie) in store.map { + println!(" \"{mode}\" = {{"); + { + let mut io: Option<Vec<String>> = None; + for (key, node) in trie.iter() { + if is_open!(io, @query key[..4]) { + is_open!(io, @close_till key 4); + print_key(key, node); + } else if is_open!(io, @query key[..3]) { + is_open!(io, @close_till key 3); + is_open!(io, @set_element key[4]); + print_key(key, node); + } else if is_open!(io, @query key[..2]) { + is_open!(io, @close_till key 2); + is_open!(io, @set_element key[3]); + is_open!(io, @set_element key[4]); + print_key(key, node); + } else if is_open!(io, @query key[..1]) { + is_open!(io, @close_till key 1); + is_open!(io, @set_element key[2]); + is_open!(io, @set_element key[3]); + is_open!(io, @set_element key[4]); + print_key(key, node); + } else { + { + eprint!("No query matched: '{}'", key.join(",")); + if let Some(io) = &io { + eprintln!(" (io: '{}')", io.join(",")); + } else { + eprintln!(" (io empty)"); + } + } + + is_open!(io, @close_all); + is_open!(io, @set_element key[1]); + is_open!(io, @set_element key[2]); + is_open!(io, @set_element key[3]); + is_open!(io, @set_element key[4]); + print_key(key, node); + } + } + is_open!(io, @close_all); + } + println!(" }};"); + } + println!("}}"); + + break; + } + } + } + Err(_) => { + /* connection failed */ + break; + } + } + } + Ok(()) +} |
