Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
term: allow Editor to be reusable
Merged fintohaps opened 1 year ago

The Editor is very useful for correctly opening a text editor and making changes to some initial input. The current use of Editor is only for getting input for commets.

However, it would also be useful for opening up a text editor on some other existing files or text, for example, in the command rad config edit.

The Editor struct was changed to have two new options, truncate and cleanup, to allow the user of the struct to dictate whether existing text is truncated, and if the underlying file should be remove.

The original new method is now named comment, mimicing the existing construction of a temporary RAD_COMMENT file.

The new version of new accepts any file for the Editor and will open it without truncating or removing the file.

Editor is now used for the rad config edit command.

5 files changed +78 -35 23f8cf0d 0d402647
modified radicle-cli/src/commands/config.rs
@@ -1,7 +1,6 @@
#![allow(clippy::or_fun_call)]
use std::ffi::OsString;
use std::path::Path;
-
use std::process;
use std::str::FromStr;

use anyhow::anyhow;
@@ -188,18 +187,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
                path.display()
            );
        }
-
        Operation::Edit => {
-
            let Some(cmd) = term::editor::default_editor() else {
-
                anyhow::bail!("no editor configured; please set the `EDITOR` environment variable");
-
            };
-
            process::Command::new(cmd)
-
                .stdout(process::Stdio::inherit())
-
                .stderr(process::Stdio::inherit())
-
                .stdin(process::Stdio::inherit())
-
                .arg(&path)
-
                .spawn()?
-
                .wait()?;
-
        }
+
        Operation::Edit => match term::editor::Editor::new(&path)?.extension("json").edit()? {
+
            Some(_) => {
+
                term::success!("Successfully made changes to the configuration at {path:?}")
+
            }
+
            None => term::info!("No changes were made to the configuration at {path:?}"),
+
        },
    }

    Ok(())
modified radicle-cli/src/commands/patch/review/builder.rs
@@ -853,7 +853,10 @@ impl CommentBuilder {
        for line in hunk.to_unified_string()?.lines() {
            writeln!(&mut input, "> {line}")?;
        }
-
        let output = term::Editor::new().extension("diff").edit(input)?;
+
        let output = term::Editor::comment()
+
            .extension("diff")
+
            .initial(input)?
+
            .edit()?;

        if let Some(output) = output {
            let header = HunkHeader::try_from(hunk)?;
modified radicle-cli/src/terminal/patch.rs
@@ -51,7 +51,10 @@ impl Message {
        let comment = match self {
            Message::Edit => {
                if io::stderr().is_terminal() {
-
                    term::Editor::new().extension("markdown").edit(help)?
+
                    term::Editor::comment()
+
                        .extension("markdown")
+
                        .initial(help)?
+
                        .edit()?
                } else {
                    Some(help.to_owned())
                }
modified radicle-term/src/editor.rs
@@ -13,26 +13,52 @@ pub const PATHS: &[&str] = &["/usr/local/bin", "/usr/bin", "/bin"];
/// Allows for text input in the configured editor.
pub struct Editor {
    path: PathBuf,
+
    truncate: bool,
+
    cleanup: bool,
}

-
impl Drop for Editor {
-
    fn drop(&mut self) {
-
        fs::remove_file(&self.path).ok();
+
impl Default for Editor {
+
    fn default() -> Self {
+
        Self::comment()
    }
}

-
impl Default for Editor {
-
    fn default() -> Self {
-
        Self::new()
+
impl Drop for Editor {
+
    fn drop(&mut self) {
+
        if self.cleanup {
+
            fs::remove_file(&self.path).ok();
+
        }
    }
}

impl Editor {
    /// Create a new editor.
-
    pub fn new() -> Self {
+
    pub fn new(path: impl AsRef<Path>) -> io::Result<Self> {
+
        let path = path.as_ref();
+
        if path.try_exists()? {
+
            let meta = fs::metadata(path)?;
+
            if !meta.is_file() {
+
                return Err(io::Error::new(
+
                    io::ErrorKind::InvalidInput,
+
                    "must be used to edit a file",
+
                ));
+
            }
+
        }
+
        Ok(Self {
+
            path: path.to_path_buf(),
+
            truncate: false,
+
            cleanup: false,
+
        })
+
    }
+

+
    pub fn comment() -> Self {
        let path = env::temp_dir().join(COMMENT_FILE);

-
        Self { path }
+
        Self {
+
            path,
+
            truncate: true,
+
            cleanup: true,
+
        }
    }

    /// Set the file extension.
@@ -43,26 +69,43 @@ impl Editor {
        self
    }

-
    /// Open the editor and return the edited text.
-
    ///
-
    /// If the text hasn't changed from the initial contents of the editor,
-
    /// return `None`.
-
    pub fn edit(&mut self, initial: impl AsRef<[u8]>) -> io::Result<Option<String>> {
-
        let initial = initial.as_ref();
+
    /// Truncate the file to length 0 when opening
+
    pub fn truncate(mut self, truncate: bool) -> Self {
+
        self.truncate = truncate;
+
        self
+
    }
+

+
    /// Clean up the file after the [`Editor`] is dropped.
+
    pub fn cleanup(mut self, cleanup: bool) -> Self {
+
        self.cleanup = cleanup;
+
        self
+
    }
+

+
    /// Initialize the file with the provided `content`, as long as the file
+
    /// does not already contain anything.
+
    pub fn initial(self, content: impl AsRef<[u8]>) -> io::Result<Self> {
+
        let content = content.as_ref();
        let mut file = fs::OpenOptions::new()
            .write(true)
            .create(true)
-
            .truncate(true)
+
            .truncate(self.truncate)
            .open(&self.path)?;

        if file.metadata()?.len() == 0 {
-
            file.write_all(initial)?;
-
            if !initial.ends_with(&[b'\n']) {
+
            file.write_all(content)?;
+
            if !content.ends_with(&[b'\n']) {
                file.write_all(b"\n")?;
            }
            file.flush()?;
        }
+
        Ok(self)
+
    }

+
    /// Open the editor and return the edited text.
+
    ///
+
    /// If the text hasn't changed from the initial contents of the editor,
+
    /// return `None`.
+
    pub fn edit(&mut self) -> io::Result<Option<String>> {
        let Some(cmd) = self::default_editor() else {
            return Err(io::Error::new(
                io::ErrorKind::NotFound,
@@ -126,7 +169,7 @@ impl Editor {
}

/// Get the default editor command.
-
pub fn default_editor() -> Option<OsString> {
+
fn default_editor() -> Option<OsString> {
    // First check the standard environment variables.
    if let Ok(visual) = env::var("VISUAL") {
        if !visual.is_empty() {
modified radicle-tools/src/rad-cli-demo.rs
@@ -35,9 +35,10 @@ fn main() -> anyhow::Result<()> {
            radicle_term::pager::page(table)?;
        }
        "editor" => {
-
            let output = terminal::editor::Editor::new()
+
            let output = terminal::editor::Editor::comment()
                .extension("rs")
-
                .edit("// Enter code here.");
+
                .initial("// Enter code here.")?
+
                .edit();

            match output {
                Ok(Some(s)) => {