1use std::sync::Arc;
2use std::time::Duration;
3
4use crate::Editor;
5use collections::{HashMap, HashSet};
6use gpui::AsyncApp;
7use gpui::{App, Entity, Task};
8use language::Buffer;
9use language::Language;
10use lsp::LanguageServerId;
11use lsp::LanguageServerName;
12use project::LanguageServerToQuery;
13use project::LocationLink;
14use project::Project;
15use project::TaskSourceKind;
16use project::lsp_store::lsp_ext_command::GetLspRunnables;
17use smol::future::FutureExt as _;
18use task::ResolvedTask;
19use task::TaskContext;
20use text::BufferId;
21use ui::SharedString;
22use util::ResultExt as _;
23
24pub(crate) fn find_specific_language_server_in_selection<F>(
25 editor: &Editor,
26 cx: &mut App,
27 filter_language: F,
28 language_server_name: LanguageServerName,
29) -> Option<(
30 text::Anchor,
31 Arc<Language>,
32 LanguageServerId,
33 Entity<Buffer>,
34)>
35where
36 F: Fn(&Language) -> bool,
37{
38 let project = editor.project.clone()?;
39 let multi_buffer = editor.buffer();
40 let mut seen_buffer_ids = HashSet::default();
41 editor
42 .selections
43 .disjoint_anchors_arc()
44 .iter()
45 .find_map(|selection| {
46 let multi_buffer = multi_buffer.read(cx);
47 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
48 let (position, buffer) = multi_buffer_snapshot
49 .anchor_to_buffer_anchor(selection.head())
50 .and_then(|(anchor, _)| Some((anchor, multi_buffer.buffer(anchor.buffer_id)?)))?;
51 if !seen_buffer_ids.insert(buffer.read(cx).remote_id()) {
52 return None;
53 }
54
55 let language = buffer.read(cx).language_at(position)?;
56 if filter_language(&language) {
57 let server_id = buffer.update(cx, |buffer, cx| {
58 project
59 .read(cx)
60 .language_server_id_for_name(buffer, &language_server_name, cx)
61 })?;
62 Some((position, language, server_id, buffer))
63 } else {
64 None
65 }
66 })
67}
68
69async fn lsp_task_context(
70 project: &Entity<Project>,
71 buffer: &Entity<Buffer>,
72 cx: &mut AsyncApp,
73) -> Option<TaskContext> {
74 let (worktree_store, environment) = project.read_with(cx, |project, _| {
75 (project.worktree_store(), project.environment().clone())
76 });
77
78 let worktree_abs_path = cx.update(|cx| {
79 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx));
80
81 worktree_id
82 .and_then(|worktree_id| worktree_store.read(cx).worktree_for_id(worktree_id, cx))
83 .and_then(|worktree| worktree.read(cx).root_dir())
84 });
85
86 let project_env = environment
87 .update(cx, |environment, cx| {
88 environment.buffer_environment(buffer, &worktree_store, cx)
89 })
90 .await;
91
92 Some(TaskContext {
93 cwd: worktree_abs_path.map(|p| p.to_path_buf()),
94 project_env: project_env.unwrap_or_default(),
95 ..TaskContext::default()
96 })
97}
98
99pub fn lsp_tasks(
100 project: Entity<Project>,
101 task_sources: &HashMap<LanguageServerName, Vec<BufferId>>,
102 for_position: Option<text::Anchor>,
103 cx: &mut App,
104) -> Task<Vec<(TaskSourceKind, Vec<(Option<LocationLink>, ResolvedTask)>)>> {
105 let lsp_task_sources = task_sources
106 .iter()
107 .filter_map(|(name, buffer_ids)| {
108 let buffers = buffer_ids
109 .iter()
110 .filter(|&&buffer_id| match for_position {
111 Some(for_position) => for_position.buffer_id == buffer_id,
112 None => true,
113 })
114 .filter_map(|&buffer_id| project.read(cx).buffer_for_id(buffer_id, cx))
115 .collect::<Vec<_>>();
116
117 let server_id = buffers.iter().find_map(|buffer| {
118 project.read_with(cx, |project, cx| {
119 project.language_server_id_for_name(buffer.read(cx), name, cx)
120 })
121 });
122 server_id.zip(Some(buffers))
123 })
124 .collect::<Vec<_>>();
125
126 cx.spawn(async move |cx| {
127 cx.spawn(async move |cx| {
128 let mut lsp_tasks = HashMap::default();
129 for (server_id, buffers) in lsp_task_sources {
130 let mut new_lsp_tasks = Vec::new();
131 for buffer in buffers {
132 let source_kind = match buffer.update(cx, |buffer, _| {
133 buffer.language().map(|language| language.name())
134 }) {
135 Some(language_name) => TaskSourceKind::Lsp {
136 server: server_id,
137 language_name: SharedString::from(language_name),
138 },
139 None => continue,
140 };
141 let id_base = source_kind.to_id_base();
142 let lsp_buffer_context = lsp_task_context(&project, &buffer, cx)
143 .await
144 .unwrap_or_default();
145
146 let runnables_task = project.update(cx, |project, cx| {
147 let buffer_id = buffer.read(cx).remote_id();
148 project.request_lsp(
149 buffer,
150 LanguageServerToQuery::Other(server_id),
151 GetLspRunnables {
152 buffer_id,
153 position: for_position,
154 },
155 cx,
156 )
157 });
158 if let Some(new_runnables) = runnables_task.await.log_err() {
159 new_lsp_tasks.extend(new_runnables.runnables.into_iter().filter_map(
160 |(location, runnable)| {
161 let resolved_task =
162 runnable.resolve_task(&id_base, &lsp_buffer_context)?;
163 Some((location, resolved_task))
164 },
165 ));
166 }
167 lsp_tasks
168 .entry(source_kind)
169 .or_insert_with(Vec::new)
170 .append(&mut new_lsp_tasks);
171 }
172 }
173 lsp_tasks.into_iter().collect()
174 })
175 .race({
176 // `lsp::DEFAULT_LSP_REQUEST_TIMEOUT` is larger than we want for the modal to open fast
177 let timer = cx.background_executor().timer(Duration::from_millis(200));
178 async move {
179 timer.await;
180 log::info!("Timed out waiting for LSP tasks");
181 Vec::new()
182 }
183 })
184 .await
185 })
186}
187
188#[cfg(test)]
189mod tests {
190 use std::sync::Arc;
191
192 use futures::StreamExt as _;
193 use gpui::{AppContext as _, Entity, TestAppContext};
194 use language::{FakeLspAdapter, Language};
195 use languages::rust_lang;
196 use lsp::{LanguageServerId, LanguageServerName};
197 use multi_buffer::MultiBuffer;
198 use project::{FakeFs, Project};
199 use util::path;
200
201 use crate::{MoveToEnd, editor_tests::init_test, test::build_editor_with_project};
202
203 use super::find_specific_language_server_in_selection;
204
205 #[gpui::test]
206 async fn test_find_language_server_at_end_of_file(cx: &mut TestAppContext) {
207 init_test(cx, |_| {});
208
209 let fs = FakeFs::new(cx.executor());
210 fs.insert_file(path!("/file.rs"), "fn main() {}".into())
211 .await;
212
213 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
214 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
215 language_registry.add(rust_lang());
216 let mut fake_servers =
217 language_registry.register_fake_lsp("Rust", FakeLspAdapter::default());
218
219 let underlying_buffer = project
220 .update(cx, |project, cx| {
221 project.open_local_buffer(path!("/file.rs"), cx)
222 })
223 .await
224 .unwrap();
225
226 let buffer = cx.new(|cx| MultiBuffer::singleton(underlying_buffer.clone(), cx));
227 let (editor, cx) = cx.add_window_view(|window, cx| {
228 build_editor_with_project(project.clone(), buffer, window, cx)
229 });
230
231 let fake_server = fake_servers.next().await.unwrap();
232 cx.executor().run_until_parked();
233
234 let expected_server_id = fake_server.server.server_id();
235 let language_server_name = LanguageServerName::new_static("the-fake-language-server");
236 let filter = |language: &Language| language.name().as_ref() == "Rust";
237
238 let assert_result = |result: Option<(
239 text::Anchor,
240 Arc<Language>,
241 LanguageServerId,
242 Entity<language::Buffer>,
243 )>,
244 message: &str| {
245 let (_, language, server_id, buffer) = result.expect(message);
246 assert_eq!(
247 language.name().as_ref(),
248 "Rust",
249 "{message}: wrong language"
250 );
251 assert_eq!(server_id, expected_server_id, "{message}: wrong server ID");
252 assert_eq!(buffer, underlying_buffer, "{message}: wrong buffer");
253 };
254
255 editor.update(cx, |editor, cx| {
256 assert_result(
257 find_specific_language_server_in_selection(
258 editor,
259 cx,
260 filter,
261 language_server_name.clone(),
262 ),
263 "should find correct language server at beginning of file",
264 );
265 });
266
267 editor.update_in(cx, |editor, window, cx| {
268 editor.move_to_end(&MoveToEnd, window, cx);
269 });
270
271 editor.update(cx, |editor, cx| {
272 assert_result(
273 find_specific_language_server_in_selection(
274 editor,
275 cx,
276 filter,
277 language_server_name.clone(),
278 ),
279 "should find correct language server at end of file",
280 );
281 });
282 }
283}