sued 0.24.2

shut up editor - a minimalist line-based text editor written in Rust
Documentation
//! Contains the `Command` struct, which is used to define and execute modular commands in sued.
//!
//! This file is part of sued.
//!
//! Visit `main.rs` and `lib.rs` for context and usage.
//!
//! sued is free software licensed under the Apache License, Version 2.0.

use crate::commands;
use crate::EditorState;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

/// Represents a sued editing command.
///
/// Commands can be defined anywhere in the program, but must be added to
/// a [CommandRegistry] to make them accessible.
#[derive(Debug, Default, Clone)]
pub struct Command {
    /// The name of the command to execute using [run_sued_command](crate::run_sued_command)
    /// or in the REPL.
    /// This should ideally be lowercase with no separations (no camelCase or
    /// snake_case).
    pub name: &'static str,
    /// The arguments that the command takes.
    /// This is PURELY VISUAL and does not control the arguments the command
    /// uses. You are expected to manually handle command-line arguments
    /// in [the action definition](CommandAction::action).
    pub arguments: Vec<&'static str>,
    /// A description of what the command does.
    /// This will be printed in the `~help` command.
    pub description: &'static str,
    /// A more expansive description of what the command does.
    /// This will be printed in the `~help <command>` and `~doc <command>`
    /// commands.
    pub documentation: &'static str,
    /// The scope of the command.
    /// For more information about command scope, see the [CommandScope] enum.
    pub scope: CommandScope,
    /// The action that running the command uses.
    /// For more information about command actions, see the [CommandAction]
    /// struct.
    pub action: CommandAction,
}

impl Command {
    /// Creates a new [Command] with default values.
    ///
    /// This should not be called and is only here for convention. Create a new
    /// Command and instantiate each field manually instead.
    pub fn new() -> Command {
        Command::default()
    }

    pub fn matches(&self, command: &str) -> bool {
        self.name == command
    }

    pub fn to_string(&self) -> String {
        format!("{}", self)
    }
}

impl std::fmt::Display for Command {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
        let scope_name: &str = match self.scope {
            CommandScope::Global => "global",
            CommandScope::REPLOnly => "repl",
            CommandScope::FileOnly => "file",
            CommandScope::Private => "private",
        };
        write!(
            fmt,
            "{}: {}\nscope: {}\nfunction: {:#?}",
            self.name, self.description, scope_name, self.action
        )
    }
}

impl Ord for Command {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.name.cmp(other.name)
    }
}

impl PartialOrd for Command {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Eq for Command {}

impl PartialEq for Command {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
    }
}

/// Represents the scope of a `Command`.
///
/// "Scope" for a sued command refers to where the command is allowed to be
/// called from.
///
/// Values:
/// - [CommandScope::Global]: Available everywhere in sued.
/// - [CommandScope::REPLOnly]: Available only in sued's text editing components.
/// - [CommandScope::FileOnly]: Available only in sued as a library.
/// - [CommandScope::Private]: Unavailable no matter what.
#[derive(Debug, Default, Clone)]
pub enum CommandScope {
    /// Commands of this scope are available everywhere in sued.
    ///
    /// Useful for when you don't care about the scope of the command you're
    /// running, and it's nice to have idiomatically as well.
    #[default]
    Global,
    /// Commands of this scope are available only in sued's text editing components.
    ///
    /// Intended to be used only in sued's text editing components - not
    /// recommended for user-created commands.
    REPLOnly,
    /// Commands of this scope are available only in sued as a library.
    ///
    /// Should be used when you're writing commands for sued as a library,
    /// not for user-created commands.
    FileOnly,
    /// Commands of this scope are not available anywhere in sued.
    ///
    /// Since `Private` commands are not hard to call when using sued as a
    /// library, this variant is more idiomatic than anything.
    ///
    /// It's strongly recommended you **don't** use this scope for user-created
    /// commands.
    Private,
}

impl std::fmt::Display for CommandScope {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CommandScope::Global => write!(f, "global"),
            CommandScope::REPLOnly => write!(f, "editor only"),
            CommandScope::FileOnly => write!(f, "library only"),
            CommandScope::Private => write!(f, "private"),
        }
    }
}

/// Represents the action of a [Command].
///
/// [CommandAction::action] is a closure that is used to execute the command, wrapped in
/// an `Arc<Mutex<FnMut>>` to make it thread-safe.
#[derive(Clone)]
pub struct CommandAction {
    /// The action of the command.
    ///
    /// This is a closure that is used to execute the command, wrapped in
    /// an `Arc<Mutex<FnMut>>` to make it thread-safe.
    ///
    /// To define an action, write something like this:
    ///
    /// ```rust
    /// let action = Arc::new(Mutex::new(|args: Vec<&str>, registry: &mut CommandRegistry, buffer: &mut FileBuffer| {
    ///     // Take some arguments
    ///     let arg: &str = if let Some(arg) = args.get(1) { // because args[0] is the command
    ///         arg
    ///     }
    ///     else {
    ///         // Quit if there is no argument
    ///         return "no arguments".to_string();
    ///     };
    ///
    ///     // Match it against the registry
    ///     let command = registry.get_command(arg);
    ///
    ///     // If the command is not found, return an error
    ///     if command.is_none() {
    ///         return format!("command {} not found", arg);
    ///     }
    ///
    ///     // If the command is found, put its name on the buffer
    ///     buffer.contents.push(String::from(arg));
    ///
    ///     // Return a String for the REPL
    ///     format!("added {} to the buffer", arg)
    /// }));
    /// ```
    ///
    /// Then, pass it to a construction of [Command]'s [action](Command::action)
    /// field.
    ///
    /// If you need to access values or variables outside of the command, you
    /// should have a comprehensive understanding of Rust's ownership and
    /// lifetimes - fundamental understanding is not enough.
    ///
    /// It's not enough to know that you're supposed to borrow or clone that
    /// variable if it doesn't implement Copy, or that you should use `String`
    /// instead of `&str` if the value doesn't live long enough. It isn't
    /// enough just to "know the rules".
    ///
    /// I've been coding in Rust for four years and the concept of lifetimes
    /// *still* boggles my mind. Maybe I should have read [The Book](https://doc.rust-lang.org/book/)
    /// instead of just writing code. Oh well. At least it's not Unsafe Rust.
    ///
    /// If you want to spare yourself the headache, stick with `args`,
    /// and `state` because you know they'll live for exactly as long as the
    /// closure itself.
    pub action: Arc<Mutex<dyn FnMut(Vec<&str>, &mut EditorState) -> String>>,
}

impl CommandAction {
    /// Creates a new [CommandAction].
    ///
    /// This is useful for defining actions for commands without having to type
    /// `Arc::new(Mutex::new(|args: Vec<&str>, state: &mut EditorState| { ... }))`
    /// every single time.
    pub fn new<A>(action: A) -> Self
    where
        A: FnMut(Vec<&str>, &mut EditorState) -> String + 'static,
    {
        CommandAction {
            action: Arc::new(Mutex::new(action)),
        }
    }

    /// Calls the [CommandAction] and returns the result.
    ///
    /// Intended to be used internally by sued.
    ///
    /// You can call it directly, but this is not recommended unless you're
    /// working with commands whose [CommandScope] is [CommandScope::Private].
    pub fn call(&mut self, args: Vec<&str>, state: &mut EditorState) -> String {
        let mut action = self.action.lock().unwrap();
        action(args, state)
    }
}

impl std::fmt::Debug for CommandAction {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(fmt, "CommandAction is a closure and cannot be debugged")
    }
}

impl Default for CommandAction {
    fn default() -> Self {
        CommandAction::new(|_args: Vec<&str>, _state: &mut EditorState| {
            format!("command has no action")
        })
    }
}

/// Represents a collection of [Command]s.
///
/// Stores [Arc]-wrapped [Command]s in a [HashMap] for easy lookup.
/// This uses [Arc] to make [Command]s thread-safe.
#[derive(Clone, Default)]
pub struct CommandRegistry {
    pub commands: HashMap<String, Arc<Command>>,
}

impl CommandRegistry {
    /// Creates a new empty `CommandRegistry`.
    ///
    /// NOT RECOMMENDED unless you're defining an entirely new command set or
    /// you're not using any of sued's usual commands. Or you're me, and you're
    /// *defining* those default commands.
    ///
    /// It's recommended to use `instantiate` instead.
    pub fn new() -> CommandRegistry {
        CommandRegistry {
            commands: HashMap::new(),
        }
    }

    /// Registers the default commands from `suedfn` into the `CommandRegistry`.
    ///
    /// This is the recommended way to initialise a command registry when using
    /// sued as a library if you want to integrate the editor's commands into
    /// your own programs.
    ///
    /// If you're using sued's structures and functions, but not necessarily its
    /// commands, you can use `new` instead.
    pub fn instantiate() -> CommandRegistry {
        let mut r = CommandRegistry::new();
        commands::register_all(&mut r);
        r
    }

    /// Adds a [Command] to the [CommandRegistry].
    ///
    /// This function is what you'll use to add your own defined commands to
    /// a [CommandRegistry].
    /// If a command of the same name already exists, it won't be added.
    pub fn add_command(&mut self, command: Command) {
        let command = Arc::new(command);
        let command_already_exists = self.commands.contains_key(command.name);
        if command_already_exists {
            eprintln!("command {} already exists", command.name);
            return;
        }
        self.commands
            .insert(command.name.to_string(), Arc::clone(&command));
    }

    /// Removes (deregisters) a [Command] from the [CommandRegistry].
    ///
    /// If the command doesn't exist (isn't already registered), it won't be
    /// removed.
    pub fn remove_command(&mut self, command: &str) {
        let result = self.commands.remove(command);
        if result.is_none() {
            eprintln!("command {} is not registered", command);
        }
    }

    /// Retrieves a `Command` from the `CommandRegistry` by name.
    ///
    /// This is used internally by sued to retrieve and run commands, but it's
    /// useful for people using sued as a library, too.
    pub fn get_command(&self, command: &str) -> Option<Arc<Command>> {
        self.commands.get(command).cloned()
    }

    /// Display each [Command] in the [CommandRegistry] one by one.
    ///
    /// For debugging purposes. It's not recommended to use this function
    /// for anything else, but you don't need me to tell you that.
    pub fn get_all_commands(&self) -> Vec<Arc<Command>> {
        self.commands.values().cloned().collect()
    }
}