mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-25 20:11:49 +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": {
|
||||
"enable": false
|
||||
},
|
||||
"code_actions_on_format": {},
|
||||
// Different settings for specific languages.
|
||||
"languages": {
|
||||
"Plain Text": {
|
||||
@ -492,7 +493,10 @@
|
||||
},
|
||||
"Go": {
|
||||
"tab_size": 4,
|
||||
"hard_tabs": true
|
||||
"hard_tabs": true,
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"Markdown": {
|
||||
"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<()>> {
|
||||
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();
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
format.await?;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.perform_format(project.clone(), FormatTrigger::Save, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
if buffers.len() == 1 {
|
||||
project
|
||||
|
@ -93,6 +93,8 @@ pub struct LanguageSettings {
|
||||
pub inlay_hints: InlayHintSettings,
|
||||
/// Whether to automatically close brackets.
|
||||
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).
|
||||
@ -215,6 +217,11 @@ pub struct LanguageSettingsContent {
|
||||
///
|
||||
/// Default: true
|
||||
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.
|
||||
@ -550,6 +557,10 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
|
||||
merge(&mut settings.use_autoclose, src.use_autoclose);
|
||||
merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
|
||||
merge(&mut settings.wrap_guides, src.wrap_guides.clone());
|
||||
merge(
|
||||
&mut settings.code_actions_on_format,
|
||||
src.code_actions_on_format.clone(),
|
||||
);
|
||||
|
||||
merge(
|
||||
&mut settings.preferred_line_length,
|
||||
|
@ -123,6 +123,7 @@ pub(crate) struct GetCompletions {
|
||||
|
||||
pub(crate) struct GetCodeActions {
|
||||
pub range: Range<Anchor>,
|
||||
pub kinds: Option<Vec<lsp::CodeActionKind>>,
|
||||
}
|
||||
|
||||
pub(crate) struct OnTypeFormatting {
|
||||
@ -1603,7 +1604,10 @@ impl LspCommand for GetCodeActions {
|
||||
partial_result_params: Default::default(),
|
||||
context: lsp::CodeActionContext {
|
||||
diagnostics: relevant_diagnostics,
|
||||
only: language_server.code_action_kinds(),
|
||||
only: self
|
||||
.kinds
|
||||
.clone()
|
||||
.or_else(|| language_server.code_action_kinds()),
|
||||
..lsp::CodeActionContext::default()
|
||||
},
|
||||
}
|
||||
@ -1664,7 +1668,10 @@ impl LspCommand for GetCodeActions {
|
||||
})?
|
||||
.await?;
|
||||
|
||||
Ok(Self { range: start..end })
|
||||
Ok(Self {
|
||||
range: start..end,
|
||||
kinds: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn response_to_proto(
|
||||
|
@ -4150,10 +4150,11 @@ impl Project {
|
||||
let buffer = buffer_handle.read(cx);
|
||||
let file = File::from_dyn(buffer.file())?;
|
||||
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)
|
||||
.map(|s| s.1.clone());
|
||||
Some((buffer_handle, buffer_abs_path, server))
|
||||
.map(|(a, s)| (Some(a.clone()), Some(s.clone())))
|
||||
.unwrap_or((None, None));
|
||||
Some((buffer_handle, buffer_abs_path, adapter, server))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@ -4161,7 +4162,7 @@ impl Project {
|
||||
// Do not allow multiple concurrent formatting requests for the
|
||||
// same buffer.
|
||||
project.update(&mut cx, |this, cx| {
|
||||
buffers_with_paths_and_servers.retain(|(buffer, _, _)| {
|
||||
buffers_with_paths_and_servers.retain(|(buffer, _, _, _)| {
|
||||
this.buffers_being_formatted
|
||||
.insert(buffer.read(cx).remote_id())
|
||||
});
|
||||
@ -4173,7 +4174,7 @@ impl Project {
|
||||
let buffers = &buffers_with_paths_and_servers;
|
||||
move || {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for (buffer, _, _) in buffers {
|
||||
for (buffer, _, _, _) in buffers {
|
||||
this.buffers_being_formatted
|
||||
.remove(&buffer.read(cx).remote_id());
|
||||
}
|
||||
@ -4183,7 +4184,9 @@ impl Project {
|
||||
});
|
||||
|
||||
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| {
|
||||
language_settings(buffer.language(), buffer.file(), cx).clone()
|
||||
})?;
|
||||
@ -4214,6 +4217,88 @@ impl Project {
|
||||
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
|
||||
// or external command.
|
||||
let mut format_operation = None;
|
||||
@ -4323,6 +4408,8 @@ impl Project {
|
||||
|
||||
if let Some(transaction_id) = whitespace_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(
|
||||
buffer_handle.clone(),
|
||||
LanguageServerToQuery::Primary,
|
||||
GetCodeActions { range },
|
||||
GetCodeActions { range, kinds: None },
|
||||
cx,
|
||||
)
|
||||
}
|
||||
@ -5178,6 +5265,103 @@ impl Project {
|
||||
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(
|
||||
&self,
|
||||
buffer_handle: Model<Buffer>,
|
||||
|
Loading…
Reference in New Issue
Block a user