Quick switch and primary mode

This commit is contained in:
Armin Friedl 2021-11-08 21:18:16 +01:00
parent bdb1520cf0
commit 9fc30022ec
6 changed files with 63 additions and 32 deletions

4
Cargo.lock generated
View file

@ -376,9 +376,9 @@ dependencies = [
[[package]] [[package]]
name = "flx-rs" name = "flx-rs"
version = "0.1.1" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078541905b40af0ab9d964d367796f3b8eb009ba010c1864b9735f707188ff5b" checksum = "dd8bff3fe3d46781cd9a98a8dbb0174b87a0a88ca04e78c269c06ade46d5f289"
[[package]] [[package]]
name = "fnv" name = "fnv"

View file

@ -19,8 +19,7 @@ xcb-util = {version = "0.3.0", features = ["ewmh", "icccm"]}
fuzzy-matcher = "0.3.7" fuzzy-matcher = "0.3.7"
fuse-rust = "0.3.0" fuse-rust = "0.3.0"
# Currently buggy, waiting for fix flx-rs = "0.1.2"
flx-rs = "0.1.1"
walkdir = "2.3.2" walkdir = "2.3.2"
freedesktop_entry_parser = "1.2.0" freedesktop_entry_parser = "1.2.0"

3
sh/htop.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/bash
htop

View file

@ -1,12 +1,14 @@
use log::{debug, trace}; use log::{debug, trace};
use std::cell::RefCell;
use std::error::Error; use std::error::Error;
use std::rc::Rc;
use winit::window::{Window, WindowBuilder}; use winit::window::{Window, WindowBuilder};
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode,
WindowEvent::{CloseRequested, ReceivedCharacter}}; WindowEvent::{CloseRequested, ReceivedCharacter}};
use crate::matcher::{FuseMatcher, SkimMatcher, FlxMatcher}; use crate::matcher::{FuseMatcher, PrefixMatcher, SkimMatcher, FlxMatcher};
mod roftl; mod roftl;
mod ui; mod ui;
@ -24,7 +26,9 @@ fn main() -> Result<(), Box<dyn Error>> {
.add_source(sources::Window::new()) .add_source(sources::Window::new())
.add_source(sources::Apps::new()) .add_source(sources::Apps::new())
.add_source(sources::ShellHost::new()) .add_source(sources::ShellHost::new())
.with_matcher(FuseMatcher::new()); .with_matcher(FuseMatcher::new())
.with_primary_matcher(PrefixMatcher::new());
debug!{"Source roftl sources"} debug!{"Source roftl sources"}
roftl.source(); roftl.source();
@ -49,7 +53,7 @@ fn main() -> Result<(), Box<dyn Error>> {
enum Mode { enum Mode {
Selection, Selection,
Actions Actions,
} }
struct RoftlLoop { struct RoftlLoop {
@ -105,27 +109,25 @@ impl RoftlLoop {
if window_id == self.window.id() if window_id == self.window.id()
&& matches!{self.mode, Mode::Selection} => && matches!{self.mode, Mode::Selection} =>
{ {
if self.input_changed {
trace!{"Redrawing with input {}", self.input_buffer}
let results = self.roftl.narrow(&self.input_buffer); let results = self.roftl.narrow(&self.input_buffer);
trace!{"Narrow result {:?}", results} 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 // correct selection for results
if results.len() > 0 { self.selection = self.selection % results.len() } if results.len() > 0 { self.selection = self.selection % results.len() }
else { self.selection = 0 } else { self.selection = 0 }
if self.input_changed {
trace!{"Redrawing with input {}", self.input_buffer}
ui::draw(&self.window, &self.input_buffer, results, self.selection); ui::draw(&self.window, &self.input_buffer, results, self.selection);
} else { } else {
trace!{"Quick redraw with input {}", self.input_buffer} 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); ui::redraw_quick(&self.window, results, self.selection);
} }
@ -185,7 +187,7 @@ impl RoftlLoop {
// Enter // Enter
c if c == char::from(0x0d) => { c if c == char::from(0x0d) => {
trace!{"Retrieved enter. Trigger primary action"} 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 ControlFlow::Exit
} }
@ -235,22 +237,22 @@ impl RoftlLoop {
match character { match character {
'1' => { '1' => {
trace!{"Retrieved action selection 1. Trigger primary action"} 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 ControlFlow::Exit
}, },
'2' => { '2' => {
trace!{"Retrieved action selection 2. Trigger secondary action"} 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 ControlFlow::Exit
}, },
'3' => { '3' => {
trace!{"Retrieved action selection 3. Trigger tertiary action"} 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 ControlFlow::Exit
}, },
'4' => { '4' => {
trace!{"Retrieved action selection 4. Trigger quaternary action"} 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 ControlFlow::Exit
}, },
_ => { _ => {

View file

@ -12,7 +12,7 @@ impl Matcher for PrefixMatcher {
fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> { fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> {
let mut indices = 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)); (0..needle.len()).for_each(|i| indices.push(i));
return Option::Some((1.0, indices)); return Option::Some((1.0, indices));
} }

View file

@ -26,7 +26,7 @@ pub trait Source: Send + Sync {
fn name(&self) -> &'static str; fn name(&self) -> &'static str;
fn entries(&mut self) -> Vec<Entry>; fn entries(&mut self) -> Vec<Entry>;
fn action(&self, entry: &Entry, action: Action); fn action(&self, entry: &Entry, action: Action);
fn actions(&self, entry: &Entry) -> Vec<String> fn actions(&self, _entry: &Entry) -> Vec<String>
{ vec![] } { vec![] }
} }
@ -37,6 +37,11 @@ pub trait Matcher: Send + Sync {
pub struct Roftl { pub struct Roftl {
sources: Vec<SourceRef>, sources: Vec<SourceRef>,
matcher: MatcherRef, 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<Entry>, entries: Vec<Entry>,
narrow_map: Vec<usize> narrow_map: Vec<usize>
} }
@ -46,6 +51,7 @@ impl Roftl {
Roftl { Roftl {
sources: vec![], sources: vec![],
matcher: PrefixMatcher::new(), matcher: PrefixMatcher::new(),
primary_matcher: PrefixMatcher::new(),
entries: vec![], entries: vec![],
narrow_map: vec![] narrow_map: vec![]
} }
@ -65,6 +71,11 @@ impl Roftl {
self self
} }
pub fn with_primary_matcher(mut self, matcher: MatcherRef) -> Self {
self.primary_matcher = matcher;
self
}
pub fn source(&mut self) { pub fn source(&mut self) {
self.entries = self.sources self.entries = self.sources
.par_iter_mut() .par_iter_mut()
@ -76,6 +87,21 @@ impl Roftl {
pub fn narrow(&mut self, input: &str) -> Vec<(&Entry, Vec<usize>)> { pub fn narrow(&mut self, input: &str) -> Vec<(&Entry, Vec<usize>)> {
let mut scored_entries: Vec<(f64, usize, &Entry, Vec<usize>)> = self.entries let mut scored_entries: Vec<(f64, usize, &Entry, Vec<usize>)> = self.entries
.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.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() .par_iter()
.enumerate() .enumerate()
.filter_map(|(idx, entry)| { .filter_map(|(idx, entry)| {
@ -85,6 +111,7 @@ impl Roftl {
return match_result.map(|(score, indices)| (score, idx, entry, indices) ) return match_result.map(|(score, indices)| (score, idx, entry, indices) )
}) })
.collect(); .collect();
}
scored_entries scored_entries
.par_sort_by(|e1, e2| e1.0.partial_cmp(&e2.0).unwrap()); .par_sort_by(|e1, e2| e1.0.partial_cmp(&e2.0).unwrap());
@ -100,7 +127,7 @@ impl Roftl {
.collect() .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); let (entry, source) = self.find_selection(selection_id);
source.action(entry, action); source.action(entry, action);
} }