mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-25 19:29:49 +00:00
Start work on applying code actions that use commands
Co-Authored-By: Keith Simmons <keith@zed.dev>
This commit is contained in:
parent
e987a8ba63
commit
fed5d141bc
1 changed files with 247 additions and 87 deletions
|
@ -143,6 +143,7 @@ enum LanguageServerEvent {
|
|||
token: String,
|
||||
},
|
||||
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
|
||||
WorkspaceEdit(lsp::ApplyWorkspaceEditParams),
|
||||
}
|
||||
|
||||
pub struct LanguageServerStatus {
|
||||
|
@ -1367,6 +1368,24 @@ impl Project {
|
|||
})
|
||||
.detach();
|
||||
|
||||
language_server
|
||||
.on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
|
||||
let language_server_events_tx = language_server_events_tx.clone();
|
||||
move |params, _| {
|
||||
language_server_events_tx
|
||||
.try_send(LanguageServerEvent::WorkspaceEdit(params))
|
||||
.ok();
|
||||
async move {
|
||||
Ok(lsp::ApplyWorkspaceEditResponse {
|
||||
applied: true,
|
||||
failed_change: None,
|
||||
failure_reason: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
language_server
|
||||
.on_notification::<lsp::notification::Progress, _>(move |params, _| {
|
||||
let token = match params.token {
|
||||
|
@ -1416,12 +1435,20 @@ impl Project {
|
|||
// Process all the LSP events.
|
||||
cx.spawn(|mut cx| {
|
||||
let this = this.downgrade();
|
||||
let adapter = adapter.clone();
|
||||
let language_server = language_server.clone();
|
||||
async move {
|
||||
while let Ok(event) = language_server_events_rx.recv().await {
|
||||
let this = this.upgrade(&cx)?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.on_lsp_event(server_id, event, &language, cx)
|
||||
});
|
||||
Self::on_lsp_event(
|
||||
this,
|
||||
server_id,
|
||||
&adapter,
|
||||
&language_server,
|
||||
event,
|
||||
&mut cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Don't starve the main thread when lots of events arrive all at once.
|
||||
smol::future::yield_now().await;
|
||||
|
@ -1585,109 +1612,142 @@ impl Project {
|
|||
.detach();
|
||||
}
|
||||
|
||||
fn on_lsp_event(
|
||||
&mut self,
|
||||
async fn on_lsp_event(
|
||||
this: ModelHandle<Self>,
|
||||
language_server_id: usize,
|
||||
adapter: &Arc<dyn LspAdapter>,
|
||||
language_server: &Arc<LanguageServer>,
|
||||
event: LanguageServerEvent,
|
||||
language: &Arc<Language>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) {
|
||||
let disk_diagnostics_token = language.disk_based_diagnostics_progress_token();
|
||||
let language_server_status =
|
||||
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
||||
status
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token();
|
||||
match event {
|
||||
LanguageServerEvent::WorkStart { token } => {
|
||||
if Some(token.as_str()) == disk_diagnostics_token {
|
||||
language_server_status.pending_diagnostic_updates += 1;
|
||||
if language_server_status.pending_diagnostic_updates == 1 {
|
||||
self.disk_based_diagnostics_started(cx);
|
||||
self.broadcast_language_server_update(
|
||||
this.update(cx, |this, cx| {
|
||||
let language_server_status = if let Some(status) =
|
||||
this.language_server_statuses.get_mut(&language_server_id)
|
||||
{
|
||||
status
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
if Some(token.as_str()) == disk_based_diagnostics_progress_token {
|
||||
language_server_status.pending_diagnostic_updates += 1;
|
||||
if language_server_status.pending_diagnostic_updates == 1 {
|
||||
this.disk_based_diagnostics_started(cx);
|
||||
this.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
|
||||
proto::LspDiskBasedDiagnosticsUpdating {},
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.on_lsp_work_start(language_server_id, token.clone(), cx);
|
||||
this.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::WorkStart(
|
||||
proto::LspWorkStart { token },
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
LanguageServerEvent::WorkProgress { token, progress } => {
|
||||
this.update(cx, |this, cx| {
|
||||
if Some(token.as_str()) != disk_based_diagnostics_progress_token {
|
||||
this.on_lsp_work_progress(
|
||||
language_server_id,
|
||||
token.clone(),
|
||||
progress.clone(),
|
||||
cx,
|
||||
);
|
||||
this.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::WorkProgress(
|
||||
proto::LspWorkProgress {
|
||||
token,
|
||||
message: progress.message,
|
||||
percentage: progress.percentage.map(|p| p as u32),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
LanguageServerEvent::WorkEnd { token } => {
|
||||
this.update(cx, |this, cx| {
|
||||
if Some(token.as_str()) == disk_based_diagnostics_progress_token {
|
||||
let language_server_status = if let Some(status) =
|
||||
this.language_server_statuses.get_mut(&language_server_id)
|
||||
{
|
||||
status
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
language_server_status.pending_diagnostic_updates -= 1;
|
||||
if language_server_status.pending_diagnostic_updates == 0 {
|
||||
this.disk_based_diagnostics_finished(cx);
|
||||
this.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
||||
proto::LspDiskBasedDiagnosticsUpdated {},
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.on_lsp_work_end(language_server_id, token.clone(), cx);
|
||||
this.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::WorkEnd(proto::LspWorkEnd {
|
||||
token,
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
LanguageServerEvent::DiagnosticsUpdate(mut params) => {
|
||||
this.update(cx, |this, cx| {
|
||||
adapter.process_diagnostics(&mut params);
|
||||
|
||||
if disk_based_diagnostics_progress_token.is_none() {
|
||||
this.disk_based_diagnostics_started(cx);
|
||||
this.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
|
||||
proto::LspDiskBasedDiagnosticsUpdating {},
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.on_lsp_work_start(language_server_id, token.clone(), cx);
|
||||
self.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::WorkStart(proto::LspWorkStart {
|
||||
token,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
LanguageServerEvent::WorkProgress { token, progress } => {
|
||||
if Some(token.as_str()) != disk_diagnostics_token {
|
||||
self.on_lsp_work_progress(
|
||||
language_server_id,
|
||||
token.clone(),
|
||||
progress.clone(),
|
||||
cx,
|
||||
);
|
||||
self.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::WorkProgress(
|
||||
proto::LspWorkProgress {
|
||||
token,
|
||||
message: progress.message,
|
||||
percentage: progress.percentage.map(|p| p as u32),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
LanguageServerEvent::WorkEnd { token } => {
|
||||
if Some(token.as_str()) == disk_diagnostics_token {
|
||||
language_server_status.pending_diagnostic_updates -= 1;
|
||||
if language_server_status.pending_diagnostic_updates == 0 {
|
||||
self.disk_based_diagnostics_finished(cx);
|
||||
self.broadcast_language_server_update(
|
||||
this.update_diagnostics(params, adapter.disk_based_diagnostic_sources(), cx)
|
||||
.log_err();
|
||||
if disk_based_diagnostics_progress_token.is_none() {
|
||||
this.disk_based_diagnostics_finished(cx);
|
||||
this.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
||||
proto::LspDiskBasedDiagnosticsUpdated {},
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.on_lsp_work_end(language_server_id, token.clone(), cx);
|
||||
self.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::WorkEnd(proto::LspWorkEnd {
|
||||
token,
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
LanguageServerEvent::DiagnosticsUpdate(mut params) => {
|
||||
language.process_diagnostics(&mut params);
|
||||
LanguageServerEvent::WorkspaceEdit(params) => {
|
||||
let transaction = Self::deserialize_workspace_edit(
|
||||
this,
|
||||
params.edit,
|
||||
false,
|
||||
adapter.clone(),
|
||||
language_server.clone(),
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
|
||||
if disk_diagnostics_token.is_none() {
|
||||
self.disk_based_diagnostics_started(cx);
|
||||
self.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
|
||||
proto::LspDiskBasedDiagnosticsUpdating {},
|
||||
),
|
||||
);
|
||||
}
|
||||
self.update_diagnostics(params, language.disk_based_diagnostic_sources(), cx)
|
||||
.log_err();
|
||||
if disk_diagnostics_token.is_none() {
|
||||
self.disk_based_diagnostics_finished(cx);
|
||||
self.broadcast_language_server_update(
|
||||
language_server_id,
|
||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
||||
proto::LspDiskBasedDiagnosticsUpdated {},
|
||||
),
|
||||
);
|
||||
}
|
||||
// Check if there is a code action currently running, using the state that is
|
||||
// set in `start_code_action`. If so, then store the transaction for later use.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2679,6 +2739,16 @@ impl Project {
|
|||
&mut cx,
|
||||
)
|
||||
.await
|
||||
} else if let Some(command) = action.lsp_action.command {
|
||||
this.update(&mut cx, |this, _| this.start_code_action());
|
||||
lang_server
|
||||
.request::<lsp::request::ExecuteCommand>(lsp::ExecuteCommandParams {
|
||||
command: command.command,
|
||||
arguments: command.arguments.unwrap_or_default(),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
Ok(this.update(&mut cx, |this, cx| this.finish_code_action(cx)))
|
||||
} else {
|
||||
Ok(ProjectTransaction::default())
|
||||
}
|
||||
|
@ -2837,6 +2907,17 @@ impl Project {
|
|||
Ok(project_transaction)
|
||||
}
|
||||
|
||||
fn start_code_action(&mut self) {
|
||||
// Set some state that will be read inside of `on_lsp_event` when handling a `WorkspaceEdit`
|
||||
// event, and will cause the `ProjectTransaction` to be stored.
|
||||
}
|
||||
|
||||
fn finish_code_action(&mut self, cx: &mut ModelContext<Self>) -> ProjectTransaction {
|
||||
// Retrieve all stored `ProjectTransactions` that have been received since `start_code_action`
|
||||
// was called, and combine them together.
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn prepare_rename<T: ToPointUtf16>(
|
||||
&self,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
|
@ -5992,6 +6073,85 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_apply_code_action(cx: &mut gpui::TestAppContext) {
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "TypeScript".into(),
|
||||
path_suffixes: vec!["ts".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
"a.ts": "",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, cx);
|
||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||
|
||||
let (tree, _) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_local_worktree("/dir", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let worktree_id = tree.read_with(cx, |tree, _| tree.id());
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let fake_server = fake_language_servers.next().await.unwrap();
|
||||
|
||||
let actions = project.update(cx, |project, cx| project.code_actions(&buffer, 0..0, cx));
|
||||
fake_server
|
||||
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
|
||||
Ok(Some(vec![
|
||||
lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
|
||||
title: "The code action".into(),
|
||||
command: Some(lsp::Command {
|
||||
title: "The command".into(),
|
||||
command: "_the/command".into(),
|
||||
arguments: Some(vec![json!("the-argument")]),
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
|
||||
title: "two".into(),
|
||||
..Default::default()
|
||||
}),
|
||||
]))
|
||||
})
|
||||
.next()
|
||||
.await;
|
||||
|
||||
let action = actions.await.unwrap()[0].clone();
|
||||
let apply = project.update(cx, |project, cx| {
|
||||
project.apply_code_action(buffer.clone(), action, true, cx)
|
||||
});
|
||||
fake_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
|
||||
|action, _| async move { Ok(action) },
|
||||
);
|
||||
fake_server
|
||||
.handle_request::<lsp::request::ExecuteCommand, _, _>(move |params, cx| async move {
|
||||
// fake_server.send();
|
||||
Ok(Some(json!(null)))
|
||||
})
|
||||
.next()
|
||||
.await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_save_file(cx: &mut gpui::TestAppContext) {
|
||||
let fs = FakeFs::new(cx.background());
|
||||
|
|
Loading…
Reference in a new issue