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-alt-b": "branches::OpenRecent",
|
||||||
"ctrl-~": "workspace::NewTerminal",
|
"ctrl-~": "workspace::NewTerminal",
|
||||||
"ctrl-s": "workspace::Save",
|
"ctrl-s": "workspace::Save",
|
||||||
|
"ctrl-k s": "workspace::SaveWithoutFormat",
|
||||||
"ctrl-shift-s": "workspace::SaveAs",
|
"ctrl-shift-s": "workspace::SaveAs",
|
||||||
"ctrl-n": "workspace::NewFile",
|
"ctrl-n": "workspace::NewFile",
|
||||||
"ctrl-shift-n": "workspace::NewWindow",
|
"ctrl-shift-n": "workspace::NewWindow",
|
||||||
|
|
|
@ -408,6 +408,7 @@
|
||||||
"alt-cmd-b": "branches::OpenRecent",
|
"alt-cmd-b": "branches::OpenRecent",
|
||||||
"ctrl-~": "workspace::NewTerminal",
|
"ctrl-~": "workspace::NewTerminal",
|
||||||
"cmd-s": "workspace::Save",
|
"cmd-s": "workspace::Save",
|
||||||
|
"cmd-k s": "workspace::SaveWithoutFormat",
|
||||||
"cmd-shift-s": "workspace::SaveAs",
|
"cmd-shift-s": "workspace::SaveAs",
|
||||||
"cmd-n": "workspace::NewFile",
|
"cmd-n": "workspace::NewFile",
|
||||||
"cmd-shift-n": "workspace::NewWindow",
|
"cmd-shift-n": "workspace::NewWindow",
|
||||||
|
@ -426,8 +427,8 @@
|
||||||
"cmd-j": "workspace::ToggleBottomDock",
|
"cmd-j": "workspace::ToggleBottomDock",
|
||||||
"alt-cmd-y": "workspace::CloseAllDocks",
|
"alt-cmd-y": "workspace::CloseAllDocks",
|
||||||
"cmd-shift-f": "pane::DeploySearch",
|
"cmd-shift-f": "pane::DeploySearch",
|
||||||
"cmd-k cmd-t": "theme_selector::Toggle",
|
|
||||||
"cmd-k cmd-s": "zed::OpenKeymap",
|
"cmd-k cmd-s": "zed::OpenKeymap",
|
||||||
|
"cmd-k cmd-t": "theme_selector::Toggle",
|
||||||
"cmd-t": "project_symbols::Toggle",
|
"cmd-t": "project_symbols::Toggle",
|
||||||
"cmd-p": "file_finder::Toggle",
|
"cmd-p": "file_finder::Toggle",
|
||||||
"cmd-shift-p": "command_palette::Toggle",
|
"cmd-shift-p": "command_palette::Toggle",
|
||||||
|
|
|
@ -735,8 +735,13 @@ impl Item for ProjectDiagnosticsEditor {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
|
fn save(
|
||||||
self.editor.save(project, cx)
|
&mut self,
|
||||||
|
format: bool,
|
||||||
|
project: Model<Project>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Task<Result<()>> {
|
||||||
|
self.editor.save(format, project, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_as(
|
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)));
|
assert!(cx.read(|cx| editor.is_dirty(cx)));
|
||||||
|
|
||||||
let save = editor
|
let save = editor
|
||||||
.update(cx, |editor, cx| editor.save(project.clone(), cx))
|
.update(cx, |editor, cx| editor.save(true, project.clone(), cx))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
fake_server
|
fake_server
|
||||||
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
.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!()
|
unreachable!()
|
||||||
});
|
});
|
||||||
let save = editor
|
let save = editor
|
||||||
.update(cx, |editor, cx| editor.save(project.clone(), cx))
|
.update(cx, |editor, cx| editor.save(true, project.clone(), cx))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cx.executor().advance_clock(super::FORMAT_TIMEOUT);
|
cx.executor().advance_clock(super::FORMAT_TIMEOUT);
|
||||||
cx.executor().start_waiting();
|
cx.executor().start_waiting();
|
||||||
|
@ -5326,7 +5326,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||||
});
|
});
|
||||||
|
|
||||||
let save = editor
|
let save = editor
|
||||||
.update(cx, |editor, cx| editor.save(project.clone(), cx))
|
.update(cx, |editor, cx| editor.save(true, project.clone(), cx))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
fake_server
|
fake_server
|
||||||
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
|
.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)));
|
assert!(cx.read(|cx| editor.is_dirty(cx)));
|
||||||
|
|
||||||
let save = editor
|
let save = editor
|
||||||
.update(cx, |editor, cx| editor.save(project.clone(), cx))
|
.update(cx, |editor, cx| editor.save(true, project.clone(), cx))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
fake_server
|
fake_server
|
||||||
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
|
.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
|
let save = editor
|
||||||
.update(cx, |editor, cx| editor.save(project.clone(), cx))
|
.update(cx, |editor, cx| editor.save(true, project.clone(), cx))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cx.executor().advance_clock(super::FORMAT_TIMEOUT);
|
cx.executor().advance_clock(super::FORMAT_TIMEOUT);
|
||||||
cx.executor().start_waiting();
|
cx.executor().start_waiting();
|
||||||
|
@ -5441,7 +5441,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||||
});
|
});
|
||||||
|
|
||||||
let save = editor
|
let save = editor
|
||||||
.update(cx, |editor, cx| editor.save(project.clone(), cx))
|
.update(cx, |editor, cx| editor.save(true, project.clone(), cx))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
fake_server
|
fake_server
|
||||||
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
|
.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);
|
self.report_editor_event("save", None, cx);
|
||||||
let buffers = self.buffer().clone().read(cx).all_buffers();
|
let buffers = self.buffer().clone().read(cx).all_buffers();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
this.update(&mut cx, |this, cx| {
|
if format {
|
||||||
this.perform_format(project.clone(), FormatTrigger::Save, cx)
|
this.update(&mut cx, |this, cx| {
|
||||||
})?
|
this.perform_format(project.clone(), FormatTrigger::Save, cx)
|
||||||
.await?;
|
})?
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
if buffers.len() == 1 {
|
if buffers.len() == 1 {
|
||||||
project
|
project
|
||||||
|
|
|
@ -539,11 +539,12 @@ impl Item for ProjectSearchView {
|
||||||
|
|
||||||
fn save(
|
fn save(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
format: bool,
|
||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<anyhow::Result<()>> {
|
) -> Task<anyhow::Result<()>> {
|
||||||
self.results_editor
|
self.results_editor
|
||||||
.update(cx, |editor, cx| editor.save(project, cx))
|
.update(cx, |editor, cx| editor.save(format, project, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_as(
|
fn save_as(
|
||||||
|
|
|
@ -146,7 +146,12 @@ pub trait Item: FocusableView + EventEmitter<Self::Event> {
|
||||||
fn can_save(&self, _cx: &AppContext) -> bool {
|
fn can_save(&self, _cx: &AppContext) -> bool {
|
||||||
false
|
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")
|
unimplemented!("save() must be implemented if can_save() returns true")
|
||||||
}
|
}
|
||||||
fn save_as(
|
fn save_as(
|
||||||
|
@ -258,7 +263,12 @@ pub trait ItemHandle: 'static + Send {
|
||||||
fn is_dirty(&self, cx: &AppContext) -> bool;
|
fn is_dirty(&self, cx: &AppContext) -> bool;
|
||||||
fn has_conflict(&self, cx: &AppContext) -> bool;
|
fn has_conflict(&self, cx: &AppContext) -> bool;
|
||||||
fn can_save(&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(
|
fn save_as(
|
||||||
&self,
|
&self,
|
||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
|
@ -566,8 +576,13 @@ impl<T: Item> ItemHandle for View<T> {
|
||||||
self.read(cx).can_save(cx)
|
self.read(cx).can_save(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
|
fn save(
|
||||||
self.update(cx, |item, cx| item.save(project, cx))
|
&self,
|
||||||
|
format: bool,
|
||||||
|
project: Model<Project>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Task<Result<()>> {
|
||||||
|
self.update(cx, |item, cx| item.save(format, project, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_as(
|
fn save_as(
|
||||||
|
@ -1018,6 +1033,7 @@ pub mod test {
|
||||||
|
|
||||||
fn save(
|
fn save(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
_: bool,
|
||||||
_: Model<Project>,
|
_: Model<Project>,
|
||||||
_: &mut ViewContext<Self>,
|
_: &mut ViewContext<Self>,
|
||||||
) -> Task<anyhow::Result<()>> {
|
) -> Task<anyhow::Result<()>> {
|
||||||
|
|
|
@ -44,6 +44,8 @@ pub enum SaveIntent {
|
||||||
/// write all files (even if unchanged)
|
/// write all files (even if unchanged)
|
||||||
/// prompt before overwriting on-disk changes
|
/// prompt before overwriting on-disk changes
|
||||||
Save,
|
Save,
|
||||||
|
/// same as Save, but without auto formatting
|
||||||
|
SaveWithoutFormat,
|
||||||
/// write any files that have local changes
|
/// write any files that have local changes
|
||||||
/// prompt before overwriting on-disk changes
|
/// prompt before overwriting on-disk changes
|
||||||
SaveAll,
|
SaveAll,
|
||||||
|
@ -1122,7 +1124,7 @@ impl Pane {
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// when saving a single buffer, we ignore whether or not it's dirty.
|
// 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;
|
is_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1136,6 +1138,8 @@ impl Pane {
|
||||||
has_conflict = false;
|
has_conflict = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let should_format = save_intent != SaveIntent::SaveWithoutFormat;
|
||||||
|
|
||||||
if has_conflict && can_save {
|
if has_conflict && can_save {
|
||||||
let answer = pane.update(cx, |pane, cx| {
|
let answer = pane.update(cx, |pane, cx| {
|
||||||
pane.activate_item(item_ix, true, true, cx);
|
pane.activate_item(item_ix, true, true, cx);
|
||||||
|
@ -1147,7 +1151,10 @@ impl Pane {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
match answer.await {
|
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?,
|
Ok(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
|
||||||
_ => return Ok(false),
|
_ => return Ok(false),
|
||||||
}
|
}
|
||||||
|
@ -1179,7 +1186,8 @@ impl Pane {
|
||||||
}
|
}
|
||||||
|
|
||||||
if can_save {
|
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 {
|
} else if can_save_as {
|
||||||
let start_abs_path = project
|
let start_abs_path = project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
|
@ -1211,7 +1219,7 @@ impl Pane {
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
if Self::can_autosave_item(item, cx) {
|
if Self::can_autosave_item(item, cx) {
|
||||||
item.save(project, cx)
|
item.save(true, project, cx)
|
||||||
} else {
|
} else {
|
||||||
Task::ready(Ok(()))
|
Task::ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,7 @@ actions!(
|
||||||
AddFolderToProject,
|
AddFolderToProject,
|
||||||
Unfollow,
|
Unfollow,
|
||||||
SaveAs,
|
SaveAs,
|
||||||
|
SaveWithoutFormat,
|
||||||
ReloadActiveItem,
|
ReloadActiveItem,
|
||||||
ActivatePreviousPane,
|
ActivatePreviousPane,
|
||||||
ActivateNextPane,
|
ActivateNextPane,
|
||||||
|
@ -3532,6 +3533,11 @@ impl Workspace {
|
||||||
.save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
|
.save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
|
||||||
.detach_and_log_err(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| {
|
.on_action(cx.listener(|workspace, _: &SaveAs, cx| {
|
||||||
workspace
|
workspace
|
||||||
.save_active_item(SaveIntent::SaveAs, cx)
|
.save_active_item(SaveIntent::SaveAs, cx)
|
||||||
|
|
|
@ -1979,7 +1979,7 @@ mod tests {
|
||||||
editor.newline(&Default::default(), cx);
|
editor.newline(&Default::default(), cx);
|
||||||
editor.move_down(&Default::default(), cx);
|
editor.move_down(&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()
|
.unwrap()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue