diff --git a/espanso/src/cli/edit.rs b/espanso/src/cli/edit.rs
new file mode 100644
index 0000000..3050508
--- /dev/null
+++ b/espanso/src/cli/edit.rs
@@ -0,0 +1,230 @@
+/*
+ * 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::path::{Path, PathBuf};
+use std::process::Command;
+
+use super::{CliModule, CliModuleArgs};
+
+#[cfg(target_os = "linux")]
+fn default_editor() -> String {
+ "/bin/nano".to_owned()
+}
+#[cfg(target_os = "macos")]
+fn default_editor() -> String {
+ "/usr/bin/nano".to_owned()
+}
+#[cfg(target_os = "windows")]
+fn default_editor() -> String {
+ "C:\\Windows\\System32\\notepad.exe".to_owned()
+}
+
+pub fn new() -> CliModule {
+ CliModule {
+ requires_paths: true,
+ subcommand: "edit".to_string(),
+ entry: edit_main,
+ ..Default::default()
+ }
+}
+
+fn edit_main(args: CliModuleArgs) -> i32 {
+ let paths = args.paths.expect("missing paths argument");
+ let cli_args = args.cli_args.expect("missing cli_args");
+
+ if !paths.config.is_dir() {
+ panic!(
+ "config directory does not exist in path: {:?}",
+ paths.config
+ );
+ }
+
+ // Determine which is the file to edit
+ let target_file = cli_args.value_of("target_file");
+ let target_path = determine_target_path(&paths.config, target_file);
+
+ println!(
+ "Editing file: {}",
+ &target_path.to_string_lossy().to_string()
+ );
+
+ open_editor(&target_path);
+
+ // The previous version automatically reloaded the config after saving.
+ // Given that the new version automatically reloads config changes, this could be avoided.
+ // Nevertheless, this assumption might be wrong, so I'm keeping the necessary code.
+ // TODO: evaluate if reload is needed after v2 becomes stable
+
+ // // Based on the fact that the file already exists or not, we should detect in different
+ // // ways if a reload is needed
+ // let should_reload = if target_path.exists() {
+ // // Get the last modified date, so that we can detect if the user actually edits the file
+ // // before reloading
+ // let metadata = std::fs::metadata(&target_path).expect("cannot gather file metadata");
+ // let last_modified = metadata
+ // .modified()
+ // .expect("cannot read file last modified date");
+
+ // let result = open_editor(&target_path);
+ // if result {
+ // let new_metadata = std::fs::metadata(&target_path).expect("cannot gather file metadata");
+ // let new_last_modified = new_metadata
+ // .modified()
+ // .expect("cannot read file last modified date");
+
+ // if last_modified != new_last_modified {
+ // println!("File has been modified, reloading configuration");
+ // true
+ // } else {
+ // println!("File has not been modified, avoiding reload");
+ // false
+ // }
+ // } else {
+ // false
+ // }
+ // } else {
+ // let result = open_editor(&target_path);
+ // if result {
+ // // If the file has been created, we should reload the espanso config
+ // if target_path.exists() {
+ // println!("A new file has been created, reloading configuration");
+ // true
+ // } else {
+ // println!("No file has been created, avoiding reload");
+ // false
+ // }
+ // } else {
+ // false
+ // }
+ // };
+
+ // let no_restart: bool = if cli_args.is_present("norestart") {
+ // println!("Skipping automatic restart");
+ // true
+ // } else {
+ // false
+ // };
+
+ // if should_reload && !no_restart {
+ // // Check if the new configuration is valid
+
+ // if let Err(err) = crate::config::load_config(&paths.config, &paths.packages) {
+ // eprintln!("Unable to reload espanso due to a configuration error:");
+ // eprintln!("{:?}", err);
+ // return 1;
+ // };
+
+ // restart_espanso(&paths_overrides).expect("unable to restart espanso");
+ // }
+
+ 0
+}
+
+fn determine_target_path(config_path: &Path, target_file: Option<&str>) -> PathBuf {
+ if let Some(target_file) = target_file {
+ match target_file {
+ "default" => {
+ if espanso_config::is_legacy_config(config_path) {
+ config_path.join("default.yml")
+ } else {
+ config_path.join("config").join("default.yml")
+ }
+ }
+ "base" => {
+ if espanso_config::is_legacy_config(config_path) {
+ panic!("'base' alias cannot be used in compatibility mode, please migrate your configuration by running 'espanso migrate'")
+ } else {
+ config_path.join("match").join("base.yml")
+ }
+ }
+ custom => {
+ if !custom.ends_with(".yml") && !custom.ends_with(".yaml") {
+ if espanso_config::is_legacy_config(config_path) {
+ config_path.join("user").join(format!("{}.yml", custom))
+ } else {
+ config_path.join("match").join(format!("{}.yml", custom))
+ }
+ } else {
+ config_path.join(custom)
+ }
+ }
+ }
+ } else if espanso_config::is_legacy_config(config_path) {
+ config_path.join("default.yml")
+ } else {
+ config_path.join("match").join("base.yml")
+ }
+}
+
+pub fn open_editor(file_path: &Path) -> bool {
+ // Check if another editor is defined in the environment variables
+ let editor_var = std::env::var_os("EDITOR");
+ let visual_var = std::env::var_os("VISUAL");
+
+ // Prioritize the editors specified by the environment variable, use the default one
+ let editor: String = if let Some(editor_var) = editor_var {
+ editor_var.to_string_lossy().to_string()
+ } else if let Some(visual_var) = visual_var {
+ visual_var.to_string_lossy().to_string()
+ } else {
+ default_editor()
+ };
+
+ // Start the editor and wait for its termination
+ let status = if cfg!(target_os = "windows") {
+ Command::new(&editor).arg(file_path).spawn()
+ } else {
+ // On Unix, spawn the editor using the shell so that it can
+ // accept parameters. See issue #245
+ Command::new("/bin/bash")
+ .arg("-c")
+ .arg(format!("{} '{}'", editor, file_path.to_string_lossy()))
+ .spawn()
+ };
+
+ if let Ok(mut child) = status {
+ // Wait for the user to edit the configuration
+ let result = child.wait();
+
+ if let Ok(exit_status) = result {
+ exit_status.success()
+ } else {
+ false
+ }
+ } else {
+ println!("Error: could not start editor at: {}", &editor);
+ false
+ }
+}
+
+// fn restart_espanso(paths_overrides: &PathsOverrides) -> Result<()> {
+// let espanso_exe_path = std::env::current_exe()?;
+
+// let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string());
+// command.args(&["restart"]);
+// command.with_paths_overrides(paths_overrides);
+
+// let output = command.output()?;
+
+// if output.status.success() {
+// Ok(())
+// } else {
+// bail!("restart command returned a non-zero exit code");
+// }
+// }
diff --git a/espanso/src/cli/mod.rs b/espanso/src/cli/mod.rs
index 64ccff6..2cdba94 100644
--- a/espanso/src/cli/mod.rs
+++ b/espanso/src/cli/mod.rs
@@ -24,6 +24,7 @@ use espanso_config::{config::ConfigStore, error::NonFatalErrorSet, matches::stor
use espanso_path::Paths;
pub mod daemon;
+pub mod edit;
pub mod env_path;
pub mod launcher;
pub mod log;
diff --git a/espanso/src/main.rs b/espanso/src/main.rs
index c005612..4ea4050 100644
--- a/espanso/src/main.rs
+++ b/espanso/src/main.rs
@@ -60,6 +60,7 @@ const LOG_FILE_NAME: &str = "espanso.log";
lazy_static! {
static ref CLI_HANDLERS: Vec = vec![
cli::path::new(),
+ cli::edit::new(),
cli::launcher::new(),
cli::log::new(),
cli::worker::new(),
@@ -215,17 +216,20 @@ fn main() {
// .subcommand(SubCommand::with_name("toggle")
// .about("Toggle the status of the espanso replacement engine."))
// )
- // .subcommand(SubCommand::with_name("edit")
- // .about("Open the default text editor to edit config files and reload them automatically when exiting")
- // .arg(Arg::with_name("config")
- // .help("Defaults to \"default\". The configuration file name to edit (without the .yml extension)."))
- // .arg(Arg::with_name("norestart")
- // .short("n")
- // .long("norestart")
- // .required(false)
- // .takes_value(false)
- // .help("Avoid restarting espanso after editing the file"))
- // )
+ .subcommand(SubCommand::with_name("edit")
+ .about("Shortcut to open the default text editor to edit config files")
+ .arg(Arg::with_name("target_file")
+ .help(r#"Defaults to "match/base.yml", it contains the relative path of the file you want to edit,
+such as 'config/default.yml' or 'match/base.yml'.
+For convenience, you can also specify the name directly and Espanso will figure out the path.
+For example, specifying 'email' is equivalent to 'match/email.yml'."#))
+ // .arg(Arg::with_name("norestart")
+ // .short("n")
+ // .long("norestart")
+ // .required(false)
+ // .takes_value(false)
+ // .help("Avoid restarting espanso after editing the file"))
+ )
// .subcommand(SubCommand::with_name("detect")
// .about("Tool to detect current window properties, to simplify filters creation."))
.subcommand(