1use std::sync::Arc;
2use std::time::Duration;
3
4use crate::Editor;
5use collections::HashMap;
6use futures::stream::FuturesUnordered;
7use gpui::AsyncApp;
8use gpui::{App, AppContext as _, Entity, Task};
9use itertools::Itertools;
10use language::Buffer;
11use language::Language;
12use lsp::LanguageServerId;
13use lsp::LanguageServerName;
14use multi_buffer::Anchor;
15use project::LanguageServerToQuery;
16use project::LocationLink;
17use project::Project;
18use project::TaskSourceKind;
19use project::lsp_store::lsp_ext_command::GetLspRunnables;
20use smol::future::FutureExt as _;
21use smol::stream::StreamExt;
22use task::ResolvedTask;
23use task::TaskContext;
24use text::BufferId;
25use ui::SharedString;
26use util::ResultExt as _;
27
28pub(crate) fn find_specific_language_server_in_selection<F>(
29 editor: &Editor,
30 cx: &mut App,
31 filter_language: F,
32 language_server_name: &str,
33) -> Task<Option<(Anchor, Arc<Language>, LanguageServerId, Entity<Buffer>)>>
34where
35 F: Fn(&Language) -> bool,
36{
37 let Some(project) = &editor.project else {
38 return Task::ready(None);
39 };
40
41 let applicable_buffers = editor
42 .selections
43 .disjoint_anchors()
44 .iter()
45 .filter(|selection| selection.start == selection.end)
46 .filter_map(|selection| Some((selection.start, selection.start.buffer_id?)))
47 .filter_map(|(trigger_anchor, buffer_id)| {
48 let buffer = editor.buffer().read(cx).buffer(buffer_id)?;
49 let language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
50 if filter_language(&language) {
51 Some((trigger_anchor, buffer, language))
52 } else {
53 None
54 }
55 })
56 .unique_by(|(_, buffer, _)| buffer.read(cx).remote_id())
57 .collect::<Vec<_>>();
58
59 let applicable_buffer_tasks = applicable_buffers
60 .into_iter()
61 .map(|(trigger_anchor, buffer, language)| {
62 let task = buffer.update(cx, |buffer, cx| {
63 project.update(cx, |project, cx| {
64 project.language_server_id_for_name(buffer, language_server_name, cx)
65 })
66 });
67 (trigger_anchor, buffer, language, task)
68 })
69 .collect::<Vec<_>>();
70 cx.background_spawn(async move {
71 for (trigger_anchor, buffer, language, task) in applicable_buffer_tasks {
72 if let Some(server_id) = task.await {
73 return Some((trigger_anchor, language, server_id, buffer));
74 }
75 }
76
77 None
78 })
79}
80
81async fn lsp_task_context(
82 project: &Entity<Project>,
83 buffer: &Entity<Buffer>,
84 cx: &mut AsyncApp,
85) -> Option<TaskContext> {
86 let worktree_store = project
87 .read_with(cx, |project, _| project.worktree_store())
88 .ok()?;
89
90 let worktree_abs_path = cx
91 .update(|cx| {
92 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx));
93
94 worktree_id
95 .and_then(|worktree_id| worktree_store.read(cx).worktree_for_id(worktree_id, cx))
96 .and_then(|worktree| worktree.read(cx).root_dir())
97 })
98 .ok()?;
99
100 let project_env = project
101 .update(cx, |project, cx| {
102 project.buffer_environment(&buffer, &worktree_store, cx)
103 })
104 .ok()?
105 .await;
106
107 Some(TaskContext {
108 cwd: worktree_abs_path.map(|p| p.to_path_buf()),
109 project_env: project_env.unwrap_or_default(),
110 ..TaskContext::default()
111 })
112}
113
114pub fn lsp_tasks(
115 project: Entity<Project>,
116 task_sources: &HashMap<LanguageServerName, Vec<BufferId>>,
117 for_position: Option<text::Anchor>,
118 cx: &mut App,
119) -> Task<Vec<(TaskSourceKind, Vec<(Option<LocationLink>, ResolvedTask)>)>> {
120 let mut lsp_task_sources = task_sources
121 .iter()
122 .map(|(name, buffer_ids)| {
123 let buffers = buffer_ids
124 .iter()
125 .filter(|&&buffer_id| match for_position {
126 Some(for_position) => for_position.buffer_id == Some(buffer_id),
127 None => true,
128 })
129 .filter_map(|&buffer_id| project.read(cx).buffer_for_id(buffer_id, cx))
130 .collect::<Vec<_>>();
131 language_server_for_buffers(project.clone(), name.clone(), buffers, cx)
132 })
133 .collect::<FuturesUnordered<_>>();
134
135 cx.spawn(async move |cx| {
136 cx.spawn(async move |cx| {
137 let mut lsp_tasks = HashMap::default();
138 while let Some(server_to_query) = lsp_task_sources.next().await {
139 if let Some((server_id, buffers)) = server_to_query {
140 let mut new_lsp_tasks = Vec::new();
141 for buffer in buffers {
142 let source_kind = match buffer.update(cx, |buffer, _| {
143 buffer.language().map(|language| language.name())
144 }) {
145 Ok(Some(language_name)) => TaskSourceKind::Lsp {
146 server: server_id,
147 language_name: SharedString::from(language_name),
148 },
149 Ok(None) => continue,
150 Err(_) => return Vec::new(),
151 };
152 let id_base = source_kind.to_id_base();
153 let lsp_buffer_context = lsp_task_context(&project, &buffer, cx)
154 .await
155 .unwrap_or_default();
156
157 if let Ok(runnables_task) = project.update(cx, |project, cx| {
158 let buffer_id = buffer.read(cx).remote_id();
159 project.request_lsp(
160 buffer,
161 LanguageServerToQuery::Other(server_id),
162 GetLspRunnables {
163 buffer_id,
164 position: for_position,
165 },
166 cx,
167 )
168 }) {
169 if let Some(new_runnables) = runnables_task.await.log_err() {
170 new_lsp_tasks.extend(
171 new_runnables.runnables.into_iter().filter_map(
172 |(location, runnable)| {
173 let resolved_task = runnable
174 .resolve_task(&id_base, &lsp_buffer_context)?;
175 Some((location, resolved_task))
176 },
177 ),
178 );
179 }
180 }
181 lsp_tasks
182 .entry(source_kind)
183 .or_insert_with(Vec::new)
184 .append(&mut new_lsp_tasks);
185 }
186 }
187 }
188 lsp_tasks.into_iter().collect()
189 })
190 .race({
191 // `lsp::LSP_REQUEST_TIMEOUT` is larger than we want for the modal to open fast
192 let timer = cx.background_executor().timer(Duration::from_millis(200));
193 async move {
194 timer.await;
195 log::info!("Timed out waiting for LSP tasks");
196 Vec::new()
197 }
198 })
199 .await
200 })
201}
202
203fn language_server_for_buffers(
204 project: Entity<Project>,
205 name: LanguageServerName,
206 candidates: Vec<Entity<Buffer>>,
207 cx: &mut App,
208) -> Task<Option<(LanguageServerId, Vec<Entity<Buffer>>)>> {
209 cx.spawn(async move |cx| {
210 for buffer in &candidates {
211 let server_id = buffer
212 .update(cx, |buffer, cx| {
213 project.update(cx, |project, cx| {
214 project.language_server_id_for_name(buffer, &name.0, cx)
215 })
216 })
217 .ok()?
218 .await;
219 if let Some(server_id) = server_id {
220 return Some((server_id, candidates));
221 }
222 }
223 None
224 })
225}