term: allow Editor to be reusable
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.
This commit is contained in:
parent
23f8cf0d84
commit
0d402647cb
|
|
@ -1,7 +1,6 @@
|
||||||
#![allow(clippy::or_fun_call)]
|
#![allow(clippy::or_fun_call)]
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
@ -188,18 +187,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
path.display()
|
path.display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Operation::Edit => {
|
Operation::Edit => match term::editor::Editor::new(&path)?.extension("json").edit()? {
|
||||||
let Some(cmd) = term::editor::default_editor() else {
|
Some(_) => {
|
||||||
anyhow::bail!("no editor configured; please set the `EDITOR` environment variable");
|
term::success!("Successfully made changes to the configuration at {path:?}")
|
||||||
};
|
}
|
||||||
process::Command::new(cmd)
|
None => term::info!("No changes were made to the configuration at {path:?}"),
|
||||||
.stdout(process::Stdio::inherit())
|
},
|
||||||
.stderr(process::Stdio::inherit())
|
|
||||||
.stdin(process::Stdio::inherit())
|
|
||||||
.arg(&path)
|
|
||||||
.spawn()?
|
|
||||||
.wait()?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -853,7 +853,10 @@ impl CommentBuilder {
|
||||||
for line in hunk.to_unified_string()?.lines() {
|
for line in hunk.to_unified_string()?.lines() {
|
||||||
writeln!(&mut input, "> {line}")?;
|
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 {
|
if let Some(output) = output {
|
||||||
let header = HunkHeader::try_from(hunk)?;
|
let header = HunkHeader::try_from(hunk)?;
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,10 @@ impl Message {
|
||||||
let comment = match self {
|
let comment = match self {
|
||||||
Message::Edit => {
|
Message::Edit => {
|
||||||
if io::stderr().is_terminal() {
|
if io::stderr().is_terminal() {
|
||||||
term::Editor::new().extension("markdown").edit(help)?
|
term::Editor::comment()
|
||||||
|
.extension("markdown")
|
||||||
|
.initial(help)?
|
||||||
|
.edit()?
|
||||||
} else {
|
} else {
|
||||||
Some(help.to_owned())
|
Some(help.to_owned())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,26 +13,52 @@ pub const PATHS: &[&str] = &["/usr/local/bin", "/usr/bin", "/bin"];
|
||||||
/// Allows for text input in the configured editor.
|
/// Allows for text input in the configured editor.
|
||||||
pub struct Editor {
|
pub struct Editor {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
}
|
truncate: bool,
|
||||||
|
cleanup: bool,
|
||||||
impl Drop for Editor {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
fs::remove_file(&self.path).ok();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Editor {
|
impl Default for Editor {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::comment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Editor {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.cleanup {
|
||||||
|
fs::remove_file(&self.path).ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Editor {
|
impl Editor {
|
||||||
/// Create a new 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);
|
let path = env::temp_dir().join(COMMENT_FILE);
|
||||||
|
|
||||||
Self { path }
|
Self {
|
||||||
|
path,
|
||||||
|
truncate: true,
|
||||||
|
cleanup: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the file extension.
|
/// Set the file extension.
|
||||||
|
|
@ -43,26 +69,43 @@ impl Editor {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open the editor and return the edited text.
|
/// Truncate the file to length 0 when opening
|
||||||
///
|
pub fn truncate(mut self, truncate: bool) -> Self {
|
||||||
/// If the text hasn't changed from the initial contents of the editor,
|
self.truncate = truncate;
|
||||||
/// return `None`.
|
self
|
||||||
pub fn edit(&mut self, initial: impl AsRef<[u8]>) -> io::Result<Option<String>> {
|
}
|
||||||
let initial = initial.as_ref();
|
|
||||||
|
/// 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()
|
let mut file = fs::OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.truncate(true)
|
.truncate(self.truncate)
|
||||||
.open(&self.path)?;
|
.open(&self.path)?;
|
||||||
|
|
||||||
if file.metadata()?.len() == 0 {
|
if file.metadata()?.len() == 0 {
|
||||||
file.write_all(initial)?;
|
file.write_all(content)?;
|
||||||
if !initial.ends_with(&[b'\n']) {
|
if !content.ends_with(&[b'\n']) {
|
||||||
file.write_all(b"\n")?;
|
file.write_all(b"\n")?;
|
||||||
}
|
}
|
||||||
file.flush()?;
|
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 {
|
let Some(cmd) = self::default_editor() else {
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::NotFound,
|
io::ErrorKind::NotFound,
|
||||||
|
|
@ -126,7 +169,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the default editor command.
|
/// Get the default editor command.
|
||||||
pub fn default_editor() -> Option<OsString> {
|
fn default_editor() -> Option<OsString> {
|
||||||
// First check the standard environment variables.
|
// First check the standard environment variables.
|
||||||
if let Ok(visual) = env::var("VISUAL") {
|
if let Ok(visual) = env::var("VISUAL") {
|
||||||
if !visual.is_empty() {
|
if !visual.is_empty() {
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,10 @@ fn main() -> anyhow::Result<()> {
|
||||||
radicle_term::pager::page(table)?;
|
radicle_term::pager::page(table)?;
|
||||||
}
|
}
|
||||||
"editor" => {
|
"editor" => {
|
||||||
let output = terminal::editor::Editor::new()
|
let output = terminal::editor::Editor::comment()
|
||||||
.extension("rs")
|
.extension("rs")
|
||||||
.edit("// Enter code here.");
|
.initial("// Enter code here.")?
|
||||||
|
.edit();
|
||||||
|
|
||||||
match output {
|
match output {
|
||||||
Ok(Some(s)) => {
|
Ok(Some(s)) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue