mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 18:41:56 +03:00
Add "code_actions_on_format"
(#7860)
This lets Go programmers configure `"code_actions_on_format": { "source.organizeImports": true, }` so that they don't have to manage their imports manually I landed on `code_actions_on_format` instead of `code_actions_on_save` (the VSCode version of this) because I want to run these when I explicitly format (and not if `format_on_save` is disabled). Co-Authored-By: Thorsten <thorsten@zed.dev> Release Notes: - Added `"code_actions_on_format"` to control additional formatting steps on format/save ([#5232](https://github.com/zed-industries/zed/issues/5232)). - Added a `"code_actions_on_format"` of `"source.organizeImports"` for Go ([#4886](https://github.com/zed-industries/zed/issues/4886)). Co-authored-by: Thorsten <thorsten@zed.dev>
This commit is contained in:
parent
e1ae0d46da
commit
ea322e1d1c
@ -482,6 +482,7 @@
|
|||||||
"deno": {
|
"deno": {
|
||||||
"enable": false
|
"enable": false
|
||||||
},
|
},
|
||||||
|
"code_actions_on_format": {},
|
||||||
// Different settings for specific languages.
|
// Different settings for specific languages.
|
||||||
"languages": {
|
"languages": {
|
||||||
"Plain Text": {
|
"Plain Text": {
|
||||||
@ -492,7 +493,10 @@
|
|||||||
},
|
},
|
||||||
"Go": {
|
"Go": {
|
||||||
"tab_size": 4,
|
"tab_size": 4,
|
||||||
"hard_tabs": true
|
"hard_tabs": true,
|
||||||
|
"code_actions_on_format": {
|
||||||
|
"source.organizeImports": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Markdown": {
|
"Markdown": {
|
||||||
"soft_wrap": "preferred_line_length"
|
"soft_wrap": "preferred_line_length"
|
||||||
|
@ -704,10 +704,12 @@ impl Item for Editor {
|
|||||||
|
|
||||||
fn save(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
|
fn save(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
|
||||||
self.report_editor_event("save", None, cx);
|
self.report_editor_event("save", None, cx);
|
||||||
let format = self.perform_format(project.clone(), FormatTrigger::Save, cx);
|
|
||||||
let buffers = self.buffer().clone().read(cx).all_buffers();
|
let buffers = self.buffer().clone().read(cx).all_buffers();
|
||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
format.await?;
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.perform_format(project.clone(), FormatTrigger::Save, cx)
|
||||||
|
})?
|
||||||
|
.await?;
|
||||||
|
|
||||||
if buffers.len() == 1 {
|
if buffers.len() == 1 {
|
||||||
project
|
project
|
||||||
|
@ -93,6 +93,8 @@ pub struct LanguageSettings {
|
|||||||
pub inlay_hints: InlayHintSettings,
|
pub inlay_hints: InlayHintSettings,
|
||||||
/// Whether to automatically close brackets.
|
/// Whether to automatically close brackets.
|
||||||
pub use_autoclose: bool,
|
pub use_autoclose: bool,
|
||||||
|
/// Which code actions to run on save
|
||||||
|
pub code_actions_on_format: HashMap<String, bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The settings for [GitHub Copilot](https://github.com/features/copilot).
|
/// The settings for [GitHub Copilot](https://github.com/features/copilot).
|
||||||
@ -215,6 +217,11 @@ pub struct LanguageSettingsContent {
|
|||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
pub use_autoclose: Option<bool>,
|
pub use_autoclose: Option<bool>,
|
||||||
|
|
||||||
|
/// Which code actions to run on save
|
||||||
|
///
|
||||||
|
/// Default: {} (or {"source.organizeImports": true} for Go).
|
||||||
|
pub code_actions_on_format: Option<HashMap<String, bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The contents of the GitHub Copilot settings.
|
/// The contents of the GitHub Copilot settings.
|
||||||
@ -550,6 +557,10 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
|
|||||||
merge(&mut settings.use_autoclose, src.use_autoclose);
|
merge(&mut settings.use_autoclose, src.use_autoclose);
|
||||||
merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
|
merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
|
||||||
merge(&mut settings.wrap_guides, src.wrap_guides.clone());
|
merge(&mut settings.wrap_guides, src.wrap_guides.clone());
|
||||||
|
merge(
|
||||||
|
&mut settings.code_actions_on_format,
|
||||||
|
src.code_actions_on_format.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
merge(
|
merge(
|
||||||
&mut settings.preferred_line_length,
|
&mut settings.preferred_line_length,
|
||||||
|
@ -123,6 +123,7 @@ pub(crate) struct GetCompletions {
|
|||||||
|
|
||||||
pub(crate) struct GetCodeActions {
|
pub(crate) struct GetCodeActions {
|
||||||
pub range: Range<Anchor>,
|
pub range: Range<Anchor>,
|
||||||
|
pub kinds: Option<Vec<lsp::CodeActionKind>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct OnTypeFormatting {
|
pub(crate) struct OnTypeFormatting {
|
||||||
@ -1603,7 +1604,10 @@ impl LspCommand for GetCodeActions {
|
|||||||
partial_result_params: Default::default(),
|
partial_result_params: Default::default(),
|
||||||
context: lsp::CodeActionContext {
|
context: lsp::CodeActionContext {
|
||||||
diagnostics: relevant_diagnostics,
|
diagnostics: relevant_diagnostics,
|
||||||
only: language_server.code_action_kinds(),
|
only: self
|
||||||
|
.kinds
|
||||||
|
.clone()
|
||||||
|
.or_else(|| language_server.code_action_kinds()),
|
||||||
..lsp::CodeActionContext::default()
|
..lsp::CodeActionContext::default()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1664,7 +1668,10 @@ impl LspCommand for GetCodeActions {
|
|||||||
})?
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Self { range: start..end })
|
Ok(Self {
|
||||||
|
range: start..end,
|
||||||
|
kinds: None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response_to_proto(
|
fn response_to_proto(
|
||||||
|
@ -4150,10 +4150,11 @@ impl Project {
|
|||||||
let buffer = buffer_handle.read(cx);
|
let buffer = buffer_handle.read(cx);
|
||||||
let file = File::from_dyn(buffer.file())?;
|
let file = File::from_dyn(buffer.file())?;
|
||||||
let buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
|
let buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
|
||||||
let server = self
|
let (adapter, server) = self
|
||||||
.primary_language_server_for_buffer(buffer, cx)
|
.primary_language_server_for_buffer(buffer, cx)
|
||||||
.map(|s| s.1.clone());
|
.map(|(a, s)| (Some(a.clone()), Some(s.clone())))
|
||||||
Some((buffer_handle, buffer_abs_path, server))
|
.unwrap_or((None, None));
|
||||||
|
Some((buffer_handle, buffer_abs_path, adapter, server))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@ -4161,7 +4162,7 @@ impl Project {
|
|||||||
// Do not allow multiple concurrent formatting requests for the
|
// Do not allow multiple concurrent formatting requests for the
|
||||||
// same buffer.
|
// same buffer.
|
||||||
project.update(&mut cx, |this, cx| {
|
project.update(&mut cx, |this, cx| {
|
||||||
buffers_with_paths_and_servers.retain(|(buffer, _, _)| {
|
buffers_with_paths_and_servers.retain(|(buffer, _, _, _)| {
|
||||||
this.buffers_being_formatted
|
this.buffers_being_formatted
|
||||||
.insert(buffer.read(cx).remote_id())
|
.insert(buffer.read(cx).remote_id())
|
||||||
});
|
});
|
||||||
@ -4173,7 +4174,7 @@ impl Project {
|
|||||||
let buffers = &buffers_with_paths_and_servers;
|
let buffers = &buffers_with_paths_and_servers;
|
||||||
move || {
|
move || {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
for (buffer, _, _) in buffers {
|
for (buffer, _, _, _) in buffers {
|
||||||
this.buffers_being_formatted
|
this.buffers_being_formatted
|
||||||
.remove(&buffer.read(cx).remote_id());
|
.remove(&buffer.read(cx).remote_id());
|
||||||
}
|
}
|
||||||
@ -4183,7 +4184,9 @@ impl Project {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let mut project_transaction = ProjectTransaction::default();
|
let mut project_transaction = ProjectTransaction::default();
|
||||||
for (buffer, buffer_abs_path, language_server) in &buffers_with_paths_and_servers {
|
for (buffer, buffer_abs_path, lsp_adapter, language_server) in
|
||||||
|
&buffers_with_paths_and_servers
|
||||||
|
{
|
||||||
let settings = buffer.update(&mut cx, |buffer, cx| {
|
let settings = buffer.update(&mut cx, |buffer, cx| {
|
||||||
language_settings(buffer.language(), buffer.file(), cx).clone()
|
language_settings(buffer.language(), buffer.file(), cx).clone()
|
||||||
})?;
|
})?;
|
||||||
@ -4214,6 +4217,88 @@ impl Project {
|
|||||||
buffer.end_transaction(cx)
|
buffer.end_transaction(cx)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
if let (Some(lsp_adapter), Some(language_server)) =
|
||||||
|
(lsp_adapter, language_server)
|
||||||
|
{
|
||||||
|
// Apply the code actions on
|
||||||
|
let code_actions: Vec<lsp::CodeActionKind> = settings
|
||||||
|
.code_actions_on_format
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(kind, enabled)| {
|
||||||
|
if *enabled {
|
||||||
|
Some(kind.clone().into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !code_actions.is_empty()
|
||||||
|
&& !(trigger == FormatTrigger::Save
|
||||||
|
&& settings.format_on_save == FormatOnSave::Off)
|
||||||
|
{
|
||||||
|
let actions = project
|
||||||
|
.update(&mut cx, |this, cx| {
|
||||||
|
this.request_lsp(
|
||||||
|
buffer.clone(),
|
||||||
|
LanguageServerToQuery::Other(language_server.server_id()),
|
||||||
|
GetCodeActions {
|
||||||
|
range: text::Anchor::MIN..text::Anchor::MAX,
|
||||||
|
kinds: Some(code_actions),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for action in actions {
|
||||||
|
if let Some(edit) = action.lsp_action.edit {
|
||||||
|
if edit.changes.is_none() && edit.document_changes.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let new = Self::deserialize_workspace_edit(
|
||||||
|
project
|
||||||
|
.upgrade()
|
||||||
|
.ok_or_else(|| anyhow!("project dropped"))?,
|
||||||
|
edit,
|
||||||
|
push_to_history,
|
||||||
|
lsp_adapter.clone(),
|
||||||
|
language_server.clone(),
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
project_transaction.0.extend(new.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(command) = action.lsp_action.command {
|
||||||
|
project.update(&mut cx, |this, _| {
|
||||||
|
this.last_workspace_edits_by_language_server
|
||||||
|
.remove(&language_server.server_id());
|
||||||
|
})?;
|
||||||
|
|
||||||
|
language_server
|
||||||
|
.request::<lsp::request::ExecuteCommand>(
|
||||||
|
lsp::ExecuteCommandParams {
|
||||||
|
command: command.command,
|
||||||
|
arguments: command.arguments.unwrap_or_default(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
project.update(&mut cx, |this, _| {
|
||||||
|
project_transaction.0.extend(
|
||||||
|
this.last_workspace_edits_by_language_server
|
||||||
|
.remove(&language_server.server_id())
|
||||||
|
.unwrap_or_default()
|
||||||
|
.0,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Apply language-specific formatting using either a language server
|
// Apply language-specific formatting using either a language server
|
||||||
// or external command.
|
// or external command.
|
||||||
let mut format_operation = None;
|
let mut format_operation = None;
|
||||||
@ -4323,6 +4408,8 @@ impl Project {
|
|||||||
|
|
||||||
if let Some(transaction_id) = whitespace_transaction_id {
|
if let Some(transaction_id) = whitespace_transaction_id {
|
||||||
b.group_until_transaction(transaction_id);
|
b.group_until_transaction(transaction_id);
|
||||||
|
} else if let Some(transaction) = project_transaction.0.get(buffer) {
|
||||||
|
b.group_until_transaction(transaction.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5162,7 +5249,7 @@ impl Project {
|
|||||||
self.request_lsp(
|
self.request_lsp(
|
||||||
buffer_handle.clone(),
|
buffer_handle.clone(),
|
||||||
LanguageServerToQuery::Primary,
|
LanguageServerToQuery::Primary,
|
||||||
GetCodeActions { range },
|
GetCodeActions { range, kinds: None },
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -5178,6 +5265,103 @@ impl Project {
|
|||||||
self.code_actions_impl(buffer_handle, range, cx)
|
self.code_actions_impl(buffer_handle, range, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_code_actions_on_save(
|
||||||
|
&self,
|
||||||
|
buffers: HashSet<Model<Buffer>>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Task<Result<ProjectTransaction>> {
|
||||||
|
if !self.is_local() {
|
||||||
|
return Task::ready(Ok(Default::default()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffers_with_adapters_and_servers = buffers
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|buffer_handle| {
|
||||||
|
let buffer = buffer_handle.read(cx);
|
||||||
|
self.primary_language_server_for_buffer(buffer, cx)
|
||||||
|
.map(|(a, s)| (buffer_handle, a.clone(), s.clone()))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
cx.spawn(move |this, mut cx| async move {
|
||||||
|
for (buffer_handle, lsp_adapter, lang_server) in buffers_with_adapters_and_servers {
|
||||||
|
let actions = this
|
||||||
|
.update(&mut cx, |this, cx| {
|
||||||
|
let buffer = buffer_handle.read(cx);
|
||||||
|
let kinds: Vec<lsp::CodeActionKind> =
|
||||||
|
language_settings(buffer.language(), buffer.file(), cx)
|
||||||
|
.code_actions_on_format
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(kind, enabled)| {
|
||||||
|
if *enabled {
|
||||||
|
Some(kind.clone().into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if kinds.is_empty() {
|
||||||
|
return Task::ready(Ok(vec![]));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.request_lsp(
|
||||||
|
buffer_handle.clone(),
|
||||||
|
LanguageServerToQuery::Other(lang_server.server_id()),
|
||||||
|
GetCodeActions {
|
||||||
|
range: text::Anchor::MIN..text::Anchor::MAX,
|
||||||
|
kinds: Some(kinds),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for action in actions {
|
||||||
|
if let Some(edit) = action.lsp_action.edit {
|
||||||
|
if edit.changes.is_some() || edit.document_changes.is_some() {
|
||||||
|
return Self::deserialize_workspace_edit(
|
||||||
|
this.upgrade().ok_or_else(|| anyhow!("no app present"))?,
|
||||||
|
edit,
|
||||||
|
true,
|
||||||
|
lsp_adapter.clone(),
|
||||||
|
lang_server.clone(),
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(command) = action.lsp_action.command {
|
||||||
|
this.update(&mut cx, |this, _| {
|
||||||
|
this.last_workspace_edits_by_language_server
|
||||||
|
.remove(&lang_server.server_id());
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let result = lang_server
|
||||||
|
.request::<lsp::request::ExecuteCommand>(lsp::ExecuteCommandParams {
|
||||||
|
command: command.command,
|
||||||
|
arguments: command.arguments.unwrap_or_default(),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(err) = result {
|
||||||
|
// TODO: LSP ERROR
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(this.update(&mut cx, |this, _| {
|
||||||
|
this.last_workspace_edits_by_language_server
|
||||||
|
.remove(&lang_server.server_id())
|
||||||
|
.unwrap_or_default()
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ProjectTransaction::default())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_code_action(
|
pub fn apply_code_action(
|
||||||
&self,
|
&self,
|
||||||
buffer_handle: Model<Buffer>,
|
buffer_handle: Model<Buffer>,
|
||||||
|
Loading…
Reference in New Issue
Block a user