// 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}")) } }