diff --git a/.github/workflows/run_checks.yml b/.github/workflows/run_checks.yml new file mode 100644 index 0000000..ef0c57b --- /dev/null +++ b/.github/workflows/run_checks.yml @@ -0,0 +1,22 @@ +name: Run checks +on: + push: +env: + CARGO_TERM_COLOR: always +jobs: + run_checks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Check linting + run: cargo clippy --all-targets --all-features -- -D warnings + + - name: Build + run: cargo build --locked --verbose + + - name: Run tests + run: cargo test --locked --verbose diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml deleted file mode 100644 index 891a835..0000000 --- a/.github/workflows/run_tests.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Run tests -on: - push: -env: - CARGO_TERM_COLOR: always -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose diff --git a/src/keybinding/bash_zsh.rs b/src/keybinding/bash_zsh.rs index 7ee1486..f2df524 100644 --- a/src/keybinding/bash_zsh.rs +++ b/src/keybinding/bash_zsh.rs @@ -56,13 +56,13 @@ mod tests { #[test] fn test_keyevent_to_shell_seq_ctrl() { - let seq = keyevent_to_shell_seq(parse_keysequence("").unwrap()[0].clone()); + let seq = keyevent_to_shell_seq(parse_keysequence("").unwrap()[0]); assert_eq!(seq, "\x07"); // Ctrl-G } #[test] fn test_keyevent_to_shell_seq_alt() { - let seq = keyevent_to_shell_seq(parse_keysequence("").unwrap()[0].clone()); + let seq = keyevent_to_shell_seq(parse_keysequence("").unwrap()[0]); assert_eq!(seq, "\x1Bx"); // ESC + 'x' } } diff --git a/src/keybinding/fish.rs b/src/keybinding/fish.rs index 27e7228..a9860a9 100644 --- a/src/keybinding/fish.rs +++ b/src/keybinding/fish.rs @@ -46,7 +46,7 @@ pub fn fish_keyevent_to_shell_seq(ev: KeyEvent) -> String { _ => "".into(), }; - return format!("{}{}", modifier, keycode); + format!("{}{}", modifier, keycode) } #[cfg(test)] @@ -56,21 +56,19 @@ mod tests { #[test] fn test_fish_fields_simple() { - let seq = fish_keyevent_to_shell_seq(parse_keysequence("").unwrap()[0].clone()); + let seq = fish_keyevent_to_shell_seq(parse_keysequence("").unwrap()[0]); assert_eq!(seq, "ctrl-g"); } #[test] fn test_fish_fields_combo() { - let seq = fish_keyevent_to_shell_seq(parse_keysequence("").unwrap()[0].clone()); + let seq = fish_keyevent_to_shell_seq(parse_keysequence("").unwrap()[0]); assert_eq!(seq, "ctrl-alt-shift-x"); } #[test] fn test_fish_fields_non_char() { - let seq = fish_keyevent_to_shell_seq(parse_keysequence("").unwrap()[0].clone()); + let seq = fish_keyevent_to_shell_seq(parse_keysequence("").unwrap()[0]); assert_eq!(seq, "f5"); } - - } diff --git a/src/keybinding/nushell.rs b/src/keybinding/nushell.rs index 0e5fc3d..c121abc 100644 --- a/src/keybinding/nushell.rs +++ b/src/keybinding/nushell.rs @@ -65,7 +65,7 @@ mod tests { #[test] fn test_nushell_fields_simple() { let ev = parse_keysequence("").unwrap(); - let fields = nushell_keyevent_to_fields(ev.first().unwrap().clone()).unwrap(); + let fields = nushell_keyevent_to_fields(*ev.first().unwrap()).unwrap(); assert_eq!(fields.modifier, "Control"); assert_eq!(fields.keycode, "Char_g"); } @@ -73,7 +73,7 @@ mod tests { #[test] fn test_nushell_fields_combo() { let ev = parse_keysequence("").unwrap(); - let fields = nushell_keyevent_to_fields(ev.first().unwrap().clone()).unwrap(); + let fields = nushell_keyevent_to_fields(*ev.first().unwrap()).unwrap(); assert_eq!(fields.modifier, "Control_Alt_Shift"); assert_eq!(fields.keycode, "Char_x"); } @@ -81,7 +81,7 @@ mod tests { #[test] fn test_nushell_fields_non_char() { let ev = parse_keysequence("").unwrap(); - let fields = nushell_keyevent_to_fields(ev.first().unwrap().clone()).unwrap(); + let fields = nushell_keyevent_to_fields(*ev.first().unwrap()).unwrap(); assert_eq!(fields.keycode, "F5"); assert_eq!(fields.modifier, "None"); } diff --git a/src/keybinding/shell_binding.rs b/src/keybinding/shell_binding.rs index cb70665..977b76c 100644 --- a/src/keybinding/shell_binding.rs +++ b/src/keybinding/shell_binding.rs @@ -32,7 +32,7 @@ pub fn keyevents_to_shell_binding( .map(|ev| keyevent_to_shell_seq(*ev)) .collect::(); - return Ok(format!( + Ok(format!( "\nbind -m emacs -x '\"{}\":{}'\n\ bind -m vi-insert -x '\"{}\":{}'\n\ # In vi-command mode, switch to insert mode, invoke leadr using the binding defined above, then return to command mode\n\ @@ -43,17 +43,16 @@ pub fn keyevents_to_shell_binding( function_name, key_code_string, function_name, - )); + )) } Shell::Fish => { let key_code_string = events .iter() - .map(|ev| fish_keyevent_to_shell_seq(*ev) + ",") - .collect::(); + .map(|ev| fish_keyevent_to_shell_seq(*ev)) + .collect::>() + .join(","); - let key_code_string = key_code_string.trim_end_matches(","); - - return Ok(format!("\nbind {} {}\n", key_code_string, function_name)); + Ok(format!("\nbind {} {}\n", key_code_string, function_name)) } Shell::Nushell => { ensure!( @@ -86,11 +85,11 @@ pub fn keyevents_to_shell_binding( .map(|ev| keyevent_to_shell_seq(*ev)) .collect::(); - return Ok(format!( + Ok(format!( "zle -N {}\n\ bindkey '{}' {}", function_name, key_code_string, function_name - )); + )) } } } diff --git a/src/main.rs b/src/main.rs index de1a60b..8a75585 100644 --- a/src/main.rs +++ b/src/main.rs @@ -99,11 +99,9 @@ fn main() -> Result<()> { fn get_config_dir() -> Result { if let Ok(custom_path) = std::env::var("LEADR_CONFIG_DIR") { Ok(PathBuf::from(custom_path)) + } else if let Some(path) = ProjectDirs::from("com", "leadr", "leadr") { + Ok(path.config_dir().to_path_buf()) } else { - if let Some(path) = ProjectDirs::from("com", "leadr", "leadr") { - Ok(path.config_dir().to_path_buf()) - } else { - Err(eyre!("Could not determine configuration directory.")) - } + Err(eyre!("Could not determine configuration directory.")) } } diff --git a/src/mappings.rs b/src/mappings.rs index f3bd3ae..2affcda 100644 --- a/src/mappings.rs +++ b/src/mappings.rs @@ -227,11 +227,12 @@ impl Mappings { pub fn next_possible_keys(&self, sequence: &str) -> BTreeSet { let mut next_keys = BTreeSet::new(); - for (key, _) in &self.mappings { - if key.starts_with(sequence) { - if let Some((_, ch)) = key[sequence.len()..].char_indices().next() { - next_keys.insert(ch.to_string()); - } + for key in self.mappings.keys() { + if let Some(next_char) = key + .strip_prefix(sequence) + .and_then(|rest| rest.chars().next()) + { + next_keys.insert(next_char.to_string()); } } diff --git a/src/session.rs b/src/session.rs index 7e7cff1..9147fc6 100644 --- a/src/session.rs +++ b/src/session.rs @@ -38,67 +38,75 @@ impl LeadrSession { // Cosmetically fix the prompt line disappearing while leadr is active. let mut prompt_guard = prompt::PromptGuard::try_new(); - if let Ok(ref mut guard) = prompt_guard { - if self.config.redraw_prompt_line { - guard.redraw()?; - } + if let Ok(ref mut guard) = prompt_guard + && self.config.redraw_prompt_line + { + guard.redraw()?; } loop { let timeout_reached = start_time.elapsed() >= self.config.panel.delay; if self.config.panel.enabled && panel.is_none() && timeout_reached { - let result = (|| { - let p = Panel::try_new(self.config.panel.clone(), self.theme.clone())?; - p.draw(&self.sequence, &self.mappings)?; - Ok(p) - })(); - - match result { - Ok(p) => panel = Some(p), - Err(_e) if self.config.panel.fail_silently => {} - Err(e) => return Err(e), - } + panel = self.try_new_panel()?; } - if poll(Duration::from_millis(50))? { - if let Event::Key(KeyEvent { + if poll(Duration::from_millis(50))? + && let Event::Key(KeyEvent { code, modifiers, .. }) = read()? - { - if modifiers == crossterm::event::KeyModifiers::CONTROL { - if code == KeyCode::Char('c') { - return Ok(SessionResult::Cancelled); - } - continue; + { + if modifiers == crossterm::event::KeyModifiers::CONTROL { + if code == KeyCode::Char('c') { + return Ok(SessionResult::Cancelled); } - match code { - KeyCode::Char(c) => { - self.sequence.push(c); - if let Some(mapping) = self.mappings.match_sequence(&self.sequence) { - return Ok(SessionResult::Command(mapping.format_command())); - } - - if !self.mappings.has_partial_match(&self.sequence) { - return Ok(SessionResult::NoMatch); - } - } - KeyCode::Backspace => { - self.sequence.pop(); + continue; + } + match code { + KeyCode::Char(c) => { + self.sequence.push(c); + if let Some(mapping) = self.mappings.match_sequence(&self.sequence) { + return Ok(SessionResult::Command(mapping.format_command())); } - KeyCode::Esc => { - return Ok(SessionResult::Cancelled); + + if !self.mappings.has_partial_match(&self.sequence) { + return Ok(SessionResult::NoMatch); } - _ => {} } - if let Some(panel) = panel.as_mut() { - if let Err(e) = panel.draw(&self.sequence, &self.mappings) { - if !self.config.panel.fail_silently { - return Err(e); - } - } + KeyCode::Backspace => { + self.sequence.pop(); + } + KeyCode::Esc => { + return Ok(SessionResult::Cancelled); } + _ => {} + } + + if let Some(ref mut p) = panel { + self.try_draw_panel(p)?; } } } } + + /// Try creating a new panel and draw upon success. + /// Will return Ok(None) if panel creation fails but fail_silently is set. + fn try_new_panel(&self) -> Result> { + match Panel::try_new(self.config.panel.clone(), self.theme.clone()) { + Ok(mut p) => { + self.try_draw_panel(&mut p)?; + Ok(Some(p)) + } + Err(_) if self.config.panel.fail_silently => Ok(None), + Err(e) => Err(e), + } + } + + /// Try drawing the panel, respecting the fail_silently setting. + fn try_draw_panel(&self, panel: &mut Panel) -> Result<()> { + match panel.draw(&self.sequence, &self.mappings) { + Ok(()) => Ok(()), + Err(_) if self.config.panel.fail_silently => Ok(()), + Err(e) => Err(e), + } + } } diff --git a/src/ui/panel.rs b/src/ui/panel.rs index b986ad2..40ebd29 100644 --- a/src/ui/panel.rs +++ b/src/ui/panel.rs @@ -171,7 +171,7 @@ impl Panel { .take(column.height as usize) .cloned() .collect::>(); - self.draw_entries(&mut tty, column, &mappings, &sequence, &column_keys)?; + self.draw_entries(&mut tty, column, mappings, sequence, &column_keys)?; } let footer_area = Area {