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