mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-11 13:10:54 +00:00
Handle LSP apply workspace edit request fully before responding
This commit is contained in:
parent
56523b5775
commit
ba009724dd
2 changed files with 172 additions and 196 deletions
|
@ -2593,6 +2593,8 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ranges_to_highlight = Vec::new();
|
let mut ranges_to_highlight = Vec::new();
|
||||||
|
|
|
@ -132,21 +132,6 @@ pub enum Event {
|
||||||
CollaboratorLeft(PeerId),
|
CollaboratorLeft(PeerId),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum LanguageServerEvent {
|
|
||||||
WorkStart {
|
|
||||||
token: String,
|
|
||||||
},
|
|
||||||
WorkProgress {
|
|
||||||
token: String,
|
|
||||||
progress: LanguageServerProgress,
|
|
||||||
},
|
|
||||||
WorkEnd {
|
|
||||||
token: String,
|
|
||||||
},
|
|
||||||
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
|
|
||||||
WorkspaceEdit(lsp::ApplyWorkspaceEditParams),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LanguageServerStatus {
|
pub struct LanguageServerStatus {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub pending_work: BTreeMap<String, LanguageServerProgress>,
|
pub pending_work: BTreeMap<String, LanguageServerProgress>,
|
||||||
|
@ -1330,17 +1315,30 @@ impl Project {
|
||||||
);
|
);
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
cx.spawn_weak(|this, mut cx| async move {
|
||||||
let language_server = language_server?.await.log_err()?;
|
let language_server = language_server?.await.log_err()?;
|
||||||
|
let language_server = language_server
|
||||||
|
.initialize(adapter.initialization_options())
|
||||||
|
.await
|
||||||
|
.log_err()?;
|
||||||
let this = this.upgrade(&cx)?;
|
let this = this.upgrade(&cx)?;
|
||||||
let (language_server_events_tx, language_server_events_rx) =
|
let disk_based_diagnostics_progress_token =
|
||||||
smol::channel::unbounded();
|
adapter.disk_based_diagnostics_progress_token();
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_notification::<lsp::notification::PublishDiagnostics, _>({
|
.on_notification::<lsp::notification::PublishDiagnostics, _>({
|
||||||
let language_server_events_tx = language_server_events_tx.clone();
|
let this = this.downgrade();
|
||||||
move |params, _| {
|
let adapter = adapter.clone();
|
||||||
language_server_events_tx
|
move |params, mut cx| {
|
||||||
.try_send(LanguageServerEvent::DiagnosticsUpdate(params))
|
if let Some(this) = this.upgrade(&cx) {
|
||||||
.ok();
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.on_lsp_diagnostics_published(
|
||||||
|
server_id,
|
||||||
|
params,
|
||||||
|
&adapter,
|
||||||
|
disk_based_diagnostics_progress_token,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
@ -1373,94 +1371,40 @@ impl Project {
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
|
.on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
|
||||||
let language_server_events_tx = language_server_events_tx.clone();
|
let this = this.downgrade();
|
||||||
move |params, _| {
|
let adapter = adapter.clone();
|
||||||
language_server_events_tx
|
let language_server = language_server.clone();
|
||||||
.try_send(LanguageServerEvent::WorkspaceEdit(params))
|
move |params, cx| {
|
||||||
.ok();
|
Self::on_lsp_workspace_edit(
|
||||||
async move {
|
this,
|
||||||
Ok(lsp::ApplyWorkspaceEditResponse {
|
params,
|
||||||
applied: true,
|
server_id,
|
||||||
failed_change: None,
|
adapter.clone(),
|
||||||
failure_reason: None,
|
language_server.clone(),
|
||||||
})
|
cx,
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_notification::<lsp::notification::Progress, _>(move |params, _| {
|
.on_notification::<lsp::notification::Progress, _>({
|
||||||
let token = match params.token {
|
let this = this.downgrade();
|
||||||
lsp::NumberOrString::String(token) => token,
|
move |params, mut cx| {
|
||||||
lsp::NumberOrString::Number(token) => {
|
if let Some(this) = this.upgrade(&cx) {
|
||||||
log::info!("skipping numeric progress token {}", token);
|
this.update(&mut cx, |this, cx| {
|
||||||
return;
|
this.on_lsp_progress(
|
||||||
|
params,
|
||||||
|
server_id,
|
||||||
|
disk_based_diagnostics_progress_token,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
match params.value {
|
|
||||||
lsp::ProgressParamsValue::WorkDone(progress) => match progress {
|
|
||||||
lsp::WorkDoneProgress::Begin(_) => {
|
|
||||||
language_server_events_tx
|
|
||||||
.try_send(LanguageServerEvent::WorkStart { token })
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
lsp::WorkDoneProgress::Report(report) => {
|
|
||||||
language_server_events_tx
|
|
||||||
.try_send(LanguageServerEvent::WorkProgress {
|
|
||||||
token,
|
|
||||||
progress: LanguageServerProgress {
|
|
||||||
message: report.message,
|
|
||||||
percentage: report
|
|
||||||
.percentage
|
|
||||||
.map(|p| p as usize),
|
|
||||||
last_update_at: Instant::now(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
lsp::WorkDoneProgress::End(_) => {
|
|
||||||
language_server_events_tx
|
|
||||||
.try_send(LanguageServerEvent::WorkEnd { token })
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let language_server = language_server
|
|
||||||
.initialize(adapter.initialization_options())
|
|
||||||
.await
|
|
||||||
.log_err()?;
|
|
||||||
|
|
||||||
// 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)?;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
Some(())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.language_servers
|
this.language_servers
|
||||||
.insert(key.clone(), (adapter, language_server.clone()));
|
.insert(key.clone(), (adapter, language_server.clone()));
|
||||||
|
@ -1615,75 +1559,111 @@ impl Project {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_lsp_event(
|
fn on_lsp_diagnostics_published(
|
||||||
this: ModelHandle<Self>,
|
&mut self,
|
||||||
language_server_id: usize,
|
server_id: usize,
|
||||||
|
mut params: lsp::PublishDiagnosticsParams,
|
||||||
adapter: &Arc<dyn LspAdapter>,
|
adapter: &Arc<dyn LspAdapter>,
|
||||||
language_server: &Arc<LanguageServer>,
|
disk_based_diagnostics_progress_token: Option<&str>,
|
||||||
event: LanguageServerEvent,
|
cx: &mut ModelContext<Self>,
|
||||||
cx: &mut AsyncAppContext,
|
|
||||||
) {
|
) {
|
||||||
let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token();
|
adapter.process_diagnostics(&mut params);
|
||||||
match event {
|
if disk_based_diagnostics_progress_token.is_none() {
|
||||||
LanguageServerEvent::WorkStart { token } => {
|
self.disk_based_diagnostics_started(cx);
|
||||||
this.update(cx, |this, cx| {
|
self.broadcast_language_server_update(
|
||||||
let language_server_status = if let Some(status) =
|
server_id,
|
||||||
this.language_server_statuses.get_mut(&language_server_id)
|
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
|
||||||
{
|
proto::LspDiskBasedDiagnosticsUpdating {},
|
||||||
status
|
),
|
||||||
} else {
|
);
|
||||||
return;
|
}
|
||||||
};
|
self.update_diagnostics(params, adapter.disk_based_diagnostic_sources(), cx)
|
||||||
|
.log_err();
|
||||||
|
if disk_based_diagnostics_progress_token.is_none() {
|
||||||
|
self.disk_based_diagnostics_finished(cx);
|
||||||
|
self.broadcast_language_server_update(
|
||||||
|
server_id,
|
||||||
|
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
||||||
|
proto::LspDiskBasedDiagnosticsUpdated {},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_lsp_progress(
|
||||||
|
&mut self,
|
||||||
|
progress: lsp::ProgressParams,
|
||||||
|
server_id: usize,
|
||||||
|
disk_based_diagnostics_progress_token: Option<&str>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
let token = match progress.token {
|
||||||
|
lsp::NumberOrString::String(token) => token,
|
||||||
|
lsp::NumberOrString::Number(token) => {
|
||||||
|
log::info!("skipping numeric progress token {}", token);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match progress.value {
|
||||||
|
lsp::ProgressParamsValue::WorkDone(progress) => match progress {
|
||||||
|
lsp::WorkDoneProgress::Begin(_) => {
|
||||||
|
let language_server_status =
|
||||||
|
if let Some(status) = self.language_server_statuses.get_mut(&server_id) {
|
||||||
|
status
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
if Some(token.as_str()) == disk_based_diagnostics_progress_token {
|
if Some(token.as_str()) == disk_based_diagnostics_progress_token {
|
||||||
language_server_status.pending_diagnostic_updates += 1;
|
language_server_status.pending_diagnostic_updates += 1;
|
||||||
if language_server_status.pending_diagnostic_updates == 1 {
|
if language_server_status.pending_diagnostic_updates == 1 {
|
||||||
this.disk_based_diagnostics_started(cx);
|
self.disk_based_diagnostics_started(cx);
|
||||||
this.broadcast_language_server_update(
|
self.broadcast_language_server_update(
|
||||||
language_server_id,
|
server_id,
|
||||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
|
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
|
||||||
proto::LspDiskBasedDiagnosticsUpdating {},
|
proto::LspDiskBasedDiagnosticsUpdating {},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.on_lsp_work_start(language_server_id, token.clone(), cx);
|
self.on_lsp_work_start(server_id, token.clone(), cx);
|
||||||
this.broadcast_language_server_update(
|
self.broadcast_language_server_update(
|
||||||
language_server_id,
|
server_id,
|
||||||
proto::update_language_server::Variant::WorkStart(
|
proto::update_language_server::Variant::WorkStart(
|
||||||
proto::LspWorkStart { token },
|
proto::LspWorkStart { token },
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
lsp::WorkDoneProgress::Report(report) => {
|
||||||
LanguageServerEvent::WorkProgress { token, progress } => {
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
if Some(token.as_str()) != disk_based_diagnostics_progress_token {
|
if Some(token.as_str()) != disk_based_diagnostics_progress_token {
|
||||||
this.on_lsp_work_progress(
|
self.on_lsp_work_progress(
|
||||||
language_server_id,
|
server_id,
|
||||||
token.clone(),
|
token.clone(),
|
||||||
progress.clone(),
|
LanguageServerProgress {
|
||||||
|
message: report.message.clone(),
|
||||||
|
percentage: report.percentage.map(|p| p as usize),
|
||||||
|
last_update_at: Instant::now(),
|
||||||
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
this.broadcast_language_server_update(
|
self.broadcast_language_server_update(
|
||||||
language_server_id,
|
server_id,
|
||||||
proto::update_language_server::Variant::WorkProgress(
|
proto::update_language_server::Variant::WorkProgress(
|
||||||
proto::LspWorkProgress {
|
proto::LspWorkProgress {
|
||||||
token,
|
token,
|
||||||
message: progress.message,
|
message: report.message,
|
||||||
percentage: progress.percentage.map(|p| p as u32),
|
percentage: report.percentage.map(|p| p as u32),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
lsp::WorkDoneProgress::End(_) => {
|
||||||
LanguageServerEvent::WorkEnd { token } => {
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
if Some(token.as_str()) == disk_based_diagnostics_progress_token {
|
if Some(token.as_str()) == disk_based_diagnostics_progress_token {
|
||||||
let language_server_status = if let Some(status) =
|
let language_server_status = if let Some(status) =
|
||||||
this.language_server_statuses.get_mut(&language_server_id)
|
self.language_server_statuses.get_mut(&server_id)
|
||||||
{
|
{
|
||||||
status
|
status
|
||||||
} else {
|
} else {
|
||||||
|
@ -1692,69 +1672,25 @@ impl Project {
|
||||||
|
|
||||||
language_server_status.pending_diagnostic_updates -= 1;
|
language_server_status.pending_diagnostic_updates -= 1;
|
||||||
if language_server_status.pending_diagnostic_updates == 0 {
|
if language_server_status.pending_diagnostic_updates == 0 {
|
||||||
this.disk_based_diagnostics_finished(cx);
|
self.disk_based_diagnostics_finished(cx);
|
||||||
this.broadcast_language_server_update(
|
self.broadcast_language_server_update(
|
||||||
language_server_id,
|
server_id,
|
||||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
||||||
proto::LspDiskBasedDiagnosticsUpdated {},
|
proto::LspDiskBasedDiagnosticsUpdated {},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.on_lsp_work_end(language_server_id, token.clone(), cx);
|
self.on_lsp_work_end(server_id, token.clone(), cx);
|
||||||
this.broadcast_language_server_update(
|
self.broadcast_language_server_update(
|
||||||
language_server_id,
|
server_id,
|
||||||
proto::update_language_server::Variant::WorkEnd(proto::LspWorkEnd {
|
proto::update_language_server::Variant::WorkEnd(proto::LspWorkEnd {
|
||||||
token,
|
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 {},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
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 {},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
LanguageServerEvent::WorkspaceEdit(params) => {
|
|
||||||
let transaction = Self::deserialize_workspace_edit(
|
|
||||||
this.clone(),
|
|
||||||
params.edit,
|
|
||||||
true,
|
|
||||||
adapter.clone(),
|
|
||||||
language_server.clone(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
this.update(cx, |this, _| {
|
|
||||||
if let Some(transaction) = transaction {
|
|
||||||
this.last_workspace_edits_by_language_server
|
|
||||||
.insert(language_server_id, transaction);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1802,6 +1738,40 @@ impl Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn on_lsp_workspace_edit(
|
||||||
|
this: WeakModelHandle<Self>,
|
||||||
|
params: lsp::ApplyWorkspaceEditParams,
|
||||||
|
server_id: usize,
|
||||||
|
adapter: Arc<dyn LspAdapter>,
|
||||||
|
language_server: Arc<LanguageServer>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<lsp::ApplyWorkspaceEditResponse> {
|
||||||
|
let this = this
|
||||||
|
.upgrade(&cx)
|
||||||
|
.ok_or_else(|| anyhow!("project project closed"))?;
|
||||||
|
let transaction = Self::deserialize_workspace_edit(
|
||||||
|
this.clone(),
|
||||||
|
params.edit,
|
||||||
|
true,
|
||||||
|
adapter.clone(),
|
||||||
|
language_server.clone(),
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
this.update(&mut cx, |this, _| {
|
||||||
|
if let Some(transaction) = transaction {
|
||||||
|
this.last_workspace_edits_by_language_server
|
||||||
|
.insert(server_id, transaction);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(lsp::ApplyWorkspaceEditResponse {
|
||||||
|
applied: true,
|
||||||
|
failed_change: None,
|
||||||
|
failure_reason: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn broadcast_language_server_update(
|
fn broadcast_language_server_update(
|
||||||
&self,
|
&self,
|
||||||
language_server_id: usize,
|
language_server_id: usize,
|
||||||
|
@ -2746,6 +2716,10 @@ impl Project {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
} else if let Some(command) = action.lsp_action.command {
|
} else 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());
|
||||||
|
});
|
||||||
lang_server
|
lang_server
|
||||||
.request::<lsp::request::ExecuteCommand>(lsp::ExecuteCommandParams {
|
.request::<lsp::request::ExecuteCommand>(lsp::ExecuteCommandParams {
|
||||||
command: command.command,
|
command: command.command,
|
||||||
|
@ -6071,7 +6045,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test(iterations = 100)]
|
||||||
async fn test_apply_code_action(cx: &mut gpui::TestAppContext) {
|
async fn test_apply_code_action(cx: &mut gpui::TestAppContext) {
|
||||||
let mut language = Language::new(
|
let mut language = Language::new(
|
||||||
LanguageConfig {
|
LanguageConfig {
|
||||||
|
|
Loading…
Reference in a new issue