diff --git a/makefile b/makefile index 03064d2..4c78131 100644 --- a/makefile +++ b/makefile @@ -37,6 +37,11 @@ man: config test: all seq 1 100 | target/dmenu $(ARGS) +debug: m4 + cd src/build && cargo build $(XINERAMA_FLAGS) + cp src/build/target/debug/dmenu target + seq 1 100 | RUST_BACKTRACE=1 target/dmenu $(ARGS) + plugins: cd src/config && cargo run --bin list-plugins diff --git a/src/config/src/config.rs b/src/config/src/config.rs index 20785dc..524504d 100644 --- a/src/config/src/config.rs +++ b/src/config/src/config.rs @@ -26,15 +26,16 @@ fn main() { // prepare to edit cli_base args let mut yaml = get_yaml("../dmenu/cli_base.yml"); - let yaml_args: &mut Vec = get_yaml_args(&mut yaml); + let yaml_args: &mut Vec = get_yaml_args(&mut yaml).unwrap(); // For every plugin, check if it has arguements. If so, add them to clap and overrider // While we're here, set proc_use to watch the plugin entry points for plugin in plugins { let mut plugin_yaml = get_yaml(&format!("../plugins/{}/plugin.yml", plugin)); - let plugin_yaml_args: &mut Vec = get_yaml_args(&mut plugin_yaml); - - yaml_args.append(plugin_yaml_args); + + if let Some(plugin_yaml_args) = get_yaml_args(&mut plugin_yaml) { + yaml_args.append(plugin_yaml_args); + } watch_globs.push(( format!("../plugins/{}/{}", plugin, get_yaml_top_level(&mut plugin_yaml, "entry") diff --git a/src/config/src/util.rs b/src/config/src/util.rs index 7c578d2..7e5d0f3 100644 --- a/src/config/src/util.rs +++ b/src/config/src/util.rs @@ -55,7 +55,7 @@ pub fn get_yaml_top_level<'a>(yaml: &'a mut Yaml, fieldsearch: &str) -> Option<& } #[allow(unused)] -pub fn get_yaml_args(yaml: &mut Yaml) -> &mut Vec { +pub fn get_yaml_args(yaml: &mut Yaml) -> Option<&mut Vec> { match yaml { Yaml::Hash(hash) => { for field in hash { @@ -64,7 +64,7 @@ pub fn get_yaml_args(yaml: &mut Yaml) -> &mut Vec { match field.1 { Yaml::Array(arr) => { sanitize_args(arr); - return arr; + return Some(arr); }, _ => panic!("Incorrect arg format on cli_base"), } @@ -74,7 +74,7 @@ pub fn get_yaml_args(yaml: &mut Yaml) -> &mut Vec { }, _ => panic!("Incorrect yaml format on cli_base"), } - panic!("No args found in yaml object"); + None } fn sanitize_args(args: &mut Vec) { diff --git a/src/dmenu/item.rs b/src/dmenu/item.rs index 512acf2..e136d97 100644 --- a/src/dmenu/item.rs +++ b/src/dmenu/item.rs @@ -4,6 +4,7 @@ use crate::drw::{Drw, TextOption::*}; use crate::config::{Schemes::*, DefaultWidth}; use regex::Regex; +#[allow(unused_imports)] pub enum MatchCode {Exact, Prefix, Substring, None} pub use MatchCode::*; #[derive(Debug)] @@ -24,6 +25,7 @@ impl Item { pub fn draw(&self, x: c_int, y: c_int, w: c_int, drw: &mut Drw) -> Result { drw.text(x, y, w as u32, drw.pseudo_globals.bh as u32, drw.pseudo_globals.lrpad as u32/2, Other(&self.text), false) } + #[allow(unused)] // won't be used if overriden pub fn matches(&self, re: &Regex) -> MatchCode { match re.find_iter(&self.text) .nth(0).map(|m| (m.start(), m.end())) diff --git a/src/dmenu/plugin_entry.rs b/src/dmenu/plugin_entry.rs index e1400ae..13f6abe 100644 --- a/src/dmenu/plugin_entry.rs +++ b/src/dmenu/plugin_entry.rs @@ -1,6 +1,7 @@ #[allow(unused_imports)] use crate::clapflags::CLAP_FLAGS; use crate::drw::Drw; +#[allow(unused_imports)] use crate::item::{Item, MatchCode}; use overrider::*; diff --git a/src/dmenu/run.rs b/src/dmenu/run.rs index 2046de7..7281195 100644 --- a/src/dmenu/run.rs +++ b/src/dmenu/run.rs @@ -359,9 +359,7 @@ impl Drw { } }, } - if self.draw().is_err() { - return Ok(true); - } + self.draw()?; } Ok(false) } diff --git a/src/plugins/fuzzy/deps.toml b/src/plugins/fuzzy/deps.toml new file mode 100644 index 0000000..79026d9 --- /dev/null +++ b/src/plugins/fuzzy/deps.toml @@ -0,0 +1 @@ +fuzzy-matcher = "0.3.4" \ No newline at end of file diff --git a/src/plugins/fuzzy/main.rs b/src/plugins/fuzzy/main.rs new file mode 100644 index 0000000..c628d07 --- /dev/null +++ b/src/plugins/fuzzy/main.rs @@ -0,0 +1,32 @@ +use overrider::*; + +#[allow(unused_imports)] +use crate::clapflags::CLAP_FLAGS; + +use fuzzy_matcher::FuzzyMatcher; +use fuzzy_matcher::skim::SkimMatcherV2; + +use crate::drw::Drw; +use crate::item::Item; + +#[override_default] +impl Drw { + pub fn gen_matches(&mut self) -> Result, String> { + let searchterm = self.input.clone(); + let mut items: Vec<(Item, i64)> = + self.items.as_mut().unwrap().data.iter().map(|item| { + let matcher: Box = Box::new(SkimMatcherV2::default()); + (item.clone(), + if let Some(score) = matcher.fuzzy_match(&item.text, &searchterm) { + -score + } else { + 1 + }) + }).collect(); + items.retain(|(_, score)| *score <= 0); + items.sort_by_key(|(item, _)| item.text.len()); // this prioritizes exact matches + items.sort_by_key(|(_, score)| *score); + + Ok(items.into_iter().map(|(item, _)| item).collect()) + } +} diff --git a/src/plugins/fuzzy/plugin.yml b/src/plugins/fuzzy/plugin.yml new file mode 100644 index 0000000..9362e8e --- /dev/null +++ b/src/plugins/fuzzy/plugin.yml @@ -0,0 +1,3 @@ +about: Fuzzy string matching for searches +entry: main.rs +cargo_dependencies: deps.toml diff --git a/src/plugins/spellcheck/deps.toml b/src/plugins/spellcheck/deps.toml new file mode 100644 index 0000000..a911117 --- /dev/null +++ b/src/plugins/spellcheck/deps.toml @@ -0,0 +1 @@ +ispell = "0.3.1" \ No newline at end of file diff --git a/src/plugins/spellcheck/main.rs b/src/plugins/spellcheck/main.rs new file mode 100644 index 0000000..bf4b017 --- /dev/null +++ b/src/plugins/spellcheck/main.rs @@ -0,0 +1,71 @@ +use overrider::*; + +use ispell::{SpellLauncher}; +use std::process::{Command, Stdio}; +use std::io::Write; + +use crate::drw::Drw; +use crate::item::Item; + +#[override_flag(flag = spellcheck)] +impl Drw { + pub fn gen_matches(&mut self) -> Result, String> { + let checker = SpellLauncher::new() + .aspell() + .launch(); + + let (first, second) = self.input.split_at(self.pseudo_globals.cursor); + let first_replaced = first.replace(" ", ""); + let second_replaced = second.replace(" ", ""); + self.pseudo_globals.cursor = first_replaced.chars().count(); + self.input = first_replaced+&second_replaced; + + match checker { + Ok(mut checker) => { + match checker.check(&self.input) { + Ok(mut res) => { + if res.is_empty() { + Ok(vec![Item::new(self.input.clone(), false, self)?]) + } else { + let mut ret = Vec::new(); + for word in res.swap_remove(0).suggestions.into_iter() { + ret.push(Item::new(word, false, self)?); + } + Ok(ret) + } + }, + Err(err) => Err(format!("Error: could not run aspell: {}", err)) + } + }, + Err(err) => Err(format!("Error: could not start aspell: {}", err)) + } + } + pub fn dispose(&mut self, output: String, recommendation: bool) -> Result { + if output.len() > 0 { + let mut child = Command::new("xclip") + .arg("-sel") + .arg("clip") + .stdin(Stdio::piped()) + .spawn() + .map_err(|_| "Failed to spawn child process".to_owned())?; + + child.stdin.as_mut().ok_or("Failed to open stdin".to_owned())? + .write_all(output.as_bytes()).map_err(|_| "Failed to write to stdin".to_owned())?; + } + Ok(recommendation) + } +} + +use crate::config::{ConfigDefault, DefaultWidth}; +#[override_flag(flag = spellcheck)] +impl ConfigDefault { + pub fn nostdin() -> bool { + true + } + pub fn render_flex() -> bool { + true + } + pub fn render_default_width() -> DefaultWidth { + DefaultWidth::Custom(10) + } +} diff --git a/src/plugins/spellcheck/plugin.yml b/src/plugins/spellcheck/plugin.yml new file mode 100644 index 0000000..86d6e8b --- /dev/null +++ b/src/plugins/spellcheck/plugin.yml @@ -0,0 +1,11 @@ +about: Single word spellcheck +entry: main.rs +cargo_dependencies: deps.toml + +args: + - spellcheck: + help: Enter spellcheck mode + long_help: "Enter spellcheck mode. Begin typing a word for spelling suggestions.\nOnly accepts + single word queries.\nPressing Enter will copy to clipboard before exiting." + long: spellcheck + visible_aliases: sc