mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-26 20:22:30 +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,
|
token: String,
|
||||||
},
|
},
|
||||||
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
|
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
|
||||||
|
WorkspaceEdit(lsp::ApplyWorkspaceEditParams),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LanguageServerStatus {
|
pub struct LanguageServerStatus {
|
||||||
|
@ -1367,6 +1368,24 @@ impl Project {
|
||||||
})
|
})
|
||||||
.detach();
|
.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
|
language_server
|
||||||
.on_notification::<lsp::notification::Progress, _>(move |params, _| {
|
.on_notification::<lsp::notification::Progress, _>(move |params, _| {
|
||||||
let token = match params.token {
|
let token = match params.token {
|
||||||
|
@ -1416,12 +1435,20 @@ impl Project {
|
||||||
// Process all the LSP events.
|
// Process all the LSP events.
|
||||||
cx.spawn(|mut cx| {
|
cx.spawn(|mut cx| {
|
||||||
let this = this.downgrade();
|
let this = this.downgrade();
|
||||||
|
let adapter = adapter.clone();
|
||||||
|
let language_server = language_server.clone();
|
||||||
async move {
|
async move {
|
||||||
while let Ok(event) = language_server_events_rx.recv().await {
|
while let Ok(event) = language_server_events_rx.recv().await {
|
||||||
let this = this.upgrade(&cx)?;
|
let this = this.upgrade(&cx)?;
|
||||||
this.update(&mut cx, |this, cx| {
|
Self::on_lsp_event(
|
||||||
this.on_lsp_event(server_id, event, &language, cx)
|
this,
|
||||||
});
|
server_id,
|
||||||
|
&adapter,
|
||||||
|
&language_server,
|
||||||
|
event,
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
// Don't starve the main thread when lots of events arrive all at once.
|
// Don't starve the main thread when lots of events arrive all at once.
|
||||||
smol::future::yield_now().await;
|
smol::future::yield_now().await;
|
||||||
|
@ -1585,109 +1612,142 @@ impl Project {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_lsp_event(
|
async fn on_lsp_event(
|
||||||
&mut self,
|
this: ModelHandle<Self>,
|
||||||
language_server_id: usize,
|
language_server_id: usize,
|
||||||
|
adapter: &Arc<dyn LspAdapter>,
|
||||||
|
language_server: &Arc<LanguageServer>,
|
||||||
event: LanguageServerEvent,
|
event: LanguageServerEvent,
|
||||||
language: &Arc<Language>,
|
cx: &mut AsyncAppContext,
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) {
|
) {
|
||||||
let disk_diagnostics_token = language.disk_based_diagnostics_progress_token();
|
let disk_based_diagnostics_progress_token = adapter.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;
|
|
||||||
};
|
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
LanguageServerEvent::WorkStart { token } => {
|
LanguageServerEvent::WorkStart { token } => {
|
||||||
if Some(token.as_str()) == disk_diagnostics_token {
|
this.update(cx, |this, cx| {
|
||||||
language_server_status.pending_diagnostic_updates += 1;
|
let language_server_status = if let Some(status) =
|
||||||
if language_server_status.pending_diagnostic_updates == 1 {
|
this.language_server_statuses.get_mut(&language_server_id)
|
||||||
self.disk_based_diagnostics_started(cx);
|
{
|
||||||
self.broadcast_language_server_update(
|
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,
|
language_server_id,
|
||||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
|
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
|
||||||
proto::LspDiskBasedDiagnosticsUpdating {},
|
proto::LspDiskBasedDiagnosticsUpdating {},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
this.update_diagnostics(params, adapter.disk_based_diagnostic_sources(), cx)
|
||||||
self.on_lsp_work_start(language_server_id, token.clone(), cx);
|
.log_err();
|
||||||
self.broadcast_language_server_update(
|
if disk_based_diagnostics_progress_token.is_none() {
|
||||||
language_server_id,
|
this.disk_based_diagnostics_finished(cx);
|
||||||
proto::update_language_server::Variant::WorkStart(proto::LspWorkStart {
|
this.broadcast_language_server_update(
|
||||||
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(
|
|
||||||
language_server_id,
|
language_server_id,
|
||||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
||||||
proto::LspDiskBasedDiagnosticsUpdated {},
|
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) => {
|
LanguageServerEvent::WorkspaceEdit(params) => {
|
||||||
language.process_diagnostics(&mut 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() {
|
// Check if there is a code action currently running, using the state that is
|
||||||
self.disk_based_diagnostics_started(cx);
|
// set in `start_code_action`. If so, then store the transaction for later use.
|
||||||
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 {},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2679,6 +2739,16 @@ impl Project {
|
||||||
&mut cx,
|
&mut cx,
|
||||||
)
|
)
|
||||||
.await
|
.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 {
|
} else {
|
||||||
Ok(ProjectTransaction::default())
|
Ok(ProjectTransaction::default())
|
||||||
}
|
}
|
||||||
|
@ -2837,6 +2907,17 @@ impl Project {
|
||||||
Ok(project_transaction)
|
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>(
|
pub fn prepare_rename<T: ToPointUtf16>(
|
||||||
&self,
|
&self,
|
||||||
buffer: ModelHandle<Buffer>,
|
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]
|
#[gpui::test]
|
||||||
async fn test_save_file(cx: &mut gpui::TestAppContext) {
|
async fn test_save_file(cx: &mut gpui::TestAppContext) {
|
||||||
let fs = FakeFs::new(cx.background());
|
let fs = FakeFs::new(cx.background());
|
||||||
|
|
Loading…
Reference in a new issue