From f3322f97c71e70409a92155c0599febd43eb4a37 Mon Sep 17 00:00:00 2001 From: Benedikt Peetz Date: Tue, 2 Jun 2026 21:49:19 +0200 Subject: pkgs/lf-make-map: Implement an interactive mode This mode effectively replaces the display lf gives you for mappings. --- pkgs/by-name/lf/lf-make-map/src/cli.rs | 6 + pkgs/by-name/lf/lf-make-map/src/main.rs | 89 +---------- .../lf/lf-make-map/src/mapping/interactive.rs | 174 +++++++++++++++++++++ pkgs/by-name/lf/lf-make-map/src/mapping/mod.rs | 1 + 4 files changed, 183 insertions(+), 87 deletions(-) create mode 100644 pkgs/by-name/lf/lf-make-map/src/mapping/interactive.rs (limited to 'pkgs/by-name/lf/lf-make-map/src') diff --git a/pkgs/by-name/lf/lf-make-map/src/cli.rs b/pkgs/by-name/lf/lf-make-map/src/cli.rs index 70746984..25a5a626 100644 --- a/pkgs/by-name/lf/lf-make-map/src/cli.rs +++ b/pkgs/by-name/lf/lf-make-map/src/cli.rs @@ -50,6 +50,12 @@ pub enum Command { #[command(flatten)] options: CommandOptions, }, + + /// Perform interactive selection, and then cd-there. + Interactive { + #[command(flatten)] + options: CommandOptions, + }, } #[derive(Debug, Parser)] diff --git a/pkgs/by-name/lf/lf-make-map/src/main.rs b/pkgs/by-name/lf/lf-make-map/src/main.rs index d5d934e1..1576c1dd 100644 --- a/pkgs/by-name/lf/lf-make-map/src/main.rs +++ b/pkgs/by-name/lf/lf-make-map/src/main.rs @@ -39,6 +39,7 @@ fn main() -> anyhow::Result<()> { let relevant_directories = match &args.command { Command::Visualize { options } => &options.relevant_directories, Command::Generate { options } => &options.relevant_directories, + Command::Interactive { options } => &options.relevant_directories, }; for dir in relevant_directories { @@ -99,6 +100,7 @@ fn main() -> anyhow::Result<()> { match args.command { Command::Visualize { .. } => println!("{}", mappings.0), Command::Generate { .. } => println!("{}", mappings.to_lf_mappings(args.home_name)), + Command::Interactive { .. } => mappings.interactive_start(args.home_name)?, } Ok(()) @@ -144,90 +146,3 @@ because it can't be turned to a string ) }) } - -// fn gen_lf_mappings(home_name: PathBuf, char_num: usize, rel_dirs: Vec) { -// let mut mappings_vec = vec![]; -// let mut index_counter = 0; -// rel_dirs.iter().for_each(|rel_dir| { -// mappings_vec.push(vec![Mapping::new( -// &gen_hot_key(rel_dir, rel_dir, char_num), -// rel_dir, -// rel_dir, -// None, -// )]); -// get_dir(rel_dir.to_owned()).iter().for_each(|path| { -// mappings_vec[index_counter].push(Mapping::new( -// &gen_hot_key( -// path, -// path.parent().expect("All paths here should have parents"), -// char_num, -// ), -// path, -// &path -// .parent() -// .expect("All paths here should have parents") -// .to_owned(), -// None, -// )); -// }); -// index_counter += 1; -// }); -// print_mappings(&mappings_vec, home_name); -// mappings_vec -// .into_iter() -// .for_each(|rel_dir_mapping: Vec| { -// let mut hash_map = sort_mapping_by_hot_key(rel_dir_mapping.clone()); -// //dbg!(hash_map); -// hash_map.insert("gsi".to_owned(), vec![rel_dir_mapping[0].clone()]); -// }); -// } -// -// fn sort_mapping_by_hot_key(mut mappings: Vec) -> HashMap> { -// mappings.sort_by_key(|mapping| mapping.hot_key.clone()); -// -// let mut filtered_mappings: HashMap> = HashMap::new(); -// mappings.iter().for_each(|mapping| { -// filtered_mappings.insert(mapping.hot_key.clone(), vec![]); -// }); -// //dbg!(&mappings); -// -// let mut index_counter = 1; -// mappings.iter().for_each(|mapping| { -// if mappings.len() > index_counter { -// let next_mapping = &mappings[index_counter]; -// let vec = filtered_mappings -// .get_mut(&mapping.hot_key) -// .expect("This existst as it has been initialized"); -// -// if &next_mapping.hot_key == &mapping.hot_key { -// vec.push(mapping.clone()); -// vec.push(next_mapping.clone()); -// } else { -// vec.push(mapping.clone()); -// } -// -// let new_vec = vec.to_owned(); -// filtered_mappings.insert(mapping.hot_key.to_owned(), new_vec); -// } -// -// index_counter += 1; -// }); -// filtered_mappings -// } -// -// fn print_mappings(mappings: &Vec>, home_name: PathBuf) { -// for mapping in mappings { -// mapping.iter().for_each(|map| { -// println!( -// "{} = \"cd {}\";", -// map.hot_key, -// map.path -// .display() -// .to_string() -// .replace(home_name.to_str().expect("This should be UTF-8"), "~") -// ); -// }); -// -// println!("# -------------"); -// } -// } diff --git a/pkgs/by-name/lf/lf-make-map/src/mapping/interactive.rs b/pkgs/by-name/lf/lf-make-map/src/mapping/interactive.rs new file mode 100644 index 00000000..31324e1d --- /dev/null +++ b/pkgs/by-name/lf/lf-make-map/src/mapping/interactive.rs @@ -0,0 +1,174 @@ +// nixos-config - My current NixOS configuration +// +// Copyright (C) 2025 Benedikt Peetz +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of my nixos-config. +// +// You should have received a copy of the License along with this program. +// If not, see . + +use std::{io::stdout, path::PathBuf}; + +use anyhow::Result; +use crossterm::{ + cursor::{MoveToRow, MoveUp}, + event::{ + Event, KeyEventKind, KeyModifiers, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, + PushKeyboardEnhancementFlags, read, + }, + execute, + style::Print, + terminal::{self, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen}, +}; + +use crate::mapping::map_key::MapKey; + +use super::MappingsTrie; + +enum Status { + Done(PathBuf), + Stop, +} + +impl MappingsTrie { + pub fn interactive_start(&self, home_path: PathBuf) -> Result<()> { + terminal::enable_raw_mode()?; + execute!( + stdout(), + EnterAlternateScreen, + PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES), + MoveToRow(0) + )?; + + let output = self.interactive_start_inner(home_path); + + execute!(stdout(), LeaveAlternateScreen, PopKeyboardEnhancementFlags)?; + terminal::disable_raw_mode()?; + + match output? { + Status::Done(path_buf) => { + println!("{}", path_buf.display()) + } + Status::Stop => (), + } + + Ok(()) + } + + fn interactive_start_inner(&self, home_path: PathBuf) -> Result { + macro_rules! done { + ($state:ident, $last_length:ident) => {{ + let value = self + .0 + .get(&$state) + .expect("Is some") + .value() + .expect("is some"); + + let path = home_path.join(&value.path); + + terminal::disable_raw_mode()?; + execute!( + stdout(), + MoveUp($last_length as u16), + Clear(ClearType::FromCursorDown), + Print(format!("{}\n", path.display())) + )?; + terminal::enable_raw_mode()?; + + return Ok(Status::Done(path)); + }}; + } + + let mut state: Vec = vec![]; + let mut last_length: usize = 1; + while let (trie, matched) = self.0.try_get(&state) + && matched == state + { + if trie.value().is_some() { + done!(state, last_length); + } else { + if let Some(last) = state.last_mut() + && let Some(mlast) = matched.last() + { + last.resolution = mlast.resolution; + mlast.part_path.clone_into(&mut last.part_path); + } + } + + { + terminal::disable_raw_mode()?; + let string = trie.to_string(); + execute!( + stdout(), + MoveUp(last_length as u16), + Clear(ClearType::FromCursorDown), + Print(format!( + "Current state: {}\n", + self.current_progress(home_path.display().to_string(), &state) + )), + Print(&string) + )?; + last_length = string.lines().count() + 1; + terminal::enable_raw_mode()?; + } + + if let Event::Key(event) = read()? + && event.kind == KeyEventKind::Press + { + match event.code { + crossterm::event::KeyCode::Backspace => { + state.pop(); + } + crossterm::event::KeyCode::Enter => done!(state, last_length), + crossterm::event::KeyCode::Esc => break, + crossterm::event::KeyCode::Char(char) => { + if event.modifiers == KeyModifiers::CONTROL && char == 'c' { + break; + } else { + state.push(MapKey { + key: char, + resolution: 0, + part_path: String::new(), + }); + } + } + + crossterm::event::KeyCode::Left + | crossterm::event::KeyCode::Right + | crossterm::event::KeyCode::Up + | crossterm::event::KeyCode::Down + | crossterm::event::KeyCode::Home + | crossterm::event::KeyCode::End + | crossterm::event::KeyCode::PageUp + | crossterm::event::KeyCode::PageDown + | crossterm::event::KeyCode::Tab + | crossterm::event::KeyCode::BackTab + | crossterm::event::KeyCode::Delete + | crossterm::event::KeyCode::Insert + | crossterm::event::KeyCode::F(_) + | crossterm::event::KeyCode::Null + | crossterm::event::KeyCode::CapsLock + | crossterm::event::KeyCode::ScrollLock + | crossterm::event::KeyCode::NumLock + | crossterm::event::KeyCode::PrintScreen + | crossterm::event::KeyCode::Pause + | crossterm::event::KeyCode::Menu + | crossterm::event::KeyCode::KeypadBegin + | crossterm::event::KeyCode::Media(_) + | crossterm::event::KeyCode::Modifier(_) => (), + } + } + } + + Ok(Status::Stop) + } + + fn current_progress(&self, home_path: String, state: &[MapKey]) -> String { + state + .iter() + .map(|mk| &mk.part_path) + .fold(home_path, |acc, part| format!("{acc}/{part}")) + } +} diff --git a/pkgs/by-name/lf/lf-make-map/src/mapping/mod.rs b/pkgs/by-name/lf/lf-make-map/src/mapping/mod.rs index 21392388..b733990e 100644 --- a/pkgs/by-name/lf/lf-make-map/src/mapping/mod.rs +++ b/pkgs/by-name/lf/lf-make-map/src/mapping/mod.rs @@ -14,6 +14,7 @@ use log::{Level, debug, log_enabled, trace}; use map_key::MapKey; pub mod lf_mapping; +pub mod interactive; pub mod map_key; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -- cgit v1.3.1