Add code_actions
as formatter
type (#10121)
This fixes #8992 and solves a problem that ESLint/Prettier/... users have been running into: They want to format _only_ with ESLint, which is *not* a primary language server (so `formatter: language server` does not help) and it is not a formatter. What they want to use is what they get when they have configured something like this: ```json { "languages": { "JavaScript": { "code_actions_on_format": { "source.fixAll.eslint": true } } } } ``` BUT they don't want to run the formatter. So what this PR does is to add a new formatter type: `code_actions`. With that, users can only use code actions to format: ```json { "languages": { "JavaScript": { "formatter": { "code_actions": { "source.fixAll.eslint": true } } } } } ``` This means that when formatting (via `editor: format` or on-save) only the code actions that are specified are being executed, no formatter. Release Notes: - Added a new `formatter`/`format_on_save` option: `code_actions`. When configured, this uses language server code actions to format a buffer. This can be used if one wants to, for example, format a buffer with ESLint and *not* run prettier or another formatter afterwards. Example configuration: `{"languages": {"JavaScript": {"formatter": {"code_actions": {"source.fixAll.eslint": true}}}}}` ([#8992](https://github.com/zed-industries/zed/issues/8992)). --------- Co-authored-by: JH Chabran <jh@chabran.fr> Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
parent
654504d5ee
commit
eb231d0449
@ -241,7 +241,8 @@ pub struct LanguageSettingsContent {
|
||||
///
|
||||
/// Default: false
|
||||
pub always_treat_brackets_as_autoclosed: Option<bool>,
|
||||
/// Which code actions to run on save
|
||||
/// Which code actions to run on save after the formatter.
|
||||
/// These are not run if formatting is off.
|
||||
///
|
||||
/// Default: {} (or {"source.organizeImports": true} for Go).
|
||||
pub code_actions_on_format: Option<HashMap<String, bool>>,
|
||||
@ -292,6 +293,8 @@ pub enum FormatOnSave {
|
||||
/// The arguments to pass to the program.
|
||||
arguments: Arc<[String]>,
|
||||
},
|
||||
/// Files should be formatted using code actions executed by language servers.
|
||||
CodeActions(HashMap<String, bool>),
|
||||
}
|
||||
|
||||
/// Controls how whitespace should be displayedin the editor.
|
||||
@ -325,6 +328,8 @@ pub enum Formatter {
|
||||
/// The arguments to pass to the program.
|
||||
arguments: Arc<[String]>,
|
||||
},
|
||||
/// Files should be formatted using code actions executed by language servers.
|
||||
CodeActions(HashMap<String, bool>),
|
||||
}
|
||||
|
||||
/// The settings for inlay hints.
|
||||
|
@ -31,7 +31,9 @@ pub fn prettier_plugins_for_language<'a>(
|
||||
) -> Option<&'a Vec<Arc<str>>> {
|
||||
match &language_settings.formatter {
|
||||
Formatter::Prettier { .. } | Formatter::Auto => {}
|
||||
Formatter::LanguageServer | Formatter::External { .. } => return None,
|
||||
Formatter::LanguageServer | Formatter::External { .. } | Formatter::CodeActions(_) => {
|
||||
return None
|
||||
}
|
||||
};
|
||||
if language.prettier_parser_name().is_some() {
|
||||
Some(language.prettier_plugins())
|
||||
|
@ -4539,93 +4539,27 @@ impl Project {
|
||||
buffer.end_transaction(cx)
|
||||
})?;
|
||||
|
||||
for (lsp_adapter, language_server) in adapters_and_servers.iter() {
|
||||
// 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();
|
||||
|
||||
#[allow(clippy::nonminimal_bool)]
|
||||
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 mut action in actions {
|
||||
Self::try_resolve_code_action(&language_server, &mut action)
|
||||
.await
|
||||
.context("resolving a formatting code action")?;
|
||||
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 the `code_actions_on_format` before we run the formatter.
|
||||
let code_actions = deserialize_code_actions(&settings.code_actions_on_format);
|
||||
#[allow(clippy::nonminimal_bool)]
|
||||
if !code_actions.is_empty()
|
||||
&& !(trigger == FormatTrigger::Save && settings.format_on_save == FormatOnSave::Off)
|
||||
{
|
||||
Self::execute_code_actions_on_servers(
|
||||
&project,
|
||||
&adapters_and_servers,
|
||||
code_actions,
|
||||
buffer,
|
||||
push_to_history,
|
||||
&mut project_transaction,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Apply language-specific formatting using either the primary language server
|
||||
// or external command.
|
||||
// Except for code actions, which are applied with all connected language servers.
|
||||
let primary_language_server = adapters_and_servers
|
||||
.first()
|
||||
.cloned()
|
||||
@ -4638,6 +4572,22 @@ impl Project {
|
||||
match (&settings.formatter, &settings.format_on_save) {
|
||||
(_, FormatOnSave::Off) if trigger == FormatTrigger::Save => {}
|
||||
|
||||
(Formatter::CodeActions(code_actions), FormatOnSave::On | FormatOnSave::Off)
|
||||
| (_, FormatOnSave::CodeActions(code_actions)) => {
|
||||
let code_actions = deserialize_code_actions(code_actions);
|
||||
if !code_actions.is_empty() {
|
||||
Self::execute_code_actions_on_servers(
|
||||
&project,
|
||||
&adapters_and_servers,
|
||||
code_actions,
|
||||
buffer,
|
||||
push_to_history,
|
||||
&mut project_transaction,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
(Formatter::LanguageServer, FormatOnSave::On | FormatOnSave::Off)
|
||||
| (_, FormatOnSave::LanguageServer) => {
|
||||
if let Some((language_server, buffer_abs_path)) = server_and_buffer {
|
||||
@ -8832,6 +8782,82 @@ impl Project {
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
async fn execute_code_actions_on_servers(
|
||||
project: &WeakModel<Project>,
|
||||
adapters_and_servers: &Vec<(Arc<CachedLspAdapter>, Arc<LanguageServer>)>,
|
||||
code_actions: Vec<lsp::CodeActionKind>,
|
||||
buffer: &Model<Buffer>,
|
||||
push_to_history: bool,
|
||||
project_transaction: &mut ProjectTransaction,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
for (lsp_adapter, language_server) in adapters_and_servers.iter() {
|
||||
let code_actions = code_actions.clone();
|
||||
|
||||
let actions = project
|
||||
.update(cx, move |this, cx| {
|
||||
let request = GetCodeActions {
|
||||
range: text::Anchor::MIN..text::Anchor::MAX,
|
||||
kinds: Some(code_actions),
|
||||
};
|
||||
let server = LanguageServerToQuery::Other(language_server.server_id());
|
||||
this.request_lsp(buffer.clone(), server, request, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
for mut action in actions {
|
||||
Self::try_resolve_code_action(&language_server, &mut action)
|
||||
.await
|
||||
.context("resolving a formatting code action")?;
|
||||
|
||||
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(),
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
project_transaction.0.extend(new.0);
|
||||
}
|
||||
|
||||
if let Some(command) = action.lsp_action.command {
|
||||
project.update(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(cx, |this, _| {
|
||||
project_transaction.0.extend(
|
||||
this.last_workspace_edits_by_language_server
|
||||
.remove(&language_server.server_id())
|
||||
.unwrap_or_default()
|
||||
.0,
|
||||
)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_refresh_inlay_hints(
|
||||
this: Model<Self>,
|
||||
_: TypedEnvelope<proto::RefreshInlayHints>,
|
||||
@ -9671,6 +9697,19 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_code_actions(code_actions: &HashMap<String, bool>) -> Vec<lsp::CodeActionKind> {
|
||||
code_actions
|
||||
.iter()
|
||||
.flat_map(|(kind, enabled)| {
|
||||
if *enabled {
|
||||
Some(kind.clone().into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn search_snapshots(
|
||||
snapshots: &Vec<LocalSnapshot>,
|
||||
|
Loading…
x
Reference in New Issue
Block a user