diff --git a/espanso/src/cli/worker/engine/mod.rs b/espanso/src/cli/worker/engine/mod.rs index edf0cef..aac0cc0 100644 --- a/espanso/src/cli/worker/engine/mod.rs +++ b/espanso/src/cli/worker/engine/mod.rs @@ -54,7 +54,8 @@ pub fn initialize_and_spawn( super::engine::matcher::convert::MatchConverter::new(&*config_store, &*match_store); let match_cache = super::engine::match_cache::MatchCache::load(&*config_store, &*match_store); - let modulo_manager = ui::modulo::ModuloManager::new(); + let modulo_manager = crate::gui::modulo::manager::ModuloManager::new(); + let modulo_form_ui = crate::gui::modulo::form::ModuloFormUI::new(&modulo_manager, icon_paths.form_icon); let (detect_source, modifier_state_store, sequencer) = super::engine::source::init_and_spawn().expect("failed to initialize detector module"); @@ -94,8 +95,7 @@ pub fn initialize_and_spawn( &paths.packages, ); let shell_extension = espanso_render::extension::shell::ShellExtension::new(&paths.config); - let form_adapter = - ui::modulo::form::ModuloFormProviderAdapter::new(&modulo_manager, icon_paths.form_icon); + let form_adapter = ui::form::FormProviderAdapter::new(&modulo_form_ui); let form_extension = espanso_render::extension::form::FormExtension::new(&form_adapter); let renderer = espanso_render::create(vec![ &clipboard_extension, diff --git a/espanso/src/cli/worker/engine/ui/form.rs b/espanso/src/cli/worker/engine/ui/form.rs new file mode 100644 index 0000000..c161919 --- /dev/null +++ b/espanso/src/cli/worker/engine/ui/form.rs @@ -0,0 +1,117 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::collections::HashMap; + +use espanso_render::{ + extension::form::{FormProvider, FormProviderResult}, + Params, Value, +}; +use log::error; + +use crate::gui::{FormField, FormUI}; + +pub struct FormProviderAdapter<'a> { + form_ui: &'a dyn FormUI, +} + +impl<'a> FormProviderAdapter<'a> { + pub fn new(form_ui: &'a dyn FormUI) -> Self { + Self { form_ui } + } +} + +impl<'a> FormProvider for FormProviderAdapter<'a> { + fn show(&self, layout: &str, fields: &Params, _: &Params) -> FormProviderResult { + let fields = convert_fields(fields); + match self.form_ui.show(layout, &fields) { + Ok(Some(results)) => FormProviderResult::Success(results), + Ok(None) => FormProviderResult::Aborted, + Err(err) => FormProviderResult::Error(err), + } + } +} + +// TODO: test +fn convert_fields(fields: &Params) -> HashMap { + let mut out = HashMap::new(); + for (name, field) in fields { + let mut form_field = None; + + if let Value::Object(params) = field { + if let Some(Value::String(field_type)) = params.get("type") { + form_field = match field_type.as_str() { + "text" => Some(FormField::Text { + default: params + .get("default") + .and_then(|val| val.as_string()) + .cloned(), + multiline: params + .get("multiline") + .and_then(|val| val.as_bool()) + .cloned() + .unwrap_or(false), + }), + "choice" => Some(FormField::Choice { + default: params + .get("default") + .and_then(|val| val.as_string()) + .cloned(), + values: params + .get("values") + .and_then(|val| val.as_array()) + .map(|arr| { + arr + .into_iter() + .flat_map(|choice| choice.as_string()) + .cloned() + .collect() + }) + .unwrap_or_default(), + }), + "list" => Some(FormField::List { + default: params + .get("default") + .and_then(|val| val.as_string()) + .cloned(), + values: params + .get("values") + .and_then(|val| val.as_array()) + .map(|arr| { + arr + .into_iter() + .flat_map(|choice| choice.as_string()) + .cloned() + .collect() + }) + .unwrap_or_default(), + }), + _ => None, + } + } + } + + if let Some(form_field) = form_field { + out.insert(name.clone(), form_field); + } else { + error!("malformed form field format for '{}'", name); + } + } + out +} diff --git a/espanso/src/cli/worker/engine/ui/mod.rs b/espanso/src/cli/worker/engine/ui/mod.rs index 4fa4de6..a55dc66 100644 --- a/espanso/src/cli/worker/engine/ui/mod.rs +++ b/espanso/src/cli/worker/engine/ui/mod.rs @@ -17,5 +17,5 @@ * along with espanso. If not, see . */ -pub mod modulo; +pub mod form; pub mod selector; \ No newline at end of file diff --git a/espanso/src/cli/worker/engine/ui/modulo/form.rs b/espanso/src/cli/worker/engine/ui/modulo/form.rs deleted file mode 100644 index cd60e13..0000000 --- a/espanso/src/cli/worker/engine/ui/modulo/form.rs +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This file is part of espanso. - * - * Copyright (C) 2019-2021 Federico Terzi - * - * espanso is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * espanso is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with espanso. If not, see . - */ - -use std::{collections::HashMap, path::PathBuf}; - -use super::ModuloManager; -use anyhow::Result; -use espanso_render::extension::form::{FormProvider, FormProviderResult}; -use log::{error}; -use serde::Serialize; -use serde_json::{Map, Value}; - -pub struct ModuloFormProviderAdapter<'a> { - manager: &'a ModuloManager, - icon_path: Option, -} - -impl<'a> ModuloFormProviderAdapter<'a> { - pub fn new(manager: &'a ModuloManager, icon_path: Option) -> Self { - Self { - manager, - icon_path: icon_path.map(|path| path.to_string_lossy().to_string()), - } - } -} - -impl<'a> FormProvider for ModuloFormProviderAdapter<'a> { - fn show( - &self, - layout: &str, - fields: &espanso_render::Params, - _: &espanso_render::Params, - ) -> FormProviderResult { - let modulo_form_config = ModuloFormConfig { - icon: self.icon_path.as_deref(), - title: "espanso", - layout, - fields: convert_params_into_object(fields), - }; - - match serde_json::to_string(&modulo_form_config) { - Ok(json_config) => { - match self - .manager - .invoke(&["form", "-j", "-i", "-"], &json_config) - { - Ok(output) => { - let json: Result, _> = serde_json::from_str(&output); - match json { - Ok(json) => { - if json.is_empty() { - return FormProviderResult::Aborted; - } else { - return FormProviderResult::Success(json); - } - } - Err(error) => { - return FormProviderResult::Error(error.into()); - } - } - } - Err(err) => { - return FormProviderResult::Error(err.into()); - } - } - } - Err(err) => { - return FormProviderResult::Error(err.into()); - } - } - } -} - -#[derive(Debug, Serialize)] -struct ModuloFormConfig<'a> { - icon: Option<&'a str>, - title: &'a str, - layout: &'a str, - fields: Map, -} - -// TODO: test -fn convert_params_into_object(params: &espanso_render::Params) -> Map { - let mut obj = Map::new(); - for (field, value) in params { - obj.insert(field.clone(), convert_value(value)); - } - obj -} - -// TODO: test -fn convert_value(value: &espanso_render::Value) -> Value { - match value { - espanso_render::Value::Null => Value::Null, - espanso_render::Value::Bool(value) => Value::Bool(*value), - espanso_render::Value::Number(num) => match num { - espanso_render::Number::Integer(val) => Value::Number((*val).into()), - espanso_render::Number::Float(val) => { - Value::Number(serde_json::Number::from_f64(*val).unwrap_or_else(|| { - error!("unable to convert float value to json"); - 0.into() - })) - } - }, - espanso_render::Value::String(value) => Value::String(value.clone()), - espanso_render::Value::Array(arr) => Value::Array(arr.into_iter().map(convert_value).collect()), - espanso_render::Value::Object(obj) => Value::Object(convert_params_into_object(obj)), - } -} diff --git a/espanso/src/gui/mod.rs b/espanso/src/gui/mod.rs new file mode 100644 index 0000000..39a93b5 --- /dev/null +++ b/espanso/src/gui/mod.rs @@ -0,0 +1,55 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::collections::HashMap; + +use anyhow::Result; + +pub mod modulo; + +pub trait SearchUI { + fn show(&self, items: &SearchItem) -> Result>; +} + +#[derive(Debug)] +pub struct SearchItem { + pub id: String, + pub label: String, + pub tag: Option, +} + +pub trait FormUI { + fn show(&self, layout: &str, fields: &HashMap) -> Result>>; +} + +#[derive(Debug)] +pub enum FormField { + Text { + default: Option, + multiline: bool, + }, + Choice { + default: Option, + values: Vec, + }, + List { + default: Option, + values: Vec, + } +} \ No newline at end of file diff --git a/espanso/src/gui/modulo/form.rs b/espanso/src/gui/modulo/form.rs new file mode 100644 index 0000000..91be259 --- /dev/null +++ b/espanso/src/gui/modulo/form.rs @@ -0,0 +1,107 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use serde::Serialize; +use serde_json::{json, Map, Value}; +use std::{collections::HashMap, path::PathBuf}; + +use crate::gui::{FormField, FormUI}; + +use super::manager::ModuloManager; + +pub struct ModuloFormUI<'a> { + manager: &'a ModuloManager, + icon_path: Option, +} + +impl<'a> ModuloFormUI<'a> { + pub fn new(manager: &'a ModuloManager, icon_path: Option) -> Self { + Self { + manager, + icon_path: icon_path.map(|path| path.to_string_lossy().to_string()), + } + } +} + +impl<'a> FormUI for ModuloFormUI<'a> { + fn show( + &self, + layout: &str, + fields: &HashMap, + ) -> anyhow::Result>> { + let modulo_form_config = ModuloFormConfig { + icon: self.icon_path.as_deref(), + title: "espanso", + layout, + fields: convert_fields_into_object(fields), + }; + + let json_config = serde_json::to_string(&modulo_form_config)?; + let output = self + .manager + .invoke(&["form", "-j", "-i", "-"], &json_config)?; + let json: Result, _> = serde_json::from_str(&output); + match json { + Ok(json) => { + if json.is_empty() { + return Ok(None); + } else { + return Ok(Some(json)); + } + } + Err(error) => { + return Err(error.into()); + } + } + } +} + +#[derive(Debug, Serialize)] +struct ModuloFormConfig<'a> { + icon: Option<&'a str>, + title: &'a str, + layout: &'a str, + fields: Map, +} + +// TODO: test +fn convert_fields_into_object(fields: &HashMap) -> Map { + let mut obj = Map::new(); + for (name, field) in fields { + let value = match field { + FormField::Text { default, multiline } => json!({ + "type": "text", + "default": default, + "multiline": multiline, + }), + FormField::Choice { default, values } => json!({ + "type": "choice", + "default": default, + "values": values, + }), + FormField::List { default, values } => json!({ + "type": "list", + "default": default, + "values": values, + }), + }; + obj.insert(name.clone(), value); + } + obj +} diff --git a/espanso/src/cli/worker/engine/ui/modulo/mod.rs b/espanso/src/gui/modulo/manager.rs similarity index 99% rename from espanso/src/cli/worker/engine/ui/modulo/mod.rs rename to espanso/src/gui/modulo/manager.rs index d809fa7..c849824 100644 --- a/espanso/src/cli/worker/engine/ui/modulo/mod.rs +++ b/espanso/src/gui/modulo/manager.rs @@ -23,8 +23,6 @@ use std::io::Write; use std::process::Command; use thiserror::Error; -pub mod form; - pub struct ModuloManager { modulo_path: Option, } diff --git a/espanso/src/gui/modulo/mod.rs b/espanso/src/gui/modulo/mod.rs new file mode 100644 index 0000000..8c170f3 --- /dev/null +++ b/espanso/src/gui/modulo/mod.rs @@ -0,0 +1,23 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +pub mod form; +pub mod manager; + +// TODO: implement FormUI and SearchUI traits using ModuloManager \ No newline at end of file diff --git a/espanso/src/main.rs b/espanso/src/main.rs index 366fc62..d0dfa5d 100644 --- a/espanso/src/main.rs +++ b/espanso/src/main.rs @@ -32,6 +32,7 @@ use simplelog::{ mod cli; mod engine; +mod gui; mod logging; mod util;