diff --git a/src/app/tile.rs b/src/app/tile.rs index 6c29218..a3d203c 100644 --- a/src/app/tile.rs +++ b/src/app/tile.rs @@ -66,6 +66,12 @@ impl AppIndex { app.ranking += 5; } + fn empty() -> AppIndex { + AppIndex { + by_name: BTreeMap::new(), + } + } + /// Factory function for creating pub fn from_apps(options: Vec) -> Self { let mut bmap = BTreeMap::new(); @@ -238,7 +244,7 @@ impl Tile { } else if self.page == Page::EmojiSearch { &self.emoji_apps } else { - &AppIndex::from_apps(vec![]) + &AppIndex::empty() }; let results: Vec = options .search_prefix(&query) @@ -270,10 +276,9 @@ impl Tile { /// This is the subscription function that handles hot reloading of the config fn handle_hot_reloading() -> impl futures::Stream { stream::channel(100, async |mut output| { - let mut content = fs::read_to_string( - std::env::var("HOME").unwrap_or("".to_owned()) + "/.config/rustcast/config.toml", - ) - .unwrap_or("".to_string()); + let config_path = + &(std::env::var("HOME").unwrap_or("".to_owned()) + "/.config/rustcast/config.toml"); + let mut last_modified = fs::metadata(config_path).unwrap().modified().unwrap(); let paths = default_app_paths(); let mut total_files: usize = paths @@ -282,25 +287,24 @@ fn handle_hot_reloading() -> impl futures::Stream { .sum(); loop { - let current_content = fs::read_to_string( - std::env::var("HOME").unwrap_or("".to_owned()) + "/.config/rustcast/config.toml", - ) - .unwrap_or("".to_string()); + let last_modified_check = fs::metadata(config_path).unwrap().modified().unwrap(); let current_total_files: usize = paths .par_iter() .map(|dir| count_dirs_in_dir(Path::new(dir))) .sum(); - if current_content != content { - content = current_content; - output.send(Message::ReloadConfig).await.unwrap(); + if last_modified_check != last_modified { + last_modified = last_modified_check; + info!("Config file was modified"); + let _ = output.send(Message::ReloadConfig).await; } else if total_files != current_total_files { total_files = current_total_files; - output.send(Message::ReloadConfig).await.unwrap(); + info!("App count was changed"); + let _ = output.send(Message::ReloadConfig).await; } - tokio::time::sleep(Duration::from_millis(10)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; } }) } @@ -330,7 +334,7 @@ fn handle_hotkeys() -> impl futures::Stream { { output.try_send(Message::KeyPressed(event.id)).unwrap(); } - tokio::time::sleep(Duration::from_millis(10)).await; + tokio::time::sleep(Duration::from_millis(100)).await; } }) } @@ -385,7 +389,7 @@ fn handle_recipient() -> impl futures::Stream { if let Some(abcd) = abcd { abcd.await; } - tokio::time::sleep(Duration::from_nanos(10)).await; + tokio::time::sleep(Duration::from_millis(100)).await; } }) } diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index c3ce936..293046d 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -23,6 +23,7 @@ use crate::app::menubar::menu_icon; use crate::app::tile::AppIndex; use crate::app::{Message, Page, tile::Tile}; use crate::calculator::Expr; +use crate::calculator::is_valid_expr; use crate::clipboard::ClipBoardContentType; use crate::commands::Function; use crate::config::Config; @@ -328,11 +329,15 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { }; if tile.config.buffer_rules.clear_on_enter { - window::latest() - .map(|x| x.unwrap()) - .map(Message::HideWindow) - .chain(Task::done(Message::ClearSearchQuery)) - .chain(return_focus_task) + if tile.visible { + window::latest() + .map(|x| x.unwrap()) + .map(Message::HideWindow) + .chain(Task::done(Message::ClearSearchQuery)) + .chain(return_focus_task) + } else { + Task::none() + } } else { Task::none() } @@ -373,7 +378,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { } Message::ClearSearchResults => { - tile.results = vec![]; + tile.results = Vec::new(); Task::none() } Message::WindowFocusChanged(wid, focused) => { @@ -400,32 +405,76 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { tile.query_lc = input.trim().to_lowercase(); tile.query = input; + let prev_size = tile.results.len(); - if tile.query_lc.is_empty() && tile.page != Page::ClipboardHistory { - tile.results = vec![]; - return Task::done(Message::ResizeWindow(id, DEFAULT_WINDOW_HEIGHT)); - } else if tile.query_lc == "randomvar" { - let rand_num = rand::random_range(0..100); - tile.results = vec![App { - ranking: 0, - open_command: AppCommand::Function(Function::RandomVar(rand_num)), - desc: "Easter egg".to_string(), - icons: None, - display_name: rand_num.to_string(), - search_name: String::new(), - }]; - return single_item_resize_task(id); - } else if tile.query_lc == "67" { - tile.results = vec![App { - ranking: 0, - open_command: AppCommand::Function(Function::RandomVar(67)), - desc: "Easter egg".to_string(), - icons: None, - display_name: 67.to_string(), - search_name: String::new(), - }]; - return single_item_resize_task(id); - } else if tile.query_lc.ends_with("?") { + + if tile.page == Page::ClipboardHistory && tile.query_lc != "main" { + return Task::none(); + } else if tile.page == Page::EmojiSearch && tile.query_lc.is_empty() { + tile.results = if tile.query_lc.is_empty() { + Vec::new() + } else { + tile.emoji_apps + .search_prefix(&tile.query_lc) + .map(|x| x.to_owned()) + .collect() + }; + } + + if tile.query_lc.is_empty() { + tile.results = Vec::new(); + return zero_item_resize_task(id); + }; + + match tile.query_lc.as_str() { + "randomvar" => { + let rand_num = rand::random_range(0..100); + tile.results = vec![App { + ranking: 0, + open_command: AppCommand::Function(Function::RandomVar(rand_num)), + desc: "Easter egg".to_string(), + icons: None, + display_name: rand_num.to_string(), + search_name: String::new(), + }]; + return single_item_resize_task(id); + } + "lemon" => { + tile.results = vec![App { + ranking: 0, + open_command: AppCommand::Display, + desc: "Easter Egg".to_string(), + icons: lemon_icon_handle(), + display_name: "Lemon".to_string(), + search_name: "".to_string(), + }]; + return single_item_resize_task(id); + } + "67" => { + tile.results = vec![App { + ranking: 0, + open_command: AppCommand::Function(Function::RandomVar(67)), + desc: "Easter egg".to_string(), + icons: None, + display_name: 67.to_string(), + search_name: String::new(), + }]; + return single_item_resize_task(id); + } + "cbhist" => { + task = task.chain(Task::done(Message::SwitchToPage(Page::ClipboardHistory))); + tile.page = Page::ClipboardHistory; + } + "main" => { + if tile.page != Page::Main { + task = task.chain(Task::done(Message::SwitchToPage(Page::Main))); + return Task::batch([zero_item_resize_task(id), task]); + } + } + _ => {} + } + + if tile.query_lc.ends_with("?") || tile.query_lc.split_whitespace().nth(2).is_some() { tile.results = vec![App { ranking: 0, open_command: AppCommand::Function(Function::GoogleSearch(tile.query.clone())), @@ -435,44 +484,51 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { search_name: String::new(), }]; return single_item_resize_task(id); - } else if tile.query_lc == "cbhist" { - task = task.chain(Task::done(Message::SwitchToPage(Page::ClipboardHistory))); - tile.page = Page::ClipboardHistory - } else if tile.query_lc == "main" && tile.page != Page::Main { - task = task.chain(Task::done(Message::SwitchToPage(Page::Main))); - tile.page = Page::Main; } else if tile.query_lc.starts_with(">") && tile.page == Page::Main { + let command = tile.query.strip_prefix(">").unwrap_or(""); tile.results = vec![App { ranking: 20, open_command: AppCommand::Function(Function::RunShellCommand( - tile.query.strip_prefix(">").unwrap_or("").to_string(), + command.to_string(), )), - display_name: format!( - "Shell Command: {}", - tile.query.strip_prefix(">").unwrap_or("") - ), + display_name: format!("Shell Command: {}", command), icons: None, search_name: "".to_string(), desc: "Shell Command".to_string(), }]; return single_item_resize_task(id); } + tile.handle_search_query_changed(); - if tile.results.is_empty() - && let Some(res) = Expr::from_str(&tile.query).ok() - { + if !tile.results.is_empty() { + tile.results.par_sort_by_key(|x| -x.ranking); + + let new_length = tile.results.len(); + let max_elem = min(5, new_length); + + if prev_size != new_length { + return task.chain(Task::batch([ + Task::done(Message::ResizeWindow( + id, + ((max_elem * 55) + 35 + DEFAULT_WINDOW_HEIGHT as usize) as f32, + )), + Task::done(Message::ChangeFocus(ArrowKey::Left, 1)), + ])); + } else { + return task; + } + } + if is_valid_url(&tile.query) { tile.results.push(App { ranking: 0, - open_command: AppCommand::Function(Function::Calculate(res.clone())), - desc: RUSTCAST_DESC_NAME.to_string(), + open_command: AppCommand::Function(Function::OpenWebsite(tile.query.clone())), + desc: "Web Browsing".to_string(), icons: None, - display_name: res.eval().map(|x| x.to_string()).unwrap_or("".to_string()), - search_name: "".to_string(), + display_name: "Open Website: ".to_string() + &tile.query, + search_name: String::new(), }); - } else if tile.results.is_empty() - && let Some(conversions) = unit_conversion::convert_query(&tile.query) - { + } else if let Some(conversions) = unit_conversion::convert_query(&tile.query) { tile.results = conversions .into_iter() .map(|conversion| { @@ -498,66 +554,18 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { } }) .collect(); - } else if tile.results.is_empty() && is_valid_url(&tile.query) { + } else if is_valid_expr(&tile.query) { + let res = Expr::from_str(&tile.query).unwrap(); tile.results.push(App { ranking: 0, - open_command: AppCommand::Function(Function::OpenWebsite(tile.query.clone())), - desc: "Web Browsing".to_string(), - icons: None, - display_name: "Open Website: ".to_string() + &tile.query, - search_name: "".to_string(), - }); - } else if tile.query_lc.split(' ').count() > 1 { - tile.results.push(App { - ranking: 0, - open_command: AppCommand::Function(Function::GoogleSearch(tile.query.clone())), + open_command: AppCommand::Function(Function::Calculate(res.clone())), + desc: RUSTCAST_DESC_NAME.to_string(), icons: None, - desc: "Web Search".to_string(), - display_name: format!("Search for: {}", tile.query), - search_name: String::new(), - }); - } else if tile.results.is_empty() && tile.query_lc == "lemon" { - tile.results.push(App { - ranking: 0, - open_command: AppCommand::Display, - desc: "Easter Egg".to_string(), - icons: lemon_icon_handle(), - display_name: "Lemon".to_string(), + display_name: res.eval().map(|x| x.to_string()).unwrap_or("".to_string()), search_name: "".to_string(), }); } - if !tile.query_lc.is_empty() && tile.page == Page::EmojiSearch { - tile.results = tile - .emoji_apps - .search_prefix(&tile.query_lc) - .map(|x| x.to_owned()) - .collect() - } - - tile.results.sort_by_key(|x| -x.ranking); - - let new_length = tile.results.len(); - let max_elem = min(5, new_length); - - if prev_size != new_length && tile.page != Page::ClipboardHistory { - task.chain(Task::batch([ - Task::done(Message::ResizeWindow( - id, - ((max_elem * 55) + 35 + DEFAULT_WINDOW_HEIGHT as usize) as f32, - )), - Task::done(Message::ChangeFocus(ArrowKey::Left, 1)), - ])) - } else if tile.page == Page::ClipboardHistory { - task.chain(Task::batch([ - Task::done(Message::ResizeWindow( - id, - ((7 * 55) + 35 + DEFAULT_WINDOW_HEIGHT as usize) as f32, - )), - Task::done(Message::ChangeFocus(ArrowKey::Left, 1)), - ])) - } else { - task - } + task } } } @@ -578,6 +586,11 @@ fn single_item_resize_task(id: Id) -> Task { Task::done(Message::ResizeWindow(id, 55. + DEFAULT_WINDOW_HEIGHT)) } +/// A helper function for resizing rustcast when zero results are found +fn zero_item_resize_task(id: Id) -> Task { + Task::done(Message::ResizeWindow(id, DEFAULT_WINDOW_HEIGHT)) +} + /// Handling the lemon easter egg icon fn lemon_icon_handle() -> Option { image::ImageReader::new(Cursor::new(include_bytes!("../../../docs/lemon.png"))) diff --git a/src/calculator.rs b/src/calculator.rs index c1ba3ec..df74af5 100644 --- a/src/calculator.rs +++ b/src/calculator.rs @@ -14,6 +14,11 @@ //! "log(100)" => 2 //! "log(2, 8)" => 3 +pub fn is_valid_expr(s: &str) -> bool { + let mut p = Parser::new(s); + p.parse_expr().and_then(|_| p.expect(Token::End)).is_ok() +} + #[derive(Debug, Clone, PartialEq)] pub enum Expr { Number(f64), diff --git a/src/main.rs b/src/main.rs index 28cb4a6..0b30ff2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,10 @@ use crate::{ config::Config, }; -use global_hotkey::GlobalHotKeyManager; +use global_hotkey::{ + GlobalHotKeyManager, + hotkey::{Code, HotKey, Modifiers}, +}; use log::info; use tracing_subscriber::{EnvFilter, Layer, util::SubscriberInitExt}; @@ -61,15 +64,17 @@ fn main() -> iced::Result { let manager = GlobalHotKeyManager::new().unwrap(); - let show_hide = config.toggle_hotkey.parse().unwrap(); + let show_hide = config + .toggle_hotkey + .parse() + .unwrap_or(HotKey::new(Some(Modifiers::ALT), Code::Space)); + + let cbhist = config + .clipboard_hotkey + .parse() + .unwrap_or("SUPER+SHIFT+C".parse().unwrap()); - let hotkeys = vec![ - show_hide, - config - .clipboard_hotkey - .parse() - .unwrap_or("SUPER+SHIFT+C".parse().unwrap()), - ]; + let hotkeys = vec![show_hide, cbhist]; manager .register_all(&hotkeys) diff --git a/src/utils.rs b/src/utils.rs index c5bf998..5366e06 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -52,14 +52,26 @@ pub fn open_url(url: &str) { /// Check if the provided string is a valid url pub fn is_valid_url(s: &str) -> bool { - s.ends_with(".com") - || s.ends_with(".net") - || s.ends_with(".org") - || s.ends_with(".edu") - || s.ends_with(".gov") - || s.ends_with(".io") - || s.ends_with(".co") - || s.ends_with(".me") - || s.ends_with(".app") - || s.ends_with(".dev") + match s + .chars() + .rev() + .fold(String::new(), |a, b| format!("{}{}", a, b)) + .split_once('.') + .unwrap_or(("", "")) + .0 + { + "" => false, + + // Common gTLDs (reversed) + "moc" | "gro" | "ten" | "ude" | "vog" | "lim" | "ofni" | "zib" | "eman" | "orp" | "ppa" + | "ved" | "oi" | "ia" | "oc" | "em" => true, + + // Common ccTLDs (reversed) + "su" | "ku" | "ed" | "rf" | "se" | "ti" | "ln" | "on" | "if" | "kd" | "lp" | "zc" + | "ta" | "hc" | "eb" | "ei" | "tp" | "rg" | "ur" | "au" | "rt" | "ni" | "pj" | "rk" + | "nc" | "wt" | "kh" | "gs" | "ym" | "di" | "ht" | "nv" | "rb" | "ra" | "xm" | "ac" + | "ua" | "zn" | "az" | "ge" | "li" | "as" | "ea" => true, + + _ => false, + } }