Quick switch and primary mode
This commit is contained in:
parent
bdb1520cf0
commit
9fc30022ec
6 changed files with 63 additions and 32 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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
3
sh/htop.sh
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
htop
|
40
src/main.rs
40
src/main.rs
|
@ -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
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
31
src/roftl.rs
31
src/roftl.rs
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue