From 20acc123afc1257b396cf76701e55c4707954901 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Sun, 3 Mar 2024 21:47:34 -0800 Subject: [PATCH] Implement 'format without save' (#8806) This solves a major usability problem in Zed, that there's no way to temporarily disable auto formatting without toggling the whole feature off. fixes https://github.com/zed-industries/zed/issues/5230 Release Notes: - Added a new `workspace::SaveWithoutFormatting`, bound to `cmd-k s`, to save a file without invoking the auto formatter. --- assets/keymaps/default-linux.json | 1 + assets/keymaps/default-macos.json | 3 ++- crates/diagnostics/src/diagnostics.rs | 9 +++++++-- crates/editor/src/editor_tests.rs | 12 ++++++------ crates/editor/src/items.rs | 17 ++++++++++++----- crates/search/src/project_search.rs | 3 ++- crates/workspace/src/item.rs | 24 ++++++++++++++++++++---- crates/workspace/src/pane.rs | 16 ++++++++++++---- crates/workspace/src/workspace.rs | 6 ++++++ crates/zed/src/zed.rs | 2 +- 10 files changed, 69 insertions(+), 24 deletions(-) diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 459f7e04db..29e3d19d78 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -365,6 +365,7 @@ "ctrl-alt-b": "branches::OpenRecent", "ctrl-~": "workspace::NewTerminal", "ctrl-s": "workspace::Save", + "ctrl-k s": "workspace::SaveWithoutFormat", "ctrl-shift-s": "workspace::SaveAs", "ctrl-n": "workspace::NewFile", "ctrl-shift-n": "workspace::NewWindow", diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index dcf0817815..02d05c0409 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -408,6 +408,7 @@ "alt-cmd-b": "branches::OpenRecent", "ctrl-~": "workspace::NewTerminal", "cmd-s": "workspace::Save", + "cmd-k s": "workspace::SaveWithoutFormat", "cmd-shift-s": "workspace::SaveAs", "cmd-n": "workspace::NewFile", "cmd-shift-n": "workspace::NewWindow", @@ -426,8 +427,8 @@ "cmd-j": "workspace::ToggleBottomDock", "alt-cmd-y": "workspace::CloseAllDocks", "cmd-shift-f": "pane::DeploySearch", - "cmd-k cmd-t": "theme_selector::Toggle", "cmd-k cmd-s": "zed::OpenKeymap", + "cmd-k cmd-t": "theme_selector::Toggle", "cmd-t": "project_symbols::Toggle", "cmd-p": "file_finder::Toggle", "cmd-shift-p": "command_palette::Toggle", diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 94e78390d6..6d6a946fa0 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -735,8 +735,13 @@ impl Item for ProjectDiagnosticsEditor { true } - fn save(&mut self, project: Model, cx: &mut ViewContext) -> Task> { - self.editor.save(project, cx) + fn save( + &mut self, + format: bool, + project: Model, + cx: &mut ViewContext, + ) -> Task> { + self.editor.save(format, project, cx) } fn save_as( diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index d7679c72cd..36daaec5d9 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5265,7 +5265,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { assert!(cx.read(|cx| editor.is_dirty(cx))); let save = editor - .update(cx, |editor, cx| editor.save(project.clone(), cx)) + .update(cx, |editor, cx| editor.save(true, project.clone(), cx)) .unwrap(); fake_server .handle_request::(move |params, _| async move { @@ -5303,7 +5303,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { unreachable!() }); let save = editor - .update(cx, |editor, cx| editor.save(project.clone(), cx)) + .update(cx, |editor, cx| editor.save(true, project.clone(), cx)) .unwrap(); cx.executor().advance_clock(super::FORMAT_TIMEOUT); cx.executor().start_waiting(); @@ -5326,7 +5326,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { }); let save = editor - .update(cx, |editor, cx| editor.save(project.clone(), cx)) + .update(cx, |editor, cx| editor.save(true, project.clone(), cx)) .unwrap(); fake_server .handle_request::(move |params, _| async move { @@ -5379,7 +5379,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { assert!(cx.read(|cx| editor.is_dirty(cx))); let save = editor - .update(cx, |editor, cx| editor.save(project.clone(), cx)) + .update(cx, |editor, cx| editor.save(true, project.clone(), cx)) .unwrap(); fake_server .handle_request::(move |params, _| async move { @@ -5418,7 +5418,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { }, ); let save = editor - .update(cx, |editor, cx| editor.save(project.clone(), cx)) + .update(cx, |editor, cx| editor.save(true, project.clone(), cx)) .unwrap(); cx.executor().advance_clock(super::FORMAT_TIMEOUT); cx.executor().start_waiting(); @@ -5441,7 +5441,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { }); let save = editor - .update(cx, |editor, cx| editor.save(project.clone(), cx)) + .update(cx, |editor, cx| editor.save(true, project.clone(), cx)) .unwrap(); fake_server .handle_request::(move |params, _| async move { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 96fdcfdefc..d1324adad0 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -702,14 +702,21 @@ impl Item for Editor { } } - fn save(&mut self, project: Model, cx: &mut ViewContext) -> Task> { + fn save( + &mut self, + format: bool, + project: Model, + cx: &mut ViewContext, + ) -> Task> { self.report_editor_event("save", None, cx); let buffers = self.buffer().clone().read(cx).all_buffers(); cx.spawn(|this, mut cx| async move { - this.update(&mut cx, |this, cx| { - this.perform_format(project.clone(), FormatTrigger::Save, cx) - })? - .await?; + if format { + this.update(&mut cx, |this, cx| { + this.perform_format(project.clone(), FormatTrigger::Save, cx) + })? + .await?; + } if buffers.len() == 1 { project diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index b26e4193d2..c5d5f667dc 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -539,11 +539,12 @@ impl Item for ProjectSearchView { fn save( &mut self, + format: bool, project: Model, cx: &mut ViewContext, ) -> Task> { self.results_editor - .update(cx, |editor, cx| editor.save(project, cx)) + .update(cx, |editor, cx| editor.save(format, project, cx)) } fn save_as( diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 580703d6b0..25d9f5ed89 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -146,7 +146,12 @@ pub trait Item: FocusableView + EventEmitter { fn can_save(&self, _cx: &AppContext) -> bool { false } - fn save(&mut self, _project: Model, _cx: &mut ViewContext) -> Task> { + fn save( + &mut self, + _format: bool, + _project: Model, + _cx: &mut ViewContext, + ) -> Task> { unimplemented!("save() must be implemented if can_save() returns true") } fn save_as( @@ -258,7 +263,12 @@ pub trait ItemHandle: 'static + Send { fn is_dirty(&self, cx: &AppContext) -> bool; fn has_conflict(&self, cx: &AppContext) -> bool; fn can_save(&self, cx: &AppContext) -> bool; - fn save(&self, project: Model, cx: &mut WindowContext) -> Task>; + fn save( + &self, + format: bool, + project: Model, + cx: &mut WindowContext, + ) -> Task>; fn save_as( &self, project: Model, @@ -566,8 +576,13 @@ impl ItemHandle for View { self.read(cx).can_save(cx) } - fn save(&self, project: Model, cx: &mut WindowContext) -> Task> { - self.update(cx, |item, cx| item.save(project, cx)) + fn save( + &self, + format: bool, + project: Model, + cx: &mut WindowContext, + ) -> Task> { + self.update(cx, |item, cx| item.save(format, project, cx)) } fn save_as( @@ -1018,6 +1033,7 @@ pub mod test { fn save( &mut self, + _: bool, _: Model, _: &mut ViewContext, ) -> Task> { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 58a2ee6241..e99bb3f193 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -44,6 +44,8 @@ pub enum SaveIntent { /// write all files (even if unchanged) /// prompt before overwriting on-disk changes Save, + /// same as Save, but without auto formatting + SaveWithoutFormat, /// write any files that have local changes /// prompt before overwriting on-disk changes SaveAll, @@ -1122,7 +1124,7 @@ impl Pane { })?; // when saving a single buffer, we ignore whether or not it's dirty. - if save_intent == SaveIntent::Save { + if save_intent == SaveIntent::Save || save_intent == SaveIntent::SaveWithoutFormat { is_dirty = true; } @@ -1136,6 +1138,8 @@ impl Pane { has_conflict = false; } + let should_format = save_intent != SaveIntent::SaveWithoutFormat; + if has_conflict && can_save { let answer = pane.update(cx, |pane, cx| { pane.activate_item(item_ix, true, true, cx); @@ -1147,7 +1151,10 @@ impl Pane { ) })?; match answer.await { - Ok(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?, + Ok(0) => { + pane.update(cx, |_, cx| item.save(should_format, project, cx))? + .await? + } Ok(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?, _ => return Ok(false), } @@ -1179,7 +1186,8 @@ impl Pane { } if can_save { - pane.update(cx, |_, cx| item.save(project, cx))?.await?; + pane.update(cx, |_, cx| item.save(should_format, project, cx))? + .await?; } else if can_save_as { let start_abs_path = project .update(cx, |project, cx| { @@ -1211,7 +1219,7 @@ impl Pane { cx: &mut WindowContext, ) -> Task> { if Self::can_autosave_item(item, cx) { - item.save(project, cx) + item.save(true, project, cx) } else { Task::ready(Ok(())) } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5acfd988b9..390b36a7f0 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -106,6 +106,7 @@ actions!( AddFolderToProject, Unfollow, SaveAs, + SaveWithoutFormat, ReloadActiveItem, ActivatePreviousPane, ActivateNextPane, @@ -3532,6 +3533,11 @@ impl Workspace { .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx) .detach_and_log_err(cx); })) + .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, cx| { + workspace + .save_active_item(SaveIntent::SaveWithoutFormat, cx) + .detach_and_log_err(cx); + })) .on_action(cx.listener(|workspace, _: &SaveAs, cx| { workspace .save_active_item(SaveIntent::SaveAs, cx) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 3954015907..7c47a6b6b5 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1979,7 +1979,7 @@ mod tests { editor.newline(&Default::default(), cx); editor.move_down(&Default::default(), cx); editor.move_down(&Default::default(), cx); - editor.save(project.clone(), cx) + editor.save(true, project.clone(), cx) }) }) .unwrap()