editor: Add Organize Imports Action (#25793)
Closes #10004 This PR adds support for the organize imports action. Previously, you had to manually configure it in the settings and then use format to run it. Note: Default key binding will be `alt-shift-o` which is similar to VSCode's organize import. Also, because `cmd-shift-o` is taken by outline picker. Todo: - [x] Initial working - [x] Handle remote - [x] Handle multi buffer - [x] Can we make it generic for executing any code action? Release Notes: - Added `editor:OrganizeImports` action to organize imports (sort, remove unused, etc) for supported LSPs. You can trigger it by using the `alt-shift-o` key binding.
This commit is contained in:
parent
e4e758db3a
commit
fad4df5e70
10 changed files with 408 additions and 4 deletions
|
@ -1089,6 +1089,64 @@ impl LocalLspStore {
|
|||
self.language_servers_for_buffer(buffer, cx).next()
|
||||
}
|
||||
|
||||
async fn execute_code_action_kind_locally(
|
||||
lsp_store: WeakEntity<LspStore>,
|
||||
mut buffers: Vec<Entity<Buffer>>,
|
||||
kind: CodeActionKind,
|
||||
push_to_history: bool,
|
||||
mut cx: AsyncApp,
|
||||
) -> anyhow::Result<ProjectTransaction> {
|
||||
// Do not allow multiple concurrent code actions requests for the
|
||||
// same buffer.
|
||||
lsp_store.update(&mut cx, |this, cx| {
|
||||
let this = this.as_local_mut().unwrap();
|
||||
buffers.retain(|buffer| {
|
||||
this.buffers_being_formatted
|
||||
.insert(buffer.read(cx).remote_id())
|
||||
});
|
||||
})?;
|
||||
let _cleanup = defer({
|
||||
let this = lsp_store.clone();
|
||||
let mut cx = cx.clone();
|
||||
let buffers = &buffers;
|
||||
move || {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let this = this.as_local_mut().unwrap();
|
||||
for buffer in buffers {
|
||||
this.buffers_being_formatted
|
||||
.remove(&buffer.read(cx).remote_id());
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
let mut project_transaction = ProjectTransaction::default();
|
||||
|
||||
for buffer in &buffers {
|
||||
let adapters_and_servers = lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
lsp_store
|
||||
.as_local()
|
||||
.unwrap()
|
||||
.language_servers_for_buffer(buffer, cx)
|
||||
.map(|(adapter, lsp)| (adapter.clone(), lsp.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
})?;
|
||||
Self::execute_code_actions_on_servers(
|
||||
&lsp_store,
|
||||
&adapters_and_servers,
|
||||
vec![kind.clone()],
|
||||
&buffer,
|
||||
push_to_history,
|
||||
&mut project_transaction,
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(project_transaction)
|
||||
}
|
||||
|
||||
async fn format_locally(
|
||||
lsp_store: WeakEntity<LspStore>,
|
||||
mut buffers: Vec<FormattableBuffer>,
|
||||
|
@ -2900,6 +2958,7 @@ impl LspStore {
|
|||
client.add_entity_message_handler(Self::handle_language_server_log);
|
||||
client.add_entity_message_handler(Self::handle_update_diagnostic_summary);
|
||||
client.add_entity_request_handler(Self::handle_format_buffers);
|
||||
client.add_entity_request_handler(Self::handle_apply_code_action_kind);
|
||||
client.add_entity_request_handler(Self::handle_resolve_completion_documentation);
|
||||
client.add_entity_request_handler(Self::handle_apply_code_action);
|
||||
client.add_entity_request_handler(Self::handle_inlay_hints);
|
||||
|
@ -3891,6 +3950,65 @@ impl LspStore {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn apply_code_action_kind(
|
||||
&mut self,
|
||||
buffers: HashSet<Entity<Buffer>>,
|
||||
kind: CodeActionKind,
|
||||
push_to_history: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<anyhow::Result<ProjectTransaction>> {
|
||||
if let Some(_) = self.as_local() {
|
||||
cx.spawn(move |lsp_store, mut cx| async move {
|
||||
let buffers = buffers.into_iter().collect::<Vec<_>>();
|
||||
let result = LocalLspStore::execute_code_action_kind_locally(
|
||||
lsp_store.clone(),
|
||||
buffers,
|
||||
kind,
|
||||
push_to_history,
|
||||
cx.clone(),
|
||||
)
|
||||
.await;
|
||||
lsp_store.update(&mut cx, |lsp_store, _| {
|
||||
lsp_store.update_last_formatting_failure(&result);
|
||||
})?;
|
||||
result
|
||||
})
|
||||
} else if let Some((client, project_id)) = self.upstream_client() {
|
||||
let buffer_store = self.buffer_store();
|
||||
cx.spawn(move |lsp_store, mut cx| async move {
|
||||
let result = client
|
||||
.request(proto::ApplyCodeActionKind {
|
||||
project_id,
|
||||
kind: kind.as_str().to_owned(),
|
||||
buffer_ids: buffers
|
||||
.iter()
|
||||
.map(|buffer| {
|
||||
buffer.update(&mut cx, |buffer, _| buffer.remote_id().into())
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
})
|
||||
.await
|
||||
.and_then(|result| result.transaction.context("missing transaction"));
|
||||
lsp_store.update(&mut cx, |lsp_store, _| {
|
||||
lsp_store.update_last_formatting_failure(&result);
|
||||
})?;
|
||||
|
||||
let transaction_response = result?;
|
||||
buffer_store
|
||||
.update(&mut cx, |buffer_store, cx| {
|
||||
buffer_store.deserialize_project_transaction(
|
||||
transaction_response,
|
||||
push_to_history,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
})
|
||||
} else {
|
||||
Task::ready(Ok(ProjectTransaction::default()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_inlay_hint(
|
||||
&self,
|
||||
hint: InlayHint,
|
||||
|
@ -7229,6 +7347,48 @@ impl LspStore {
|
|||
})
|
||||
}
|
||||
|
||||
async fn handle_apply_code_action_kind(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::ApplyCodeActionKind>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::ApplyCodeActionKindResponse> {
|
||||
let sender_id = envelope.original_sender_id().unwrap_or_default();
|
||||
let format = this.update(&mut cx, |this, cx| {
|
||||
let mut buffers = HashSet::default();
|
||||
for buffer_id in &envelope.payload.buffer_ids {
|
||||
let buffer_id = BufferId::new(*buffer_id)?;
|
||||
buffers.insert(this.buffer_store.read(cx).get_existing(buffer_id)?);
|
||||
}
|
||||
let kind = match envelope.payload.kind.as_str() {
|
||||
"" => Ok(CodeActionKind::EMPTY),
|
||||
"quickfix" => Ok(CodeActionKind::QUICKFIX),
|
||||
"refactor" => Ok(CodeActionKind::REFACTOR),
|
||||
"refactor.extract" => Ok(CodeActionKind::REFACTOR_EXTRACT),
|
||||
"refactor.inline" => Ok(CodeActionKind::REFACTOR_INLINE),
|
||||
"refactor.rewrite" => Ok(CodeActionKind::REFACTOR_REWRITE),
|
||||
"source" => Ok(CodeActionKind::SOURCE),
|
||||
"source.organizeImports" => Ok(CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
|
||||
"source.fixAll" => Ok(CodeActionKind::SOURCE_FIX_ALL),
|
||||
_ => Err(anyhow!("Invalid code action kind")),
|
||||
}?;
|
||||
anyhow::Ok(this.apply_code_action_kind(buffers, kind, false, cx))
|
||||
})??;
|
||||
|
||||
let project_transaction = format.await?;
|
||||
let project_transaction = this.update(&mut cx, |this, cx| {
|
||||
this.buffer_store.update(cx, |buffer_store, cx| {
|
||||
buffer_store.serialize_project_transaction_for_peer(
|
||||
project_transaction,
|
||||
sender_id,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})?;
|
||||
Ok(proto::ApplyCodeActionKindResponse {
|
||||
transaction: Some(project_transaction),
|
||||
})
|
||||
}
|
||||
|
||||
async fn shutdown_language_server(
|
||||
server_state: Option<LanguageServerState>,
|
||||
name: LanguageServerName,
|
||||
|
|
|
@ -3029,6 +3029,18 @@ impl Project {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn apply_code_action_kind(
|
||||
&self,
|
||||
buffers: HashSet<Entity<Buffer>>,
|
||||
kind: CodeActionKind,
|
||||
push_to_history: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<ProjectTransaction>> {
|
||||
self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.apply_code_action_kind(buffers, kind, push_to_history, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_rename_impl(
|
||||
&mut self,
|
||||
buffer: Entity<Buffer>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue