
Make more explicit which language LSP tasks are used. Before:  After:   Release Notes: - N/A
225 lines
8.1 KiB
Rust
225 lines
8.1 KiB
Rust
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use crate::Editor;
|
|
use collections::HashMap;
|
|
use futures::stream::FuturesUnordered;
|
|
use gpui::AsyncApp;
|
|
use gpui::{App, AppContext as _, Entity, Task};
|
|
use itertools::Itertools;
|
|
use language::Buffer;
|
|
use language::Language;
|
|
use lsp::LanguageServerId;
|
|
use lsp::LanguageServerName;
|
|
use multi_buffer::Anchor;
|
|
use project::LanguageServerToQuery;
|
|
use project::LocationLink;
|
|
use project::Project;
|
|
use project::TaskSourceKind;
|
|
use project::lsp_store::lsp_ext_command::GetLspRunnables;
|
|
use smol::future::FutureExt as _;
|
|
use smol::stream::StreamExt;
|
|
use task::ResolvedTask;
|
|
use task::TaskContext;
|
|
use text::BufferId;
|
|
use ui::SharedString;
|
|
use util::ResultExt as _;
|
|
|
|
pub(crate) fn find_specific_language_server_in_selection<F>(
|
|
editor: &Editor,
|
|
cx: &mut App,
|
|
filter_language: F,
|
|
language_server_name: &str,
|
|
) -> Task<Option<(Anchor, Arc<Language>, LanguageServerId, Entity<Buffer>)>>
|
|
where
|
|
F: Fn(&Language) -> bool,
|
|
{
|
|
let Some(project) = &editor.project else {
|
|
return Task::ready(None);
|
|
};
|
|
|
|
let applicable_buffers = editor
|
|
.selections
|
|
.disjoint_anchors()
|
|
.iter()
|
|
.filter(|selection| selection.start == selection.end)
|
|
.filter_map(|selection| Some((selection.start, selection.start.buffer_id?)))
|
|
.filter_map(|(trigger_anchor, buffer_id)| {
|
|
let buffer = editor.buffer().read(cx).buffer(buffer_id)?;
|
|
let language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
|
|
if filter_language(&language) {
|
|
Some((trigger_anchor, buffer, language))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.unique_by(|(_, buffer, _)| buffer.read(cx).remote_id())
|
|
.collect::<Vec<_>>();
|
|
|
|
let applicable_buffer_tasks = applicable_buffers
|
|
.into_iter()
|
|
.map(|(trigger_anchor, buffer, language)| {
|
|
let task = buffer.update(cx, |buffer, cx| {
|
|
project.update(cx, |project, cx| {
|
|
project.language_server_id_for_name(buffer, language_server_name, cx)
|
|
})
|
|
});
|
|
(trigger_anchor, buffer, language, task)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
cx.background_spawn(async move {
|
|
for (trigger_anchor, buffer, language, task) in applicable_buffer_tasks {
|
|
if let Some(server_id) = task.await {
|
|
return Some((trigger_anchor, language, server_id, buffer));
|
|
}
|
|
}
|
|
|
|
None
|
|
})
|
|
}
|
|
|
|
async fn lsp_task_context(
|
|
project: &Entity<Project>,
|
|
buffer: &Entity<Buffer>,
|
|
cx: &mut AsyncApp,
|
|
) -> Option<TaskContext> {
|
|
let worktree_store = project
|
|
.read_with(cx, |project, _| project.worktree_store())
|
|
.ok()?;
|
|
|
|
let worktree_abs_path = cx
|
|
.update(|cx| {
|
|
let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx));
|
|
|
|
worktree_id
|
|
.and_then(|worktree_id| worktree_store.read(cx).worktree_for_id(worktree_id, cx))
|
|
.and_then(|worktree| worktree.read(cx).root_dir())
|
|
})
|
|
.ok()?;
|
|
|
|
let project_env = project
|
|
.update(cx, |project, cx| {
|
|
project.buffer_environment(&buffer, &worktree_store, cx)
|
|
})
|
|
.ok()?
|
|
.await;
|
|
|
|
Some(TaskContext {
|
|
cwd: worktree_abs_path.map(|p| p.to_path_buf()),
|
|
project_env: project_env.unwrap_or_default(),
|
|
..TaskContext::default()
|
|
})
|
|
}
|
|
|
|
pub fn lsp_tasks(
|
|
project: Entity<Project>,
|
|
task_sources: &HashMap<LanguageServerName, Vec<BufferId>>,
|
|
for_position: Option<text::Anchor>,
|
|
cx: &mut App,
|
|
) -> Task<Vec<(TaskSourceKind, Vec<(Option<LocationLink>, ResolvedTask)>)>> {
|
|
let mut lsp_task_sources = task_sources
|
|
.iter()
|
|
.map(|(name, buffer_ids)| {
|
|
let buffers = buffer_ids
|
|
.iter()
|
|
.filter(|&&buffer_id| match for_position {
|
|
Some(for_position) => for_position.buffer_id == Some(buffer_id),
|
|
None => true,
|
|
})
|
|
.filter_map(|&buffer_id| project.read(cx).buffer_for_id(buffer_id, cx))
|
|
.collect::<Vec<_>>();
|
|
language_server_for_buffers(project.clone(), name.clone(), buffers, cx)
|
|
})
|
|
.collect::<FuturesUnordered<_>>();
|
|
|
|
cx.spawn(async move |cx| {
|
|
cx.spawn(async move |cx| {
|
|
let mut lsp_tasks = HashMap::default();
|
|
while let Some(server_to_query) = lsp_task_sources.next().await {
|
|
if let Some((server_id, buffers)) = server_to_query {
|
|
let mut new_lsp_tasks = Vec::new();
|
|
for buffer in buffers {
|
|
let source_kind = match buffer.update(cx, |buffer, _| {
|
|
buffer.language().map(|language| language.name())
|
|
}) {
|
|
Ok(Some(language_name)) => TaskSourceKind::Lsp {
|
|
server: server_id,
|
|
language_name: SharedString::from(language_name),
|
|
},
|
|
Ok(None) => continue,
|
|
Err(_) => return Vec::new(),
|
|
};
|
|
let id_base = source_kind.to_id_base();
|
|
let lsp_buffer_context = lsp_task_context(&project, &buffer, cx)
|
|
.await
|
|
.unwrap_or_default();
|
|
|
|
if let Ok(runnables_task) = project.update(cx, |project, cx| {
|
|
let buffer_id = buffer.read(cx).remote_id();
|
|
project.request_lsp(
|
|
buffer,
|
|
LanguageServerToQuery::Other(server_id),
|
|
GetLspRunnables {
|
|
buffer_id,
|
|
position: for_position,
|
|
},
|
|
cx,
|
|
)
|
|
}) {
|
|
if let Some(new_runnables) = runnables_task.await.log_err() {
|
|
new_lsp_tasks.extend(
|
|
new_runnables.runnables.into_iter().filter_map(
|
|
|(location, runnable)| {
|
|
let resolved_task = runnable
|
|
.resolve_task(&id_base, &lsp_buffer_context)?;
|
|
Some((location, resolved_task))
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
lsp_tasks
|
|
.entry(source_kind)
|
|
.or_insert_with(Vec::new)
|
|
.append(&mut new_lsp_tasks);
|
|
}
|
|
}
|
|
}
|
|
lsp_tasks.into_iter().collect()
|
|
})
|
|
.race({
|
|
// `lsp::LSP_REQUEST_TIMEOUT` is larger than we want for the modal to open fast
|
|
let timer = cx.background_executor().timer(Duration::from_millis(200));
|
|
async move {
|
|
timer.await;
|
|
log::info!("Timed out waiting for LSP tasks");
|
|
Vec::new()
|
|
}
|
|
})
|
|
.await
|
|
})
|
|
}
|
|
|
|
fn language_server_for_buffers(
|
|
project: Entity<Project>,
|
|
name: LanguageServerName,
|
|
candidates: Vec<Entity<Buffer>>,
|
|
cx: &mut App,
|
|
) -> Task<Option<(LanguageServerId, Vec<Entity<Buffer>>)>> {
|
|
cx.spawn(async move |cx| {
|
|
for buffer in &candidates {
|
|
let server_id = buffer
|
|
.update(cx, |buffer, cx| {
|
|
project.update(cx, |project, cx| {
|
|
project.language_server_id_for_name(buffer, &name.0, cx)
|
|
})
|
|
})
|
|
.ok()?
|
|
.await;
|
|
if let Some(server_id) = server_id {
|
|
return Some((server_id, candidates));
|
|
}
|
|
}
|
|
None
|
|
})
|
|
}
|