Skip to content
Merged
22 changes: 22 additions & 0 deletions .github/workflows/run_checks.yml
Original file line number Diff line number Diff line change
@@ -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
14 changes: 0 additions & 14 deletions .github/workflows/run_tests.yml

This file was deleted.

4 changes: 2 additions & 2 deletions src/keybinding/bash_zsh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ mod tests {

#[test]
fn test_keyevent_to_shell_seq_ctrl() {
let seq = keyevent_to_shell_seq(parse_keysequence("<C-g>").unwrap()[0].clone());
let seq = keyevent_to_shell_seq(parse_keysequence("<C-g>").unwrap()[0]);
assert_eq!(seq, "\x07"); // Ctrl-G
}

#[test]
fn test_keyevent_to_shell_seq_alt() {
let seq = keyevent_to_shell_seq(parse_keysequence("<M-x>").unwrap()[0].clone());
let seq = keyevent_to_shell_seq(parse_keysequence("<M-x>").unwrap()[0]);
assert_eq!(seq, "\x1Bx"); // ESC + 'x'
}
}
10 changes: 4 additions & 6 deletions src/keybinding/fish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub fn fish_keyevent_to_shell_seq(ev: KeyEvent) -> String {
_ => "".into(),
};

return format!("{}{}", modifier, keycode);
format!("{}{}", modifier, keycode)
}

#[cfg(test)]
Expand All @@ -56,21 +56,19 @@ mod tests {

#[test]
fn test_fish_fields_simple() {
let seq = fish_keyevent_to_shell_seq(parse_keysequence("<C-g>").unwrap()[0].clone());
let seq = fish_keyevent_to_shell_seq(parse_keysequence("<C-g>").unwrap()[0]);
assert_eq!(seq, "ctrl-g");
}

#[test]
fn test_fish_fields_combo() {
let seq = fish_keyevent_to_shell_seq(parse_keysequence("<C-M-S-x>").unwrap()[0].clone());
let seq = fish_keyevent_to_shell_seq(parse_keysequence("<C-M-S-x>").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("<F5>").unwrap()[0].clone());
let seq = fish_keyevent_to_shell_seq(parse_keysequence("<F5>").unwrap()[0]);
assert_eq!(seq, "f5");
}


}
6 changes: 3 additions & 3 deletions src/keybinding/nushell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,23 @@ mod tests {
#[test]
fn test_nushell_fields_simple() {
let ev = parse_keysequence("<C-g>").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");
}

#[test]
fn test_nushell_fields_combo() {
let ev = parse_keysequence("<C-M-S-x>").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");
}

#[test]
fn test_nushell_fields_non_char() {
let ev = parse_keysequence("<F5>").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");
}
Expand Down
17 changes: 8 additions & 9 deletions src/keybinding/shell_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub fn keyevents_to_shell_binding(
.map(|ev| keyevent_to_shell_seq(*ev))
.collect::<String>();

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\
Expand All @@ -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::<String>();
.map(|ev| fish_keyevent_to_shell_seq(*ev))
.collect::<Vec<_>>()
.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!(
Expand Down Expand Up @@ -86,11 +85,11 @@ pub fn keyevents_to_shell_binding(
.map(|ev| keyevent_to_shell_seq(*ev))
.collect::<String>();

return Ok(format!(
Ok(format!(
"zle -N {}\n\
bindkey '{}' {}",
function_name, key_code_string, function_name
));
))
}
}
}
8 changes: 3 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,9 @@ fn main() -> Result<()> {
fn get_config_dir() -> Result<PathBuf> {
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."))
}
}
11 changes: 6 additions & 5 deletions src/mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,12 @@ impl Mappings {
pub fn next_possible_keys(&self, sequence: &str) -> BTreeSet<String> {
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());
}
}

Expand Down
98 changes: 53 additions & 45 deletions src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<Panel>> {
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),
}
}
}
2 changes: 1 addition & 1 deletion src/ui/panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ impl Panel {
.take(column.height as usize)
.cloned()
.collect::<Vec<_>>();
self.draw_entries(&mut tty, column, &mappings, &sequence, &column_keys)?;
self.draw_entries(&mut tty, column, mappings, sequence, &column_keys)?;
}

let footer_area = Area {
Expand Down