Add `:write!` to create nonexistent subdirectories (#1839)

* Make `:write` create nonexistent subdirectories

Prompting as to whether this should take place remains a TODO.

* Move subdirectory creation to new `w!` command
pull/2088/head
Omnikar 3 years ago committed by GitHub
parent d5c0866978
commit 660e0e44b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -12,6 +12,7 @@
| `:buffer-next`, `:bn`, `:bnext` | Go to next buffer. | | `:buffer-next`, `:bn`, `:bnext` | Go to next buffer. |
| `:buffer-previous`, `:bp`, `:bprev` | Go to previous buffer. | | `:buffer-previous`, `:bp`, `:bprev` | Go to previous buffer. |
| `:write`, `:w` | Write changes to disk. Accepts an optional path (:write some/path.txt) | | `:write`, `:w` | Write changes to disk. Accepts an optional path (:write some/path.txt) |
| `:write!`, `:w!` | Write changes to disk forcefully (creating necessary subdirectories). Accepts an optional path (:write some/path.txt) |
| `:new`, `:n` | Create a new scratch buffer. | | `:new`, `:n` | Create a new scratch buffer. |
| `:format`, `:fmt` | Format the file using the LSP formatter. | | `:format`, `:fmt` | Format the file using the LSP formatter. |
| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) | | `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) |

@ -190,7 +190,11 @@ fn buffer_previous(
Ok(()) Ok(())
} }
fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> { fn write_impl(
cx: &mut compositor::Context,
path: Option<&Cow<str>>,
force: bool,
) -> anyhow::Result<()> {
let jobs = &mut cx.jobs; let jobs = &mut cx.jobs;
let doc = doc_mut!(cx.editor); let doc = doc_mut!(cx.editor);
@ -212,7 +216,7 @@ fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::
jobs.callback(callback); jobs.callback(callback);
shared shared
}); });
let future = doc.format_and_save(fmt); let future = doc.format_and_save(fmt, force);
cx.jobs.add(Job::new(future).wait_before_exiting()); cx.jobs.add(Job::new(future).wait_before_exiting());
if path.is_some() { if path.is_some() {
@ -228,7 +232,15 @@ fn write(
args: &[Cow<str>], args: &[Cow<str>],
_event: PromptEvent, _event: PromptEvent,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
write_impl(cx, args.first()) write_impl(cx, args.first(), false)
}
fn force_write(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
write_impl(cx, args.first(), true)
} }
fn new_file( fn new_file(
@ -381,7 +393,7 @@ fn write_quit(
args: &[Cow<str>], args: &[Cow<str>],
event: PromptEvent, event: PromptEvent,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
write_impl(cx, args.first())?; write_impl(cx, args.first(), false)?;
quit(cx, &[], event) quit(cx, &[], event)
} }
@ -390,7 +402,7 @@ fn force_write_quit(
args: &[Cow<str>], args: &[Cow<str>],
event: PromptEvent, event: PromptEvent,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
write_impl(cx, args.first())?; write_impl(cx, args.first(), true)?;
force_quit(cx, &[], event) force_quit(cx, &[], event)
} }
@ -447,7 +459,7 @@ fn write_all_impl(
jobs.callback(callback); jobs.callback(callback);
shared shared
}); });
let future = doc.format_and_save(fmt); let future = doc.format_and_save(fmt, force);
jobs.add(Job::new(future).wait_before_exiting()); jobs.add(Job::new(future).wait_before_exiting());
} }
@ -1140,6 +1152,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: write, fun: write,
completer: Some(completers::filename), completer: Some(completers::filename),
}, },
TypableCommand {
name: "write!",
aliases: &["w!"],
doc: "Write changes to disk forcefully (creating necessary subdirectories). Accepts an optional path (:write some/path.txt)",
fun: force_write,
completer: Some(completers::filename),
},
TypableCommand { TypableCommand {
name: "new", name: "new",
aliases: &["n"], aliases: &["n"],

@ -434,15 +434,16 @@ impl Document {
Some(fut) Some(fut)
} }
pub fn save(&mut self) -> impl Future<Output = Result<(), anyhow::Error>> { pub fn save(&mut self, force: bool) -> impl Future<Output = Result<(), anyhow::Error>> {
self.save_impl::<futures_util::future::Ready<_>>(None) self.save_impl::<futures_util::future::Ready<_>>(None, force)
} }
pub fn format_and_save( pub fn format_and_save(
&mut self, &mut self,
formatting: Option<impl Future<Output = LspFormatting>>, formatting: Option<impl Future<Output = LspFormatting>>,
force: bool,
) -> impl Future<Output = anyhow::Result<()>> { ) -> impl Future<Output = anyhow::Result<()>> {
self.save_impl(formatting) self.save_impl(formatting, force)
} }
// TODO: do we need some way of ensuring two save operations on the same doc can't run at once? // TODO: do we need some way of ensuring two save operations on the same doc can't run at once?
@ -454,6 +455,7 @@ impl Document {
fn save_impl<F: Future<Output = LspFormatting>>( fn save_impl<F: Future<Output = LspFormatting>>(
&mut self, &mut self,
formatting: Option<F>, formatting: Option<F>,
force: bool,
) -> impl Future<Output = Result<(), anyhow::Error>> { ) -> impl Future<Output = Result<(), anyhow::Error>> {
// we clone and move text + path into the future so that we asynchronously save the current // we clone and move text + path into the future so that we asynchronously save the current
// state without blocking any further edits. // state without blocking any further edits.
@ -475,9 +477,13 @@ impl Document {
if let Some(parent) = path.parent() { if let Some(parent) = path.parent() {
// TODO: display a prompt asking the user if the directories should be created // TODO: display a prompt asking the user if the directories should be created
if !parent.exists() { if !parent.exists() {
if force {
std::fs::DirBuilder::new().recursive(true).create(parent)?;
} else {
bail!("can't save file, parent directory does not exist"); bail!("can't save file, parent directory does not exist");
} }
} }
}
if let Some(fmt) = formatting { if let Some(fmt) = formatting {
let success = Transaction::from(fmt.await).changes().apply(&mut text); let success = Transaction::from(fmt.await).changes().apply(&mut text);

Loading…
Cancel
Save