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_map(|selection| Some((selection.head(), selection.head().buffer_id?)))
 46        .unique_by(|(_, buffer_id)| *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        .collect::<Vec<_>>();
 57
 58    let applicable_buffer_tasks = applicable_buffers
 59        .into_iter()
 60        .map(|(trigger_anchor, buffer, language)| {
 61            let task = buffer.update(cx, |buffer, cx| {
 62                project.update(cx, |project, cx| {
 63                    project.language_server_id_for_name(buffer, language_server_name, cx)
 64                })
 65            });
 66            (trigger_anchor, buffer, language, task)
 67        })
 68        .collect::<Vec<_>>();
 69    cx.background_spawn(async move {
 70        for (trigger_anchor, buffer, language, task) in applicable_buffer_tasks {
 71            if let Some(server_id) = task.await {
 72                return Some((trigger_anchor, language, server_id, buffer));
 73            }
 74        }
 75
 76        None
 77    })
 78}
 79
 80async fn lsp_task_context(
 81    project: &Entity<Project>,
 82    buffer: &Entity<Buffer>,
 83    cx: &mut AsyncApp,
 84) -> Option<TaskContext> {
 85    let worktree_store = project
 86        .read_with(cx, |project, _| project.worktree_store())
 87        .ok()?;
 88
 89    let worktree_abs_path = cx
 90        .update(|cx| {
 91            let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx));
 92
 93            worktree_id
 94                .and_then(|worktree_id| worktree_store.read(cx).worktree_for_id(worktree_id, cx))
 95                .and_then(|worktree| worktree.read(cx).root_dir())
 96        })
 97        .ok()?;
 98
 99    let project_env = project
100        .update(cx, |project, cx| {
101            project.buffer_environment(&buffer, &worktree_store, cx)
102        })
103        .ok()?
104        .await;
105
106    Some(TaskContext {
107        cwd: worktree_abs_path.map(|p| p.to_path_buf()),
108        project_env: project_env.unwrap_or_default(),
109        ..TaskContext::default()
110    })
111}
112
113pub fn lsp_tasks(
114    project: Entity<Project>,
115    task_sources: &HashMap<LanguageServerName, Vec<BufferId>>,
116    for_position: Option<text::Anchor>,
117    cx: &mut App,
118) -> Task<Vec<(TaskSourceKind, Vec<(Option<LocationLink>, ResolvedTask)>)>> {
119    let mut lsp_task_sources = task_sources
120        .iter()
121        .map(|(name, buffer_ids)| {
122            let buffers = buffer_ids
123                .iter()
124                .filter(|&&buffer_id| match for_position {
125                    Some(for_position) => for_position.buffer_id == Some(buffer_id),
126                    None => true,
127                })
128                .filter_map(|&buffer_id| project.read(cx).buffer_for_id(buffer_id, cx))
129                .collect::<Vec<_>>();
130            language_server_for_buffers(project.clone(), name.clone(), buffers, cx)
131        })
132        .collect::<FuturesUnordered<_>>();
133
134    cx.spawn(async move |cx| {
135        cx.spawn(async move |cx| {
136            let mut lsp_tasks = HashMap::default();
137            while let Some(server_to_query) = lsp_task_sources.next().await {
138                if let Some((server_id, buffers)) = server_to_query {
139                    let mut new_lsp_tasks = Vec::new();
140                    for buffer in buffers {
141                        let source_kind = match buffer.update(cx, |buffer, _| {
142                            buffer.language().map(|language| language.name())
143                        }) {
144                            Ok(Some(language_name)) => TaskSourceKind::Lsp {
145                                server: server_id,
146                                language_name: SharedString::from(language_name),
147                            },
148                            Ok(None) => continue,
149                            Err(_) => return Vec::new(),
150                        };
151                        let id_base = source_kind.to_id_base();
152                        let lsp_buffer_context = lsp_task_context(&project, &buffer, cx)
153                            .await
154                            .unwrap_or_default();
155
156                        if let Ok(runnables_task) = project.update(cx, |project, cx| {
157                            let buffer_id = buffer.read(cx).remote_id();
158                            project.request_lsp(
159                                buffer,
160                                LanguageServerToQuery::Other(server_id),
161                                GetLspRunnables {
162                                    buffer_id,
163                                    position: for_position,
164                                },
165                                cx,
166                            )
167                        }) {
168                            if let Some(new_runnables) = runnables_task.await.log_err() {
169                                new_lsp_tasks.extend(
170                                    new_runnables.runnables.into_iter().filter_map(
171                                        |(location, runnable)| {
172                                            let resolved_task = runnable
173                                                .resolve_task(&id_base, &lsp_buffer_context)?;
174                                            Some((location, resolved_task))
175                                        },
176                                    ),
177                                );
178                            }
179                        }
180                        lsp_tasks
181                            .entry(source_kind)
182                            .or_insert_with(Vec::new)
183                            .append(&mut new_lsp_tasks);
184                    }
185                }
186            }
187            lsp_tasks.into_iter().collect()
188        })
189        .race({
190            // `lsp::LSP_REQUEST_TIMEOUT` is larger than we want for the modal to open fast
191            let timer = cx.background_executor().timer(Duration::from_millis(200));
192            async move {
193                timer.await;
194                log::info!("Timed out waiting for LSP tasks");
195                Vec::new()
196            }
197        })
198        .await
199    })
200}
201
202fn language_server_for_buffers(
203    project: Entity<Project>,
204    name: LanguageServerName,
205    candidates: Vec<Entity<Buffer>>,
206    cx: &mut App,
207) -> Task<Option<(LanguageServerId, Vec<Entity<Buffer>>)>> {
208    cx.spawn(async move |cx| {
209        for buffer in &candidates {
210            let server_id = buffer
211                .update(cx, |buffer, cx| {
212                    project.update(cx, |project, cx| {
213                        project.language_server_id_for_name(buffer, &name.0, cx)
214                    })
215                })
216                .ok()?
217                .await;
218            if let Some(server_id) = server_id {
219                return Some((server_id, candidates));
220            }
221        }
222        None
223    })
224}