diff --git a/Cargo.lock b/Cargo.lock index 26ce654..50df510 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,6 +162,22 @@ dependencies = [ "objc", ] +[[package]] +name = "config" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" +dependencies = [ + "lazy_static", + "nom 5.1.2", + "rust-ini", + "serde 1.0.130", + "serde-hjson", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "core-foundation" version = "0.7.0" @@ -584,6 +600,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + [[package]] name = "jni-sys" version = "0.3.0" @@ -634,6 +656,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + [[package]] name = "lock_api" version = "0.4.5" @@ -785,6 +813,17 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "nom" version = "6.1.2" @@ -818,6 +857,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -1109,6 +1166,7 @@ dependencies = [ "anyhow", "cairo-rs", "cairo-sys-rs", + "config", "env_logger", "flx-rs", "freedesktop_entry_parser", @@ -1118,12 +1176,19 @@ dependencies = [ "nix 0.23.0", "pangocairo", "rayon", + "serde 1.0.130", "walkdir", "winit", "xcb", "xcb-util", ] +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" + [[package]] name = "ryu" version = "1.0.5" @@ -1151,11 +1216,54 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "serde" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" + [[package]] name = "serde" version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-hjson" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" +dependencies = [ + "lazy_static", + "num-traits 0.1.43", + "regex", + "serde 0.8.23", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19" +dependencies = [ + "itoa", + "ryu", + "serde 1.0.130", +] [[package]] name = "slab" @@ -1297,7 +1405,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ - "serde", + "serde 1.0.130", ] [[package]] @@ -1588,3 +1696,12 @@ name = "xml-rs" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml index fa8e3f3..f41462e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,8 @@ log = "0.4" env_logger = "0.9" rayon = "1.5" anyhow = "1.0" +config = "0.11" +serde = {version = "1.0", features = ["derive"]} # ui winit = {git="https://github.com/rust-windowing/winit"} diff --git a/default.toml b/default.toml new file mode 100644 index 0000000..f0ffb67 --- /dev/null +++ b/default.toml @@ -0,0 +1,14 @@ +log_level = "trace" + +[completions] +"e" = "emacs" +"f" = "firefox" +"t" = "terminal" + +[sources.primary] +matcher = "Prefix" +source = "Windows" + +[sources.additional] +matcher = "Flex" +source = ["Apps", "Shell", "Test"] diff --git a/src/core/mod.rs b/src/core/mod.rs index 05fb2ce..035a6e9 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,5 +1,6 @@ pub mod shared; pub mod roftl; +pub mod settings; use super::matcher; use super::sources; diff --git a/src/core/roftl.rs b/src/core/roftl.rs index c1c2202..cdf9989 100644 --- a/src/core/roftl.rs +++ b/src/core/roftl.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::sync::RwLock; use std::sync::Arc; use log::debug; @@ -24,7 +25,8 @@ pub struct Roftl { // switch where surprises run counter to muscle memory and are annoying. primary_matcher: MatcherRef, entries: Vec, - narrow_map: Vec + narrow_map: Vec, + completions: HashMap } impl Roftl { @@ -36,7 +38,8 @@ impl Roftl { matcher: PrefixMatcher::new(), primary_matcher: PrefixMatcher::new(), entries: vec![], - narrow_map: vec![] + narrow_map: vec![], + completions: HashMap::default() } } @@ -65,6 +68,11 @@ impl Roftl { self } + pub fn with_completions(mut self, completions: HashMap) -> Self { + self.completions = completions; + self + } + pub fn source(&mut self) { self.source_primary(); self.source_additional(); @@ -96,10 +104,6 @@ impl Roftl { } pub fn narrow(&mut self, input: &str) -> Vec<(&Entry, Vec)> { - let mut completions = std::collections::HashMap::new(); - completions.insert("e", "emacs"); - completions.insert("f", "firefox"); - // check if we received anything in self. if let Some(ref rx) = self.source_chan { if let Ok(mut entries) = rx.try_recv() { @@ -112,6 +116,7 @@ impl Roftl { // we need to primary_matcher here s.t. only the primary matcher // and not self is captured by the filter_map lambda + let completions = &self.completions; let primary_matcher = &self.primary_matcher; let mut scored_entries: Vec<(f64, usize, &Entry, Vec)> = self.entries .par_iter() @@ -121,7 +126,9 @@ impl Roftl { if input.is_empty() { return Some((0.0, idx, entry, vec![])) } - let input = completions.get(input).unwrap_or(&input); + let input = completions.get(input) + .map(|s| s.as_str()) // &String -> &str + .unwrap_or(input); let match_result = primary_matcher.try_match(&entry.name, input); return match_result.map(|(score, indices)| (score, idx, entry, indices) ) diff --git a/src/core/settings.rs b/src/core/settings.rs new file mode 100644 index 0000000..2a0ed1b --- /dev/null +++ b/src/core/settings.rs @@ -0,0 +1,107 @@ +use std::collections::HashMap; +use config::ConfigError; +use config::Config; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct Theme { + border: f64, + divider: f64 +} + +#[derive(Debug, Deserialize)] +pub enum Matcher { + Prefix, Fuse, Flex, Skim +} + +impl Matcher { + pub fn init(&self) -> Box { + match self { + Matcher::Prefix => super::matcher::PrefixMatcher::new(), + Matcher::Fuse => super::matcher::FuseMatcher::new(), + Matcher::Flex => super::matcher::FlxMatcher::new(), + Matcher::Skim => super::matcher::SkimMatcher::new() + } + } +} + +#[derive(Debug, Deserialize)] +pub enum Source { + Apps, Shell, Windows, Test +} + +impl Source { + pub fn init(&self) -> Box { + match self { + Source::Apps => super::sources::Apps::new(), + Source::Shell => super::sources::ShellHost::new(), + Source::Windows => super::sources::Window::new(), + Source::Test => super::sources::TestSource::new("ts1") + } + } +} + +#[derive(Debug, Deserialize)] +pub struct SourceConfig { + matcher: Matcher, + source: T +} + +#[derive(Debug, Deserialize)] +pub struct Sources { + primary: SourceConfig, + additional: SourceConfig>, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "lowercase", remote = "log::Level")] +enum LogLevelDef { + Error, Warn, Info, Debug, Trace, +} + + +#[derive(Debug, Deserialize)] +pub struct Settings { + #[serde(with = "LogLevelDef")] + log_level: log::Level, + completions: HashMap, + sources: Sources, +} + +impl Settings { + pub fn parse() -> Result { + let mut config: Config = Config::default(); + + config.merge(config::File::with_name("default"))?; + + config.merge(config::Environment::new() + .prefix("roftl") + .separator("_") + .ignore_empty(true))?; + + config.try_into() + } + + pub fn primary_matcher(&self) -> Box { + self.sources.primary.matcher.init() + } + + pub fn primary_source(&self) -> Box { + self.sources.primary.source.init() + } + + pub fn matcher(&self) -> Box { + self.sources.additional.matcher.init() + } + + pub fn sources(&self) -> Vec> { + self.sources.additional.source + .iter() + .map(|s| s.init()) + .collect() + } + + pub fn completions(&self) -> HashMap { + self.completions.clone() + } +} diff --git a/src/main.rs b/src/main.rs index c164998..612ee90 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,6 @@ use winit::event_loop::{ControlFlow, EventLoop}; use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent::{CloseRequested, ReceivedCharacter}}; -use crate::matcher::{FuseMatcher, PrefixMatcher}; - use self::core::roftl::Roftl; mod ui; @@ -18,14 +16,19 @@ mod core; fn main() -> Result<(), Box> { env_logger::init(); + let settings = core::settings::Settings::parse()?; + debug!{"Settings {:?}", settings} + debug!{"Set up roftl"}; let mut roftl = Roftl::new() - .add_source(sources::TestSource::new("ts1")) - .add_source(sources::Apps::new()) - .add_source(sources::ShellHost::new()) - .with_matcher(FuseMatcher::new()) - .with_primary_source(sources::Window::new()) - .with_primary_matcher(PrefixMatcher::new()); + .with_primary_source(settings.primary_source()) + .with_primary_matcher(settings.primary_matcher()) + .with_matcher(settings.matcher()) + .with_completions(settings.completions()); + + for source in settings.sources() { + roftl = roftl.add_source(source); + } debug!{"Source roftl sources"} roftl.source(); @@ -279,5 +282,3 @@ impl RoftlLoop { } } - - diff --git a/src/sources/windows.rs b/src/sources/windows.rs index b78efd4..909a302 100644 --- a/src/sources/windows.rs +++ b/src/sources/windows.rs @@ -16,7 +16,7 @@ impl Window { Box::new(Window { action_data: HashMap::::new() }) } - fn is_normal_window(&self, con: &ewmh::Connection, window: u32) -> Result { + fn is_normal_window(&self, con: &ewmh::Connection, window: u32) -> Result { let wm_type_reply = ewmh::get_wm_window_type(con, window).get_reply()?; for atom in wm_type_reply.atoms() { @@ -28,15 +28,15 @@ impl Window { Ok(false) } - fn window_category(&self, con: &ewmh::Connection, window: u32) -> Result { + fn window_category(&self, con: &ewmh::Connection, window: u32) -> Result { Ok(icccm::get_wm_class(&con, window).get_reply()?.class().into()) } - fn window_title(&self, con: &ewmh::Connection, window: u32) -> Result { + fn window_title(&self, con: &ewmh::Connection, window: u32) -> Result { Ok(ewmh::get_wm_name(con, window).get_reply()?.string().into()) } - fn switch_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::GenericError> { + fn switch_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::ReplyError> { let window_desktop = ewmh::get_wm_desktop(con, window).get_reply()?; let active_window = ewmh::get_active_window(con, screen).get_reply()?; ewmh::set_current_desktop(con, screen, window_desktop); @@ -47,7 +47,7 @@ impl Window { Ok(()) } - fn toggle_maximize_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::GenericError> { + fn toggle_maximize_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::ReplyError> { let max_horz_atom = ewmh::Connection::WM_STATE_MAXIMIZED_HORZ(con); let max_vert_atom = ewmh::Connection::WM_STATE_MAXIMIZED_VERT(con); let action_atom = ewmh::STATE_TOGGLE; @@ -58,13 +58,13 @@ impl Window { Ok(()) } - fn close_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::GenericError> { + fn close_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::ReplyError> { debug!{"Toggle maximize for {}", window} ewmh::request_close_window(con, screen, window, XCB_CURRENT_TIME, 0).request_check() } - fn toggle_hide_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::GenericError> { + fn toggle_hide_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::ReplyError> { let hidden_atom = ewmh::Connection::WM_STATE_HIDDEN(con); let action_atom = ewmh::STATE_TOGGLE;