feat(core): port 'edit' subcommand implementation from previous version

This commit is contained in:
Federico Terzi 2021-10-09 15:02:44 +02:00
parent 8f291f4717
commit 5a75a04d5a
3 changed files with 246 additions and 11 deletions

230
espanso/src/cli/edit.rs Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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");
// }
// }

View File

@ -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;

View File

@ -60,6 +60,7 @@ const LOG_FILE_NAME: &str = "espanso.log";
lazy_static! {
static ref CLI_HANDLERS: Vec<CliModule> = 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)."))
.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(