From 9fc30022ece1aaa17d720ac79b5423a763f4260e Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Mon, 8 Nov 2021 21:18:16 +0100 Subject: [PATCH] Quick switch and primary mode --- Cargo.lock | 4 ++-- Cargo.toml | 3 +-- sh/htop.sh | 3 +++ src/main.rs | 50 ++++++++++++++++++++++--------------------- src/matcher/prefix.rs | 2 +- src/roftl.rs | 33 +++++++++++++++++++++++++--- 6 files changed, 63 insertions(+), 32 deletions(-) create mode 100755 sh/htop.sh diff --git a/Cargo.lock b/Cargo.lock index ee4b9b8..78ea6d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -376,9 +376,9 @@ dependencies = [ [[package]] name = "flx-rs" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078541905b40af0ab9d964d367796f3b8eb009ba010c1864b9735f707188ff5b" +checksum = "dd8bff3fe3d46781cd9a98a8dbb0174b87a0a88ca04e78c269c06ade46d5f289" [[package]] name = "fnv" diff --git a/Cargo.toml b/Cargo.toml index 36f25cb..e19dc2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,7 @@ xcb-util = {version = "0.3.0", features = ["ewmh", "icccm"]} fuzzy-matcher = "0.3.7" fuse-rust = "0.3.0" -# Currently buggy, waiting for fix -flx-rs = "0.1.1" +flx-rs = "0.1.2" walkdir = "2.3.2" freedesktop_entry_parser = "1.2.0" diff --git a/sh/htop.sh b/sh/htop.sh new file mode 100755 index 0000000..7a6de65 --- /dev/null +++ b/sh/htop.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +htop diff --git a/src/main.rs b/src/main.rs index abbb476..15e72cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,14 @@ use log::{debug, trace}; +use std::cell::RefCell; use std::error::Error; +use std::rc::Rc; use winit::window::{Window, WindowBuilder}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent::{CloseRequested, ReceivedCharacter}}; -use crate::matcher::{FuseMatcher, SkimMatcher, FlxMatcher}; +use crate::matcher::{FuseMatcher, PrefixMatcher, SkimMatcher, FlxMatcher}; mod roftl; mod ui; @@ -24,7 +26,9 @@ fn main() -> Result<(), Box> { .add_source(sources::Window::new()) .add_source(sources::Apps::new()) .add_source(sources::ShellHost::new()) - .with_matcher(FuseMatcher::new()); + .with_matcher(FuseMatcher::new()) + .with_primary_matcher(PrefixMatcher::new()); + debug!{"Source roftl sources"} roftl.source(); @@ -49,7 +53,7 @@ fn main() -> Result<(), Box> { enum Mode { Selection, - Actions + Actions, } struct RoftlLoop { @@ -105,27 +109,25 @@ impl RoftlLoop { if window_id == self.window.id() && matches!{self.mode, Mode::Selection} => { + let results = self.roftl.narrow(&self.input_buffer); + trace!{"Narrow result {:?}", results} + + // quick switch if only one result + if results.len() == 1 { + self.roftl.do_action(0, roftl::Action::Primary); + *flow = ControlFlow::Exit; + return; + } + + // correct selection for results + if results.len() > 0 { self.selection = self.selection % results.len() } + else { self.selection = 0 } + if self.input_changed { trace!{"Redrawing with input {}", self.input_buffer} - - let results = self.roftl.narrow(&self.input_buffer); - trace!{"Narrow result {:?}", results} - - // correct selection for results - if results.len() > 0 { self.selection = self.selection % results.len() } - else { self.selection = 0 } - ui::draw(&self.window, &self.input_buffer, results, self.selection); } else { trace!{"Quick redraw with input {}", self.input_buffer} - - let results = self.roftl.narrow(&self.input_buffer); - trace!{"Narrow result {:?}", results} - - // correct selection for results - if results.len() > 0 { self.selection = self.selection % results.len() } - else { self.selection = 0 } - ui::redraw_quick(&self.window, results, self.selection); } @@ -185,7 +187,7 @@ impl RoftlLoop { // Enter c if c == char::from(0x0d) => { trace!{"Retrieved enter. Trigger primary action"} - self.roftl.action(self.selection, roftl::Action::Primary); + self.roftl.do_action(self.selection, roftl::Action::Primary); ControlFlow::Exit } @@ -235,22 +237,22 @@ impl RoftlLoop { match character { '1' => { trace!{"Retrieved action selection 1. Trigger primary action"} - self.roftl.action(self.selection, roftl::Action::Primary); + self.roftl.do_action(self.selection, roftl::Action::Primary); ControlFlow::Exit }, '2' => { trace!{"Retrieved action selection 2. Trigger secondary action"} - self.roftl.action(self.selection, roftl::Action::Secondary); + self.roftl.do_action(self.selection, roftl::Action::Secondary); ControlFlow::Exit }, '3' => { trace!{"Retrieved action selection 3. Trigger tertiary action"} - self.roftl.action(self.selection, roftl::Action::Tertiary); + self.roftl.do_action(self.selection, roftl::Action::Tertiary); ControlFlow::Exit }, '4' => { trace!{"Retrieved action selection 4. Trigger quaternary action"} - self.roftl.action(self.selection, roftl::Action::Quaternary); + self.roftl.do_action(self.selection, roftl::Action::Quaternary); ControlFlow::Exit }, _ => { diff --git a/src/matcher/prefix.rs b/src/matcher/prefix.rs index 29e438c..32e8cab 100644 --- a/src/matcher/prefix.rs +++ b/src/matcher/prefix.rs @@ -12,7 +12,7 @@ impl Matcher for PrefixMatcher { fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec)> { let mut indices = vec![]; - if haystack.starts_with(needle) { + if haystack.to_lowercase().starts_with(&needle.to_lowercase()) { (0..needle.len()).for_each(|i| indices.push(i)); return Option::Some((1.0, indices)); } diff --git a/src/roftl.rs b/src/roftl.rs index 221642c..0b0c47d 100644 --- a/src/roftl.rs +++ b/src/roftl.rs @@ -26,7 +26,7 @@ pub trait Source: Send + Sync { fn name(&self) -> &'static str; fn entries(&mut self) -> Vec; fn action(&self, entry: &Entry, action: Action); - fn actions(&self, entry: &Entry) -> Vec + fn actions(&self, _entry: &Entry) -> Vec { vec![] } } @@ -37,6 +37,11 @@ pub trait Matcher: Send + Sync { pub struct Roftl { sources: Vec, matcher: MatcherRef, + // the standard matcher can be inadequate for primary mode. E.g. a + // FuseMatcher would yield hard to guess and sometimes surprising results, + // whereas a PrefixMatcher is more deterministic. This is relevant for quick + // switch where surprises run counter to muscle memory and are annoying. + primary_matcher: MatcherRef, entries: Vec, narrow_map: Vec } @@ -46,6 +51,7 @@ impl Roftl { Roftl { sources: vec![], matcher: PrefixMatcher::new(), + primary_matcher: PrefixMatcher::new(), entries: vec![], narrow_map: vec![] } @@ -65,6 +71,11 @@ impl Roftl { self } + pub fn with_primary_matcher(mut self, matcher: MatcherRef) -> Self { + self.primary_matcher = matcher; + self + } + pub fn source(&mut self) { self.entries = self.sources .par_iter_mut() @@ -79,13 +90,29 @@ impl Roftl { .par_iter() .enumerate() .filter_map(|(idx, entry)| { + if entry.source != "window" { return None } + if input.is_empty() { return Some((0.0, idx, entry, vec![])) } - let match_result = self.matcher.try_match(&entry.name, input); + let match_result = self.primary_matcher.try_match(&entry.name, input); return match_result.map(|(score, indices)| (score, idx, entry, indices) ) }) .collect(); + if scored_entries.is_empty() || input.starts_with(",") { + let input = input.strip_prefix(",").unwrap_or(input); + scored_entries = self.entries + .par_iter() + .enumerate() + .filter_map(|(idx, entry)| { + if input.is_empty() { return Some((0.0, idx, entry, vec![])) } + + let match_result = self.matcher.try_match(&entry.name, input); + return match_result.map(|(score, indices)| (score, idx, entry, indices) ) + }) + .collect(); + } + scored_entries .par_sort_by(|e1, e2| e1.0.partial_cmp(&e2.0).unwrap()); @@ -100,7 +127,7 @@ impl Roftl { .collect() } - pub fn action(&self, selection_id: usize, action: Action) { + pub fn do_action(&self, selection_id: usize, action: Action) { let (entry, source) = self.find_selection(selection_id); source.action(entry, action); }