mirror of https://github.com/helix-editor/helix
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!` commandpull/2088/head
parent
d5c0866978
commit
660e0e44b2
|
@ -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…
Reference in New Issue