lsp_ext.rs

  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}