on master help sign in
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

commit660e0e44b2b6329c1d0af788d624dd5765c2acc6 authorOmnikar <omnikar5@gmail.com> dateTue 12 Apr 2022, 03:52 (5 years ago) parentd5c0866
3 files changed+36−10
book/src/generated/typable-cmd.md+1 helix-term/src/commands/typed.rs+25−6 helix-view/src/document.rs+10−4
book/src/generated/typable-cmd.md+1
@@ -12,6 +12,7 @@
1212| `:buffer-next`, `:bn`, `:bnext` | Go to next buffer. |
1313| `:buffer-previous`, `:bp`, `:bprev` | Go to previous buffer. |
1414| `:write`, `:w` | Write changes to disk. Accepts an optional path (:write some/path.txt) |
15+| `:write!`, `:w!` | Write changes to disk forcefully (creating necessary subdirectories). Accepts an optional path (:write some/path.txt) |
1516| `:new`, `:n` | Create a new scratch buffer. |
1617| `:format`, `:fmt` | Format the file using the LSP formatter. |
1718| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) |
helix-term/src/commands/typed.rs+25−6
@@ -190,7 +190,11 @@ fn buffer_previous(
190190 Ok(())
191191}
192192
193−fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> {
193+fn write_impl(
194+ cx: &mut compositor::Context,
195+ path: Option<&Cow<str>>,
196+ force: bool,
197+) -> anyhow::Result<()> {
194198 let jobs = &mut cx.jobs;
195199 let doc = doc_mut!(cx.editor);
196200
@@ -212,7 +216,7 @@ fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::
212216 jobs.callback(callback);
213217 shared
214218 });
215− let future = doc.format_and_save(fmt);
219+ let future = doc.format_and_save(fmt, force);
216220 cx.jobs.add(Job::new(future).wait_before_exiting());
217221
218222 if path.is_some() {
@@ -228,7 +232,15 @@ fn write(
228232 args: &[Cow<str>],
229233 _event: PromptEvent,
230234) -> anyhow::Result<()> {
231− write_impl(cx, args.first())
235+ write_impl(cx, args.first(), false)
236+}
237+
238+fn force_write(
239+ cx: &mut compositor::Context,
240+ args: &[Cow<str>],
241+ _event: PromptEvent,
242+) -> anyhow::Result<()> {
243+ write_impl(cx, args.first(), true)
232244}
233245
234246fn new_file(
@@ -381,7 +393,7 @@ fn write_quit(
381393 args: &[Cow<str>],
382394 event: PromptEvent,
383395) -> anyhow::Result<()> {
384− write_impl(cx, args.first())?;
396+ write_impl(cx, args.first(), false)?;
385397 quit(cx, &[], event)
386398}
387399
@@ -390,7 +402,7 @@ fn force_write_quit(
390402 args: &[Cow<str>],
391403 event: PromptEvent,
392404) -> anyhow::Result<()> {
393− write_impl(cx, args.first())?;
405+ write_impl(cx, args.first(), true)?;
394406 force_quit(cx, &[], event)
395407}
396408
@@ -447,7 +459,7 @@ fn write_all_impl(
447459 jobs.callback(callback);
448460 shared
449461 });
450− let future = doc.format_and_save(fmt);
462+ let future = doc.format_and_save(fmt, force);
451463 jobs.add(Job::new(future).wait_before_exiting());
452464 }
453465
@@ -1140,6 +1152,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
11401152 fun: write,
11411153 completer: Some(completers::filename),
11421154 },
1155+ TypableCommand {
1156+ name: "write!",
1157+ aliases: &["w!"],
1158+ doc: "Write changes to disk forcefully (creating necessary subdirectories). Accepts an optional path (:write some/path.txt)",
1159+ fun: force_write,
1160+ completer: Some(completers::filename),
1161+ },
11431162 TypableCommand {
11441163 name: "new",
11451164 aliases: &["n"],
helix-view/src/document.rs+10−4
@@ -434,15 +434,16 @@ impl Document {
434434 Some(fut)
435435 }
436436
437− pub fn save(&mut self) -> impl Future<Output = Result<(), anyhow::Error>> {
438− self.save_impl::<futures_util::future::Ready<_>>(None)
437+ pub fn save(&mut self, force: bool) -> impl Future<Output = Result<(), anyhow::Error>> {
438+ self.save_impl::<futures_util::future::Ready<_>>(None, force)
439439 }
440440
441441 pub fn format_and_save(
442442 &mut self,
443443 formatting: Option<impl Future<Output = LspFormatting>>,
444+ force: bool,
444445 ) -> impl Future<Output = anyhow::Result<()>> {
445− self.save_impl(formatting)
446+ self.save_impl(formatting, force)
446447 }
447448
448449 // 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 {
454455 fn save_impl<F: Future<Output = LspFormatting>>(
455456 &mut self,
456457 formatting: Option<F>,
458+ force: bool,
457459 ) -> impl Future<Output = Result<(), anyhow::Error>> {
458460 // we clone and move text + path into the future so that we asynchronously save the current
459461 // state without blocking any further edits.
@@ -475,7 +477,11 @@ impl Document {
475477 if let Some(parent) = path.parent() {
476478 // TODO: display a prompt asking the user if the directories should be created
477479 if !parent.exists() {
478− bail!("can't save file, parent directory does not exist");
480+ if force {
481+ std::fs::DirBuilder::new().recursive(true).create(parent)?;
482+ } else {
483+ bail!("can't save file, parent directory does not exist");
484+ }
479485 }
480486 }
481487