add math as own mode

This commit is contained in:
Alexander Mohr 2025-04-21 16:41:02 +02:00
parent d0b526fb9d
commit d08893d545
3 changed files with 93 additions and 121 deletions

View file

@ -67,6 +67,9 @@ pub enum Mode {
/// use worf as file browser
File,
/// Use is as calculator
Math,
}
#[derive(Debug, Error)]
@ -84,6 +87,7 @@ impl FromStr for Mode {
"drun" => Ok(Mode::Drun),
"dmenu" => Ok(Mode::Dmenu),
"file" => Ok(Mode::File),
"math" => Ok(Mode::Math),
"auto" => Ok(Mode::Auto),
_ => Err(ArgsError::InvalidParameter(
format!("{s} is not a valid argument show this, see help for details").to_owned(),

View file

@ -24,7 +24,7 @@ struct DRunCache {
}
#[derive(Clone)]
struct DRunProvider<T: std::clone::Clone> {
struct DRunProvider<T: Clone> {
items: Vec<MenuItem<T>>,
cache_path: Option<PathBuf>,
cache: HashMap<String, i64>,
@ -145,7 +145,7 @@ impl<T: Clone> ItemProvider<T> for DRunProvider<T> {
}
#[derive(Clone)]
struct FileItemProvider<T: std::clone::Clone> {
struct FileItemProvider<T: Clone> {
last_result: Option<Vec<MenuItem<T>>>,
menu_item_data: T,
}
@ -276,22 +276,78 @@ impl<T: Clone> ItemProvider<T> for FileItemProvider<T> {
}
}
#[derive(Clone)]
struct MathProvider<T: Clone> {
menu_item_data: T,
}
impl<T: std::clone::Clone> MathProvider<T> {
fn new(menu_item_data: T) -> Self {
Self {
menu_item_data,
}
}
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";
// Regex for strings that start with a number (including decimals)
let starts_with_number = r"^\s*[+-]?(\d+(\.\d*)?|\.\d+)";
let math_regex = Regex::new(math_functions).unwrap();
let number_regex = Regex::new(starts_with_number).unwrap();
math_regex.is_match(input) || number_regex.is_match(input)
}
}
impl<T: Clone> ItemProvider<T> for MathProvider<T> {
fn get_elements(&mut self, search: Option<&str>) -> Vec<MenuItem<T>> {
if let Some(search_text) = search {
let result = match meval::eval_str(search_text) {
Ok(result) => result.to_string(),
Err(e) => format!("failed to calculate {e:?}"),
};
let item = MenuItem {
label: result,
icon_path: None,
action: search.map(|s| s.to_string()),
sub_elements: vec![],
working_dir: None,
initial_sort_score: 0,
search_sort_score: 0.0,
data: Some(self.menu_item_data.clone()),
};
vec![item]
} else {
vec![]
}
}
fn get_sub_elements(&mut self, item: &MenuItem<T>) -> Option<Vec<MenuItem<T>>> {
None
}
}
#[derive(Debug, Clone, PartialEq)]
enum AutoRunType {
Math,
DRun,
File,
Ssh,
WebSearch,
Emoji,
Run,
// Ssh,
// WebSearch,
// Emoji,
// Run,
}
#[derive(Clone)]
struct AutoItemProvider {
drun_provider: DRunProvider<AutoRunType>,
file_provider: FileItemProvider<AutoRunType>,
last_result: Option<Vec<MenuItem<AutoRunType>>>,
math_provider: MathProvider<AutoRunType>,
}
impl AutoItemProvider {
@ -299,55 +355,26 @@ impl AutoItemProvider {
AutoItemProvider {
drun_provider: DRunProvider::new(AutoRunType::DRun),
file_provider: FileItemProvider::new(AutoRunType::File),
last_result: None,
math_provider: MathProvider::new(AutoRunType::Math),
}
}
}
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";
// Regex for strings that start with a number (including decimals)
let starts_with_number = r"^\s*[+-]?(\d+(\.\d*)?|\.\d+)";
let math_regex = Regex::new(math_functions).unwrap();
let number_regex = Regex::new(starts_with_number).unwrap();
math_regex.is_match(input) || number_regex.is_match(input)
}
impl ItemProvider<AutoRunType> for AutoItemProvider {
fn get_elements(&mut self, search_opt: Option<&str>) -> Vec<MenuItem<AutoRunType>> {
if let Some(search) = search_opt {
let trimmed_search = search.trim();
if trimmed_search.is_empty() {
self.drun_provider.get_elements(search_opt)
} else if contains_math_functions_or_starts_with_number(trimmed_search) {
let result = match meval::eval_str(trimmed_search) {
Ok(result) => result.to_string(),
Err(e) => format!("failed to calculate {e:?}"),
};
let item = MenuItem {
label: result,
icon_path: None,
action: Some(trimmed_search.to_owned()),
sub_elements: vec![],
working_dir: None,
initial_sort_score: 0,
search_sort_score: 0.0,
data: Some(AutoRunType::Math),
};
return vec![item];
} else if MathProvider::<AutoRunType>::contains_math_functions_or_starts_with_number(trimmed_search) {
self.math_provider.get_elements(search_opt)
} else if trimmed_search.starts_with("$")
|| trimmed_search.starts_with("/")
|| trimmed_search.starts_with("~")
{
self.file_provider.get_elements(search_opt)
} else {
return self.drun_provider.get_elements(search_opt);
self.drun_provider.get_elements(search_opt)
}
} else {
self.drun_provider.get_elements(search_opt)
@ -448,6 +475,25 @@ pub fn file(config: &mut Config) -> Result<(), String> {
Ok(())
}
pub fn math(config: &mut Config) -> Result<(), String> {
let provider = MathProvider::new("".to_owned());
if config.prompt.is_none() {
config.prompt = Some("math".to_owned());
}
// todo ues a arc instead of cloning the config
let selection_result = gui::show(config.clone(), provider);
match selection_result {
Ok(_) => {
}
Err(_) => {
log::error!("No item selected");
}
}
Ok(())
}
fn update_drun_cache_and_run<T: Clone>(
cache_path: Option<PathBuf>,
cache: &mut HashMap<String, i64>,

View file

@ -29,6 +29,9 @@ fn main() -> anyhow::Result<()> {
Mode::File => {
mode::file(&mut config).map_err(|e| anyhow!(e))?;
}
Mode::Math => {
mode::math(&mut config).map_err(|e| anyhow!(e))?;
}
Mode::Auto => {
mode::auto(&mut config)?;
}
@ -39,84 +42,3 @@ fn main() -> anyhow::Result<()> {
Err(anyhow!("No mode provided"))
}
}
//
// fn main() -> anyhow::Result<()> {
// env_logger::Builder::new()
// // todo change to info as default
// .parse_filters(&std::env::var("RUST_LOG").unwrap_or_else(|_| "debug".to_owned()))
// .init();
// let args = Args::parse();
//
// let home_dir = std::env::var("HOME")?;
// let config_path = args.config.as_ref().map(|c| PathBuf::from(c)).unwrap_or_else(||{
// std::env::var("XDG_CONF_HOME")
// .map_or(
// PathBuf::from(home_dir.clone()).join(".config"),
// |xdg_conf_home| PathBuf::from(&xdg_conf_home),
// )
// .join("wofi")// todo change to ravi
// .join("config")
// });
//
// let colors_dir = std::env::var("XDG_CACHE_HOME")
// .map_or(
// PathBuf::from(home_dir.clone()).join(".cache"),
// |xdg_conf_home| PathBuf::from(&xdg_conf_home),
// )
// .join("wal")
// .join("colors");
//
// let toml_content = fs::read_to_string(config_path)?;
// let config: Config = toml::from_str(&toml_content).unwrap_or_default();
//
//
//
// gtk4::init()?;
//
// let application = Application::builder()
// .application_id("com.example.FirstGtkApp")
// .build();
//
// application.connect_activate(|app| {
// let window = ApplicationWindow::builder()
// .application(app)
// .title("First GTK Program")
// .name("window")
// .default_width(config.x.clone().unwrap())
// .default_height(config.y.clone().unwrap())
// .resizable(false)
// .decorated(false)
// .build();
//
//
//
// // Create a dialog window
// let dialog = Dialog::new();
// dialog.set_title(Some("Custom Dialog"));
// dialog.set_default_size(300, 150);
//
// // Create a vertical box container for the dialog content
// let mut vbox =gtk4:: Box::new(Orientation::Horizontal, 10);
//
// // Add a label to the dialog
// let label = Label::new(Some("This is a custom dialog!"));
// vbox.append(&label);
//
// // Set the dialog content
// dialog.set_child(Some(&vbox));
//
// // Show the dialog
// dialog.present();
// });
//
// let empty_array: [&str; 0] = [];;
//
//
// application.run_with_args(&empty_array);
//
// debug!("merged config result {:#?}", config);
//
//
// Ok(())
// }