Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add :write! to create nonexistent subdirectories #1839

Merged
merged 3 commits into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions book/src/generated/typable-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
| `:buffer-next`, `:bn`, `:bnext` | Go to next 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 forcefully (creating necessary subdirectories). Accepts an optional path (:write some/path.txt) |
| `:new`, `:n` | Create a new scratch buffer. |
| `: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.) |
Expand Down
31 changes: 25 additions & 6 deletions helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,11 @@ fn buffer_previous(
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 doc = doc_mut!(cx.editor);

Expand All @@ -212,7 +216,7 @@ fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::
jobs.callback(callback);
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());

if path.is_some() {
Expand All @@ -228,7 +232,15 @@ fn write(
args: &[Cow<str>],
_event: PromptEvent,
) -> 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(
Expand Down Expand Up @@ -381,7 +393,7 @@ fn write_quit(
args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
write_impl(cx, args.first())?;
write_impl(cx, args.first(), false)?;
quit(cx, &[], event)
}

Expand All @@ -390,7 +402,7 @@ fn force_write_quit(
args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
write_impl(cx, args.first())?;
write_impl(cx, args.first(), true)?;
force_quit(cx, &[], event)
}

Expand Down Expand Up @@ -447,7 +459,7 @@ fn write_all_impl(
jobs.callback(callback);
shared
});
let future = doc.format_and_save(fmt);
let future = doc.format_and_save(fmt, force);
jobs.add(Job::new(future).wait_before_exiting());
}

Expand Down Expand Up @@ -1140,6 +1152,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: write,
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 {
name: "new",
aliases: &["n"],
Expand Down
14 changes: 10 additions & 4 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,15 +434,16 @@ impl Document {
Some(fut)
}

pub fn save(&mut self) -> impl Future<Output = Result<(), anyhow::Error>> {
self.save_impl::<futures_util::future::Ready<_>>(None)
pub fn save(&mut self, force: bool) -> impl Future<Output = Result<(), anyhow::Error>> {
self.save_impl::<futures_util::future::Ready<_>>(None, force)
}

pub fn format_and_save(
&mut self,
formatting: Option<impl Future<Output = LspFormatting>>,
force: bool,
) -> 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?
Expand All @@ -454,6 +455,7 @@ impl Document {
fn save_impl<F: Future<Output = LspFormatting>>(
&mut self,
formatting: Option<F>,
force: bool,
) -> impl Future<Output = Result<(), anyhow::Error>> {
// we clone and move text + path into the future so that we asynchronously save the current
// state without blocking any further edits.
Expand All @@ -475,7 +477,11 @@ impl Document {
if let Some(parent) = path.parent() {
// TODO: display a prompt asking the user if the directories should be created
if !parent.exists() {
bail!("can't save file, parent directory does not exist");
if force {
std::fs::DirBuilder::new().recursive(true).create(parent)?;
} else {
bail!("can't save file, parent directory does not exist");
}
}
}

Expand Down