on master help sign in
Add `:write!` to create nonexistent subdirectories (#1839)
660e0e4Omnikar
book/src/generated/typable-cmd.md+1
@@ -12,6 +12,7 @@
12| `:buffer-next`, `:bn`, `:bnext` | Go to next buffer. |
12| `:buffer-next`, `:bn`, `:bnext` | Go to next buffer. |
13| `:buffer-previous`, `:bp`, `:bprev` | Go to previous buffer. |
13| `:buffer-previous`, `:bp`, `:bprev` | Go to previous buffer. |
14| `:write`, `:w` | Write changes to disk. Accepts an optional path (:write some/path.txt) |
14| `: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) |
15| `:new`, `:n` | Create a new scratch buffer. |
16| `:new`, `:n` | Create a new scratch buffer. |
16| `:format`, `:fmt` | Format the file using the LSP formatter. |
17| `:format`, `:fmt` | Format the file using the LSP formatter. |
17| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) |
18| `: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(
190 Ok(())
190 Ok(())
191}
191}
192
192
193+fn write_impl(
193−fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> {
194+ cx: &mut compositor::Context,
195+ path: Option<&Cow<str>>,
196+ force: bool,
197+) -> anyhow::Result<()> {
194 let jobs = &mut cx.jobs;
198 let jobs = &mut cx.jobs;
195 let doc = doc_mut!(cx.editor);
199 let doc = doc_mut!(cx.editor);
196
200
@@ -212,7 +216,7 @@ fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::
212 jobs.callback(callback);
216 jobs.callback(callback);
213 shared
217 shared
214 });
218 });
215− let future = doc.format_and_save(fmt);
219+ let future = doc.format_and_save(fmt, force);
216 cx.jobs.add(Job::new(future).wait_before_exiting());
220 cx.jobs.add(Job::new(future).wait_before_exiting());
217
221
218 if path.is_some() {
222 if path.is_some() {
@@ -228,7 +232,15 @@ fn write(
228 args: &[Cow<str>],
232 args: &[Cow<str>],
229 _event: PromptEvent,
233 _event: PromptEvent,
230) -> anyhow::Result<()> {
234) -> 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)
232}
244}
233
245
234fn new_file(
246fn new_file(
@@ -381,7 +393,7 @@ fn write_quit(
381 args: &[Cow<str>],
393 args: &[Cow<str>],
382 event: PromptEvent,
394 event: PromptEvent,
383) -> anyhow::Result<()> {
395) -> anyhow::Result<()> {
384− write_impl(cx, args.first())?;
396+ write_impl(cx, args.first(), false)?;
385 quit(cx, &[], event)
397 quit(cx, &[], event)
386}
398}
387
399
@@ -390,7 +402,7 @@ fn force_write_quit(
390 args: &[Cow<str>],
402 args: &[Cow<str>],
391 event: PromptEvent,
403 event: PromptEvent,
392) -> anyhow::Result<()> {
404) -> anyhow::Result<()> {
393− write_impl(cx, args.first())?;
405+ write_impl(cx, args.first(), true)?;
394 force_quit(cx, &[], event)
406 force_quit(cx, &[], event)
395}
407}
396
408
@@ -447,7 +459,7 @@ fn write_all_impl(
447 jobs.callback(callback);
459 jobs.callback(callback);
448 shared
460 shared
449 });
461 });
450− let future = doc.format_and_save(fmt);
462+ let future = doc.format_and_save(fmt, force);
451 jobs.add(Job::new(future).wait_before_exiting());
463 jobs.add(Job::new(future).wait_before_exiting());
452 }
464 }
453
465
@@ -1140,6 +1152,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
1140 fun: write,
1152 fun: write,
1141 completer: Some(completers::filename),
1153 completer: Some(completers::filename),
1142 },
1154 },
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+ },
1143 TypableCommand {
1162 TypableCommand {
1144 name: "new",
1163 name: "new",
1145 aliases: &["n"],
1164 aliases: &["n"],
helix-view/src/document.rs+10−4
@@ -434,15 +434,16 @@ impl Document {
434 Some(fut)
434 Some(fut)
435 }
435 }
436
436
437− pub fn save(&mut self) -> impl Future<Output = Result<(), anyhow::Error>> {
437+ pub fn save(&mut self, force: bool) -> impl Future<Output = Result<(), anyhow::Error>> {
438− self.save_impl::<futures_util::future::Ready<_>>(None)
438+ self.save_impl::<futures_util::future::Ready<_>>(None, force)
439 }
439 }
440
440
441 pub fn format_and_save(
441 pub fn format_and_save(
442 &mut self,
442 &mut self,
443 formatting: Option<impl Future<Output = LspFormatting>>,
443 formatting: Option<impl Future<Output = LspFormatting>>,
444+ force: bool,
444 ) -> impl Future<Output = anyhow::Result<()>> {
445 ) -> impl Future<Output = anyhow::Result<()>> {
445− self.save_impl(formatting)
446+ self.save_impl(formatting, force)
446 }
447 }
447
448
448 // TODO: do we need some way of ensuring two save operations on the same doc can't run at once?
449 // 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 {
454 fn save_impl<F: Future<Output = LspFormatting>>(
455 fn save_impl<F: Future<Output = LspFormatting>>(
455 &mut self,
456 &mut self,
456 formatting: Option<F>,
457 formatting: Option<F>,
458+ force: bool,
457 ) -> impl Future<Output = Result<(), anyhow::Error>> {
459 ) -> impl Future<Output = Result<(), anyhow::Error>> {
458 // we clone and move text + path into the future so that we asynchronously save the current
460 // we clone and move text + path into the future so that we asynchronously save the current
459 // state without blocking any further edits.
461 // state without blocking any further edits.
@@ -475,7 +477,11 @@ impl Document {
475 if let Some(parent) = path.parent() {
477 if let Some(parent) = path.parent() {
476 // TODO: display a prompt asking the user if the directories should be created
478 // TODO: display a prompt asking the user if the directories should be created
477 if !parent.exists() {
479 if !parent.exists() {
480+ if force {
481+ std::fs::DirBuilder::new().recursive(true).create(parent)?;
482+ } else {
478− bail!("can't save file, parent directory does not exist");
483+ bail!("can't save file, parent directory does not exist");
484+ }
479 }
485 }
480 }
486 }
481
487