diff --git a/src/config/mod.rs b/src/config/mod.rs index 8bddc07..b1f8ea0 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -38,7 +38,7 @@ pub(crate) mod runtime; const DEFAULT_CONFIG_FILE_CONTENT : &str = include_str!("../res/config.yml"); pub const DEFAULT_CONFIG_FILE_NAME : &str = "default.yml"; -const USER_CONFIGS_FOLDER_NAME: &str = "user"; +pub const USER_CONFIGS_FOLDER_NAME: &str = "user"; // Default values for primitives fn default_name() -> String{ "default".to_owned() } @@ -68,9 +68,16 @@ fn default_exclude_default_entries() -> bool {false} fn default_matches() -> Vec { Vec::new() } fn default_global_vars() -> Vec { Vec::new() } +#[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() } // TODO: change +#[cfg(target_os = "windows")] +fn default_editor() -> String{ "C:\\Windows\\System32\\notepad.exe".to_owned() } + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Configs { -#[serde(default = "default_name")] + #[serde(default = "default_name")] pub name: String, #[serde(default = "default_parent")] @@ -148,6 +155,9 @@ pub struct Configs { #[serde(default = "default_exclude_default_entries")] pub exclude_default_entries: bool, + #[serde(default = "default_editor")] + pub editor: String, + #[serde(default = "default_matches")] pub matches: Vec, diff --git a/src/edit.rs b/src/edit.rs index fcc86a7..499ceda 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -18,44 +18,40 @@ */ use crate::config::ConfigSet; +use std::path::Path; -#[cfg(target_os = "linux")] -pub fn open_editor(config: &ConfigSet) -> bool { - // TODO -} - -#[cfg(target_os = "macos")] -pub fn open_editor(config: &ConfigSet) -> bool { - // TODO -} - -#[cfg(target_os = "windows")] -pub fn open_editor(config: &ConfigSet) -> bool { +pub fn open_editor(config: &ConfigSet, file_path: &Path) -> bool { use std::process::Command; - // Get the configuration file path - let file_path = crate::context::get_config_dir().join(crate::config::DEFAULT_CONFIG_FILE_NAME); + // 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, otherwise use the config + 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{ + config.default.editor.clone() + }; // Start the editor and wait for its termination - let status = Command::new("cmd") - .arg("/C") - .arg("start") - .arg("/wait") - .arg("C:\\Windows\\System32\\notepad.exe") + let status = Command::new(editor) .arg(file_path) .spawn(); if let Ok(mut child) = status { // Wait for the user to edit the configuration - child.wait(); + let result = child.wait(); - // TODO: instead of waiting, a file watcher should be started to detect file changes and - // after each of them a reload should be issued - - println!("Ok"); - true + if let Ok(exit_status) = result { + exit_status.success() + }else{ + false + } }else{ - println!("Error: could not start editor."); + println!("Error: could not start editor at: {}", config.default.editor); false } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index ac30214..7422b6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -98,7 +98,9 @@ fn main() { .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")) + .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)."))) .subcommand(SubCommand::with_name("dump") .about("Prints all current configuration options.")) .subcommand(SubCommand::with_name("detect") @@ -166,8 +168,8 @@ fn main() { return; } - if matches.subcommand_matches("edit").is_some() { - edit_main(config_set); + if let Some(matches) = matches.subcommand_matches("edit") { + edit_main(config_set, matches); return; } @@ -881,8 +883,66 @@ fn path_main(_config_set: ConfigSet, matches: &ArgMatches) { } } -fn edit_main(config_set: ConfigSet) { - crate::edit::open_editor(&config_set); +fn edit_main(config_set: ConfigSet, matches: &ArgMatches) { + // Determine which is the file to edit + let config = matches.value_of("config").unwrap_or("default"); + + let config_dir = crate::context::get_config_dir(); + + let config_path = match config { + "default" => { + config_dir.join(crate::config::DEFAULT_CONFIG_FILE_NAME) + }, + name => { // Otherwise, search in the user/ config folder + config_dir.join(crate::config::USER_CONFIGS_FOLDER_NAME) + .join(name.to_owned() + ".yml") + } + }; + + println!("Editing file: {:?}", &config_path); + + // 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 config_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(&config_path).expect("cannot gather file metadata"); + let last_modified = metadata.modified().expect("cannot read file last modified date"); + + let result = crate::edit::open_editor(&config_set, &config_path); + if result { + let new_metadata = std::fs::metadata(&config_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 = crate::edit::open_editor(&config_set, &config_path); + if result { + // If the file has been created, we should reload the espanso config + if config_path.exists() { + println!("A new file has been created, reloading configuration"); + true + }else{ + println!("No file has been created, avoiding reload"); + false + } + }else{ + false + } + }; + + if should_reload { + restart_main(config_set) + } } fn acquire_lock() -> Option {