Read settings from configuration file or env

This commit is contained in:
Armin Friedl 2021-11-21 20:43:13 +01:00
parent d1e9866bd3
commit 3b7bff5bd7
8 changed files with 274 additions and 25 deletions

119
Cargo.lock generated
View file

@ -162,6 +162,22 @@ dependencies = [
"objc", "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]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.7.0" version = "0.7.0"
@ -584,6 +600,12 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]] [[package]]
name = "jni-sys" name = "jni-sys"
version = "0.3.0" version = "0.3.0"
@ -634,6 +656,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.5" version = "0.4.5"
@ -785,6 +813,17 @@ dependencies = [
"memoffset", "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]] [[package]]
name = "nom" name = "nom"
version = "6.1.2" version = "6.1.2"
@ -818,6 +857,24 @@ dependencies = [
"winapi", "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]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.13.0" version = "1.13.0"
@ -1109,6 +1166,7 @@ dependencies = [
"anyhow", "anyhow",
"cairo-rs", "cairo-rs",
"cairo-sys-rs", "cairo-sys-rs",
"config",
"env_logger", "env_logger",
"flx-rs", "flx-rs",
"freedesktop_entry_parser", "freedesktop_entry_parser",
@ -1118,12 +1176,19 @@ dependencies = [
"nix 0.23.0", "nix 0.23.0",
"pangocairo", "pangocairo",
"rayon", "rayon",
"serde 1.0.130",
"walkdir", "walkdir",
"winit", "winit",
"xcb", "xcb",
"xcb-util", "xcb-util",
] ]
[[package]]
name = "rust-ini"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.5" version = "1.0.5"
@ -1151,11 +1216,54 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.130" version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 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]] [[package]]
name = "slab" name = "slab"
@ -1297,7 +1405,7 @@ version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [ dependencies = [
"serde", "serde 1.0.130",
] ]
[[package]] [[package]]
@ -1588,3 +1696,12 @@ name = "xml-rs"
version = "0.8.4" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" 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",
]

View file

@ -10,6 +10,8 @@ log = "0.4"
env_logger = "0.9" env_logger = "0.9"
rayon = "1.5" rayon = "1.5"
anyhow = "1.0" anyhow = "1.0"
config = "0.11"
serde = {version = "1.0", features = ["derive"]}
# ui # ui
winit = {git="https://github.com/rust-windowing/winit"} winit = {git="https://github.com/rust-windowing/winit"}

14
default.toml Normal file
View file

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

View file

@ -1,5 +1,6 @@
pub mod shared; pub mod shared;
pub mod roftl; pub mod roftl;
pub mod settings;
use super::matcher; use super::matcher;
use super::sources; use super::sources;

View file

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::sync::RwLock; use std::sync::RwLock;
use std::sync::Arc; use std::sync::Arc;
use log::debug; use log::debug;
@ -24,7 +25,8 @@ pub struct Roftl {
// switch where surprises run counter to muscle memory and are annoying. // switch where surprises run counter to muscle memory and are annoying.
primary_matcher: MatcherRef, primary_matcher: MatcherRef,
entries: Vec<Entry>, entries: Vec<Entry>,
narrow_map: Vec<usize> narrow_map: Vec<usize>,
completions: HashMap<String, String>
} }
impl Roftl { impl Roftl {
@ -36,7 +38,8 @@ impl Roftl {
matcher: PrefixMatcher::new(), matcher: PrefixMatcher::new(),
primary_matcher: PrefixMatcher::new(), primary_matcher: PrefixMatcher::new(),
entries: vec![], entries: vec![],
narrow_map: vec![] narrow_map: vec![],
completions: HashMap::default()
} }
} }
@ -65,6 +68,11 @@ impl Roftl {
self self
} }
pub fn with_completions(mut self, completions: HashMap<String, String>) -> Self {
self.completions = completions;
self
}
pub fn source(&mut self) { pub fn source(&mut self) {
self.source_primary(); self.source_primary();
self.source_additional(); self.source_additional();
@ -96,10 +104,6 @@ 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 completions = std::collections::HashMap::new();
completions.insert("e", "emacs");
completions.insert("f", "firefox");
// check if we received anything in self. // check if we received anything in self.
if let Some(ref rx) = self.source_chan { if let Some(ref rx) = self.source_chan {
if let Ok(mut entries) = rx.try_recv() { 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 // we need to primary_matcher here s.t. only the primary matcher
// and not self is captured by the filter_map lambda // and not self is captured by the filter_map lambda
let completions = &self.completions;
let primary_matcher = &self.primary_matcher; let primary_matcher = &self.primary_matcher;
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() .par_iter()
@ -121,7 +126,9 @@ impl Roftl {
if input.is_empty() { return Some((0.0, idx, entry, vec![])) } 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); let match_result = primary_matcher.try_match(&entry.name, input);
return match_result.map(|(score, indices)| (score, idx, entry, indices) ) return match_result.map(|(score, indices)| (score, idx, entry, indices) )

107
src/core/settings.rs Normal file
View file

@ -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<dyn super::shared::Matcher> {
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<dyn super::shared::Source> {
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<T> {
matcher: Matcher,
source: T
}
#[derive(Debug, Deserialize)]
pub struct Sources {
primary: SourceConfig<Source>,
additional: SourceConfig<Vec<Source>>,
}
#[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<String, String>,
sources: Sources,
}
impl Settings {
pub fn parse() -> Result<Self, ConfigError> {
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<dyn super::shared::Matcher> {
self.sources.primary.matcher.init()
}
pub fn primary_source(&self) -> Box<dyn super::shared::Source> {
self.sources.primary.source.init()
}
pub fn matcher(&self) -> Box<dyn super::shared::Matcher> {
self.sources.additional.matcher.init()
}
pub fn sources(&self) -> Vec<Box<dyn super::shared::Source>> {
self.sources.additional.source
.iter()
.map(|s| s.init())
.collect()
}
pub fn completions(&self) -> HashMap<String, String> {
self.completions.clone()
}
}

View file

@ -6,8 +6,6 @@ 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, PrefixMatcher};
use self::core::roftl::Roftl; use self::core::roftl::Roftl;
mod ui; mod ui;
@ -18,14 +16,19 @@ mod core;
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
env_logger::init(); env_logger::init();
let settings = core::settings::Settings::parse()?;
debug!{"Settings {:?}", settings}
debug!{"Set up roftl"}; debug!{"Set up roftl"};
let mut roftl = Roftl::new() let mut roftl = Roftl::new()
.add_source(sources::TestSource::new("ts1")) .with_primary_source(settings.primary_source())
.add_source(sources::Apps::new()) .with_primary_matcher(settings.primary_matcher())
.add_source(sources::ShellHost::new()) .with_matcher(settings.matcher())
.with_matcher(FuseMatcher::new()) .with_completions(settings.completions());
.with_primary_source(sources::Window::new())
.with_primary_matcher(PrefixMatcher::new()); for source in settings.sources() {
roftl = roftl.add_source(source);
}
debug!{"Source roftl sources"} debug!{"Source roftl sources"}
roftl.source(); roftl.source();
@ -279,5 +282,3 @@ impl RoftlLoop {
} }
} }

View file

@ -16,7 +16,7 @@ impl Window {
Box::new(Window { action_data: HashMap::<u64, (u32,i32)>::new() }) Box::new(Window { action_data: HashMap::<u64, (u32,i32)>::new() })
} }
fn is_normal_window(&self, con: &ewmh::Connection, window: u32) -> Result<bool, xcb::GenericError> { fn is_normal_window(&self, con: &ewmh::Connection, window: u32) -> Result<bool, xcb::ReplyError> {
let wm_type_reply = ewmh::get_wm_window_type(con, window).get_reply()?; let wm_type_reply = ewmh::get_wm_window_type(con, window).get_reply()?;
for atom in wm_type_reply.atoms() { for atom in wm_type_reply.atoms() {
@ -28,15 +28,15 @@ impl Window {
Ok(false) Ok(false)
} }
fn window_category(&self, con: &ewmh::Connection, window: u32) -> Result<String, xcb::GenericError> { fn window_category(&self, con: &ewmh::Connection, window: u32) -> Result<String, xcb::ReplyError> {
Ok(icccm::get_wm_class(&con, window).get_reply()?.class().into()) Ok(icccm::get_wm_class(&con, window).get_reply()?.class().into())
} }
fn window_title(&self, con: &ewmh::Connection, window: u32) -> Result<String, xcb::GenericError> { fn window_title(&self, con: &ewmh::Connection, window: u32) -> Result<String, xcb::ReplyError> {
Ok(ewmh::get_wm_name(con, window).get_reply()?.string().into()) 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 window_desktop = ewmh::get_wm_desktop(con, window).get_reply()?;
let active_window = ewmh::get_active_window(con, screen).get_reply()?; let active_window = ewmh::get_active_window(con, screen).get_reply()?;
ewmh::set_current_desktop(con, screen, window_desktop); ewmh::set_current_desktop(con, screen, window_desktop);
@ -47,7 +47,7 @@ impl Window {
Ok(()) 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_horz_atom = ewmh::Connection::WM_STATE_MAXIMIZED_HORZ(con);
let max_vert_atom = ewmh::Connection::WM_STATE_MAXIMIZED_VERT(con); let max_vert_atom = ewmh::Connection::WM_STATE_MAXIMIZED_VERT(con);
let action_atom = ewmh::STATE_TOGGLE; let action_atom = ewmh::STATE_TOGGLE;
@ -58,13 +58,13 @@ impl Window {
Ok(()) 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} debug!{"Toggle maximize for {}", window}
ewmh::request_close_window(con, screen, window, XCB_CURRENT_TIME, 0).request_check() 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 hidden_atom = ewmh::Connection::WM_STATE_HIDDEN(con);
let action_atom = ewmh::STATE_TOGGLE; let action_atom = ewmh::STATE_TOGGLE;