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.
This commit is contained in:
parent
ff65008316
commit
20acc123af
10 changed files with 69 additions and 24 deletions
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -735,8 +735,13 @@ impl Item for ProjectDiagnosticsEditor {
|
|||
true
|
||||
}
|
||||
|
||||
fn save(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
|
||||
self.editor.save(project, cx)
|
||||
fn save(
|
||||
&mut self,
|
||||
format: bool,
|
||||
project: Model<Project>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.editor.save(format, project, cx)
|
||||
}
|
||||
|
||||
fn save_as(
|
||||
|
|
|
@ -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::<lsp::request::Formatting, _, _>(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::<lsp::request::Formatting, _, _>(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::<lsp::request::RangeFormatting, _, _>(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::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
|
||||
|
|
|
@ -702,14 +702,21 @@ impl Item for Editor {
|
|||
}
|
||||
}
|
||||
|
||||
fn save(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
|
||||
fn save(
|
||||
&mut self,
|
||||
format: bool,
|
||||
project: Model<Project>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
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
|
||||
|
|
|
@ -539,11 +539,12 @@ impl Item for ProjectSearchView {
|
|||
|
||||
fn save(
|
||||
&mut self,
|
||||
format: bool,
|
||||
project: Model<Project>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
self.results_editor
|
||||
.update(cx, |editor, cx| editor.save(project, cx))
|
||||
.update(cx, |editor, cx| editor.save(format, project, cx))
|
||||
}
|
||||
|
||||
fn save_as(
|
||||
|
|
|
@ -146,7 +146,12 @@ pub trait Item: FocusableView + EventEmitter<Self::Event> {
|
|||
fn can_save(&self, _cx: &AppContext) -> bool {
|
||||
false
|
||||
}
|
||||
fn save(&mut self, _project: Model<Project>, _cx: &mut ViewContext<Self>) -> Task<Result<()>> {
|
||||
fn save(
|
||||
&mut self,
|
||||
_format: bool,
|
||||
_project: Model<Project>,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
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<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
|
||||
fn save(
|
||||
&self,
|
||||
format: bool,
|
||||
project: Model<Project>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<()>>;
|
||||
fn save_as(
|
||||
&self,
|
||||
project: Model<Project>,
|
||||
|
@ -566,8 +576,13 @@ impl<T: Item> ItemHandle for View<T> {
|
|||
self.read(cx).can_save(cx)
|
||||
}
|
||||
|
||||
fn save(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
|
||||
self.update(cx, |item, cx| item.save(project, cx))
|
||||
fn save(
|
||||
&self,
|
||||
format: bool,
|
||||
project: Model<Project>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<()>> {
|
||||
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<Project>,
|
||||
_: &mut ViewContext<Self>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
|
|
|
@ -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<Result<()>> {
|
||||
if Self::can_autosave_item(item, cx) {
|
||||
item.save(project, cx)
|
||||
item.save(true, project, cx)
|
||||
} else {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue