mod action;
mod config;
mod prompt;
pub use action::*;
use std::{
env,
ffi::{OsStr, OsString},
sync::LazyLock,
};
use crate::{
error::{InquireError, InquireResult},
formatter::StringFormatter,
prompts::prompt::Prompt,
terminal::get_default_terminal,
ui::{Backend, EditorBackend, RenderConfig},
validator::StringValidator,
};
use self::prompt::EditorPrompt;
static DEFAULT_EDITOR: LazyLock<OsString> = LazyLock::new(get_default_editor_command);
#[derive(Clone)]
pub struct Editor<'a> {
pub message: &'a str,
pub editor_command: &'a OsStr,
pub editor_command_args: &'a [&'a OsStr],
pub file_extension: &'a str,
pub predefined_text: Option<&'a str>,
pub help_message: Option<&'a str>,
pub formatter: StringFormatter<'a>,
pub validators: Vec<Box<dyn StringValidator>>,
pub render_config: RenderConfig<'a>,
}
impl<'a> Editor<'a> {
pub const DEFAULT_FORMATTER: StringFormatter<'a> = &|_| String::from("<received>");
pub const DEFAULT_VALIDATORS: Vec<Box<dyn StringValidator>> = vec![];
pub const DEFAULT_HELP_MESSAGE: Option<&'a str> = None;
pub fn new(message: &'a str) -> Self {
Self {
message,
editor_command: &DEFAULT_EDITOR,
editor_command_args: &[],
file_extension: ".txt",
predefined_text: None,
help_message: Self::DEFAULT_HELP_MESSAGE,
validators: Self::DEFAULT_VALIDATORS,
formatter: Self::DEFAULT_FORMATTER,
render_config: RenderConfig::default(),
}
}
pub fn with_help_message(mut self, message: &'a str) -> Self {
self.help_message = Some(message);
self
}
pub fn with_predefined_text(mut self, text: &'a str) -> Self {
self.predefined_text = Some(text);
self
}
pub fn with_file_extension(mut self, file_extension: &'a str) -> Self {
self.file_extension = file_extension;
self
}
pub fn with_editor_command(mut self, editor_command: &'a OsStr) -> Self {
self.editor_command = editor_command;
self
}
pub fn with_args(mut self, args: &'a [&'a OsStr]) -> Self {
self.editor_command_args = args;
self
}
pub fn with_formatter(mut self, formatter: StringFormatter<'a>) -> Self {
self.formatter = formatter;
self
}
pub fn with_validator<V>(mut self, validator: V) -> Self
where
V: StringValidator + 'static,
{
self.validators.push(Box::new(validator));
self
}
pub fn with_validators(mut self, validators: &[Box<dyn StringValidator>]) -> Self {
for validator in validators {
#[allow(suspicious_double_ref_op)]
self.validators.push(validator.clone());
}
self
}
pub fn with_render_config(mut self, render_config: RenderConfig<'a>) -> Self {
self.render_config = render_config;
self
}
pub fn prompt_skippable(self) -> InquireResult<Option<String>> {
match self.prompt() {
Ok(answer) => Ok(Some(answer)),
Err(InquireError::OperationCanceled) => Ok(None),
Err(err) => Err(err),
}
}
pub fn prompt(self) -> InquireResult<String> {
let (input_reader, terminal) = get_default_terminal()?;
let mut backend = Backend::new(input_reader, terminal, self.render_config)?;
self.prompt_with_backend(&mut backend)
}
pub(crate) fn prompt_with_backend<B: EditorBackend>(
self,
backend: &mut B,
) -> InquireResult<String> {
EditorPrompt::new(self)?.prompt(backend)
}
}
fn get_default_editor_command() -> OsString {
let mut default_editor = if cfg!(windows) {
String::from("notepad")
} else {
String::from("nano")
};
if let Ok(editor) = env::var("EDITOR") {
if !editor.is_empty() {
default_editor = editor;
}
}
if let Ok(editor) = env::var("VISUAL") {
if !editor.is_empty() {
default_editor = editor;
}
}
default_editor.into()
}