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]]
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"

View file

@ -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"

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 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<dyn Error>> {
.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<dyn Error>> {
enum Mode {
Selection,
Actions
Actions,
}
struct RoftlLoop {
@ -105,27 +109,25 @@ impl RoftlLoop {
if window_id == self.window.id()
&& matches!{self.mode, Mode::Selection} =>
{
if self.input_changed {
trace!{"Redrawing with input {}", self.input_buffer}
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}
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
},
_ => {

View file

@ -12,7 +12,7 @@ impl Matcher for PrefixMatcher {
fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> {
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));
}

View file

@ -26,7 +26,7 @@ pub trait Source: Send + Sync {
fn name(&self) -> &'static str;
fn entries(&mut self) -> Vec<Entry>;
fn action(&self, entry: &Entry, action: Action);
fn actions(&self, entry: &Entry) -> Vec<String>
fn actions(&self, _entry: &Entry) -> Vec<String>
{ vec![] }
}
@ -37,6 +37,11 @@ pub trait Matcher: Send + Sync {
pub struct Roftl {
sources: Vec<SourceRef>,
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>,
narrow_map: Vec<usize>
}
@ -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()
@ -76,6 +87,21 @@ impl Roftl {
pub fn narrow(&mut self, input: &str) -> Vec<(&Entry, Vec<usize>)> {
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()
.enumerate()
.filter_map(|(idx, entry)| {
@ -85,6 +111,7 @@ impl Roftl {
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);
}