mod actions;
mod file_editor;
use super::confirm_action;
use crate::cli::Alter;
use crate::config::Config;
use crate::content::RecipeItem;
use crate::fs_wrappers;
use crate::mkdev_error::{Context, Error};
use crate::recipe::Recipe;
use std::collections::HashMap;
use colored::Colorize;
use inquire::error::InquireResult;
use inquire::validator::{Validation, ValueRequiredValidator};
use inquire::{Select, Text};
use rust_i18n::t;
use strum::IntoEnumIterator;
pub fn editor(args: Alter, user_recipes: HashMap<String, Recipe>) -> Result<(), Error> {
let _config = Config::get()?;
let original = Recipe::pick(&user_recipes, &args.recipe)?;
let original_path = original.dwelling()?;
let was_external = original.is_external()?;
let external_msg = format!(
"{} – {}?",
t!("recipes.external", name => &original.name),
t!("general.proceed")
);
let mut recipe = original.clone();
let mut recipe_altered = false;
loop {
let action = EditorAction::select_action()?;
if matches!(action, EditorAction::Quit) {
match EditorAction::quit_menu(recipe_altered)? {
ExitAction::Save => {
if (recipe.is_external()? || was_external)
&& !confirm_action(&external_msg, true)?
{
continue;
}
let new_path = recipe.dwelling()?;
recipe.save()?;
if new_path != original_path {
fs_wrappers::remove_file(original_path, Context::Imprint)?;
}
break;
}
ExitAction::Exit => break,
ExitAction::Cancel => continue,
}
}
recipe_altered |= action.dispatch(&mut recipe)?;
println!();
}
Ok(())
}
#[derive(Clone, Copy, Debug, strum::EnumIter)]
enum EditorAction {
Name,
Description,
AddContent,
EditContent,
RemoveContents,
Quit,
}
impl EditorAction {
fn select_action() -> InquireResult<EditorAction> {
let message = &t!("menus.editor.what_next");
let opts: Vec<_> = EditorAction::iter().collect();
let vim = Config::get().unwrap().vim;
Select::new(message, opts)
.with_vim_mode(vim)
.with_help_message(&t!("menus.select_help"))
.prompt()
}
fn quit_menu(altered: bool) -> Result<ExitAction, Error> {
if !altered {
Ok(ExitAction::Exit)
} else {
let opts = ExitAction::iter().collect();
let choice = Select::new(&t!("menus.editor.save"), opts).prompt_skippable()?;
Ok(match choice {
Some(act) => act,
None => ExitAction::Cancel,
})
}
}
pub fn dispatch(&self, recipe: &mut Recipe) -> InquireResult<bool> {
self.inspect(recipe);
self.edit(recipe)
}
fn edit(&self, recipe: &mut Recipe) -> InquireResult<bool> {
match self {
Self::Name => self.edit_name(recipe),
Self::Description => self.edit_description(recipe),
Self::AddContent => self.add_content(recipe),
Self::EditContent => self.edit_content(recipe),
Self::RemoveContents => self.remove_contents(recipe),
Self::Quit => unreachable!(),
}
}
fn inspect(&self, recipe: &Recipe) {
let val = match self {
Self::Name => format!("\"{}\"", recipe.name),
Self::Description => format!("\"{}\"", recipe.description),
Self::AddContent | Self::RemoveContents | Self::EditContent => recipe
.display_contents()
.lines()
.collect::<Vec<_>>()
.join("\n "),
_ => return,
};
println!(
"{} {}:\n {}",
">".green(),
t!("menus.editor.curr_val"),
val
)
}
}
impl std::fmt::Display for EditorAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::AddContent => t!("editor.actions.add_content"),
Self::Description => t!("editor.actions.description"),
Self::EditContent => t!("editor.actions.edit_content"),
Self::Name => t!("editor.actions.name"),
Self::Quit => t!("general.quit"),
Self::RemoveContents => t!("editor.actions.remove_contents"),
}
)
}
}
fn prompt_new_name(current: &str, existing: &[RecipeItem]) -> InquireResult<Option<String>> {
let msg = &t!("menus.editor.get_content_name");
Text::new(msg)
.with_validator(ValueRequiredValidator::new(t!(
"menus.editor.content_name_required"
)))
.with_validator(|i: &str| {
if i == current || !existing.iter().any(|item| item.name() == i) {
Ok(Validation::Valid)
} else {
Ok(Validation::Invalid(
t!("menus.editor.invalid_content_name").into(),
))
}
})
.with_initial_value(current)
.prompt_skippable()
}
#[derive(Clone, Copy, Debug, strum::EnumIter)]
enum ExitAction {
Save,
Exit,
Cancel,
}
impl std::fmt::Display for ExitAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Save => t!("editor.exit.save"),
Self::Exit => t!("editor.exit.exit"),
Self::Cancel => t!("editor.exit.cancel"),
}
)
}
}
#[derive(Clone, Copy, Debug, strum::EnumIter)]
enum ContentKind {
File,
Directory,
}
impl std::fmt::Display for ContentKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::File => t!("general.file"),
Self::Directory => t!("general.directory"),
}
)
}
}