lsp_ext.rs

  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 multi_buffer::Anchor;
 13use project::LanguageServerToQuery;
 14use project::LocationLink;
 15use project::Project;
 16use project::TaskSourceKind;
 17use project::lsp_store::lsp_ext_command::GetLspRunnables;
 18use smol::future::FutureExt as _;
 19use task::ResolvedTask;
 20use task::TaskContext;
 21use text::BufferId;
 22use ui::SharedString;
 23use util::ResultExt as _;
 24
 25pub(crate) fn find_specific_language_server_in_selection<F>(
 26    editor: &Editor,
 27    cx: &mut App,
 28    filter_language: F,
 29    language_server_name: LanguageServerName,
 30) -> Option<(Anchor, Arc<Language>, LanguageServerId, Entity<Buffer>)>
 31where
 32    F: Fn(&Language) -> bool,
 33{
 34    let project = editor.project.clone()?;
 35    let multi_buffer = editor.buffer();
 36    let mut seen_buffer_ids = HashSet::default();
 37    editor
 38        .selections
 39        .disjoint_anchors_arc()
 40        .iter()
 41        .find_map(|selection| {
 42            let multi_buffer = multi_buffer.read(cx);
 43            let (position, buffer) = multi_buffer
 44                .buffer_for_anchor(selection.head(), cx)
 45                .map(|buffer| (selection.head(), buffer))
 46                .or_else(|| {
 47                    multi_buffer
 48                        .buffer_for_anchor(selection.tail(), cx)
 49                        .map(|buffer| (selection.tail(), buffer))
 50                })?;
 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.text_anchor)?;
 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 == Some(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::{Anchor, 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            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}