diff --git a/Cargo.lock b/Cargo.lock index f0b4d0a..16795c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2358,6 +2358,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2892,6 +2898,7 @@ dependencies = [ "thiserror 2.0.12", "toml", "tree_magic_mini", + "urlencoding", "which", "wl-clipboard-rs", ] diff --git a/README.md b/README.md index f7b7789..3342366 100644 --- a/README.md +++ b/README.md @@ -9,17 +9,13 @@ Worf has more features than wofi though, so there won't be 100% compatibility. Broken compatibility with wofi is not considered a bug, but issues regarding that will be accepted if you would rather use worf than wofi. -**While the main branch in general is stable and I am using this on a daily basis, it is not ready yet -to be used as a library, a lot of things are still moving around.** - It supports various modes: * Math * DRun * File * Ssh * Run -* Emoji -* // WebSearch --> tracked by https://github.com/alexmohr/worf/issues/55 +* Emoji * Auto Auto mode tries to detect the desired mode automatically, to achieve this some modes require a prefix in the search. diff --git a/worf/Cargo.toml b/worf/Cargo.toml index fe17888..12368aa 100644 --- a/worf/Cargo.toml +++ b/worf/Cargo.toml @@ -53,3 +53,4 @@ emoji = "0.2.1" wl-clipboard-rs = "0.9.2" notify-rust="4.11.7" thiserror = "2.0.12" +urlencoding = "2.1.3" diff --git a/worf/src/lib/config.rs b/worf/src/lib/config.rs index eee8c05..2d0bb19 100644 --- a/worf/src/lib/config.rs +++ b/worf/src/lib/config.rs @@ -86,6 +86,9 @@ pub enum Mode { /// Emoji browser Emoji, + + /// Open search engine. + WebSearch, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -136,6 +139,7 @@ impl FromStr for Mode { "math" => Ok(Mode::Math), "ssh" => Ok(Mode::Ssh), "emoji" => Ok(Mode::Emoji), + "websearch" => Ok(Mode::WebSearch), "auto" => Ok(Mode::Auto), _ => Err(Error::InvalidArgument( format!("{s} is not a valid argument, see help for details").to_owned(), @@ -412,6 +416,11 @@ pub struct Config { /// See `KeyDetectionType` for details. #[clap(long = "key-detection-type")] key_detection_type: Option, + + /// Defines the search query to use. + /// Defaults to `` + #[clap(long = "search-query")] + search_query: Option, } impl Config { @@ -499,6 +508,7 @@ impl Config { Mode::Auto => "auto".to_owned(), Mode::Ssh => "ssh".to_owned(), Mode::Emoji => "emoji".to_owned(), + Mode::WebSearch => "websearch".to_owned(), }, }, @@ -646,6 +656,13 @@ impl Config { pub fn dynamic_lines_limit(&self) -> bool { self.dynamic_lines_limit.unwrap_or(true) } + + #[must_use] + pub fn search_query(&self) -> String { + self.search_query + .clone() + .unwrap_or_else(|| "https://duckduckgo.com/?q=".to_owned()) + } } fn default_false() -> bool { diff --git a/worf/src/lib/modes/auto.rs b/worf/src/lib/modes/auto.rs index c546aca..df28f2d 100644 --- a/worf/src/lib/modes/auto.rs +++ b/worf/src/lib/modes/auto.rs @@ -1,5 +1,6 @@ use regex::Regex; +use crate::modes::search::SearchProvider; use crate::{ Error, config::Config, @@ -20,7 +21,7 @@ enum AutoRunType { DRun, File, Ssh, - // WebSearch, + WebSearch, } #[derive(Clone)] @@ -29,6 +30,7 @@ struct AutoItemProvider { file: FileItemProvider, math: MathProvider, ssh: SshProvider, + search: SearchProvider, last_mode: Option, } @@ -44,6 +46,7 @@ impl AutoItemProvider { file: FileItemProvider::new(AutoRunType::File, config.sort_order()), math: MathProvider::new(AutoRunType::Math), ssh: SshProvider::new(AutoRunType::Ssh, &config.sort_order()), + search: SearchProvider::new(AutoRunType::WebSearch, config.search_query()), last_mode: None, } } @@ -66,7 +69,7 @@ impl AutoItemProvider { fn contains_math_functions_or_starts_with_number(input: &str) -> bool { // Regex for function names (word boundaries to match whole words) - let math_functions = r"\b(sqrt|abs|exp|ln|sin|cos|tan|asin|acos|atan|atan2|sinh|cosh|tanh|asinh|acosh|atanh|floor|ceil|round|signum|min|max|pi|e)\b"; + let math_functions = r"\b(sqrt|abs|exp|ln|sin|cos|tan|asin|acos|atan|atan2|sinh|cosh|tanh|asinh|acosh|atanh|floor|ceil|round|signum|min|max|pi|e|0x|0b|\||&|<<|>>|\^)\b"; // Regex for strings that start with a number (including decimals) let starts_with_number = r"^\s*[+-]?(\d+(\.\d*)?|\.\d+)"; @@ -90,6 +93,13 @@ impl ItemProvider for AutoItemProvider { (AutoRunType::File, self.file.get_elements(search_opt)) } else if search.starts_with("ssh") { (AutoRunType::Ssh, self.ssh.get_elements(search_opt)) + } else if search.starts_with('?') { + let re = Regex::new(r"^\?\s*").unwrap(); + let query = re.replace(search, ""); + ( + AutoRunType::WebSearch, + self.search.get_elements(Some(&query)), + ) } else { return self.default_auto_elements(search_opt); }; @@ -131,7 +141,7 @@ pub fn show(config: &Config) -> Result<(), Error> { provider.clone(), true, Some( - vec!["ssh", "emoji", "^\\$\\w+"] + vec!["ssh", "emoji", "^\\$\\w+", "^\\?\\s*"] .into_iter() .map(|s| Regex::new(s).unwrap()) .collect(), @@ -160,6 +170,12 @@ pub fn show(config: &Config) -> Result<(), Error> { ssh::launch(&selection_result, config)?; break; } + AutoRunType::WebSearch => { + if let Some(action) = selection_result.action { + spawn_fork(&action, None)?; + } + break; + } } } else if selection_result.label.starts_with("ssh") { selection_result.label = selection_result.label.chars().skip(4).collect(); diff --git a/worf/src/lib/modes/mod.rs b/worf/src/lib/modes/mod.rs index 69892cf..1221c2f 100644 --- a/worf/src/lib/modes/mod.rs +++ b/worf/src/lib/modes/mod.rs @@ -9,6 +9,7 @@ pub mod emoji; pub mod file; pub mod math; pub mod run; +pub mod search; pub mod ssh; pub(crate) fn load_cache(cache_path: Option) -> (Option, HashMap) { diff --git a/worf/src/lib/modes/search.rs b/worf/src/lib/modes/search.rs new file mode 100644 index 0000000..7091d19 --- /dev/null +++ b/worf/src/lib/modes/search.rs @@ -0,0 +1,60 @@ +use urlencoding::encode; + +use crate::desktop::spawn_fork; +use crate::{ + Error, + config::Config, + gui::{self, ItemProvider, MenuItem}, +}; + +#[derive(Clone)] +pub(crate) struct SearchProvider { + search_query: String, + data: T, +} + +impl SearchProvider { + pub fn new(data: T, search_query: String) -> Self { + Self { + search_query, + data: data.clone(), + } + } +} + +impl ItemProvider for SearchProvider { + fn get_elements(&mut self, query: Option<&str>) -> (bool, Vec>) { + if let Some(query) = query { + let url = format!("{}{}", self.search_query, encode(query)); + let run_search = MenuItem::new( + format!("Search {query}"), + None, + Some(format!("xdg-open {url}")), + vec![], + None, + 0.0, + Some(self.data.clone()), + ); + (true, vec![run_search]) + } else { + (false, vec![]) + } + } + + fn get_sub_elements(&mut self, _: &MenuItem) -> (bool, Option>>) { + (false, None) + } +} + +/// Shows the emoji mode +/// # Errors +/// +/// Forwards errors from the gui. See `gui::show` for details. +pub fn show(config: &Config) -> Result<(), Error> { + let provider = SearchProvider::new(String::new(), config.search_query()); + let selection_result = gui::show(config.clone(), provider, true, None, None)?; + match selection_result.menu.action { + None => Err(Error::MissingAction), + Some(action) => spawn_fork(&action, None), + } +} diff --git a/worf/src/main.rs b/worf/src/main.rs index d0573b1..f9c24de 100644 --- a/worf/src/main.rs +++ b/worf/src/main.rs @@ -41,6 +41,7 @@ fn main() -> anyhow::Result<()> { Mode::Ssh => modes::ssh::show(&config), Mode::Emoji => modes::emoji::show(&config), Mode::Auto => modes::auto::show(&config), + Mode::WebSearch => modes::search::show(&config), }; if let Err(err) = result {