1//! This module defines an LSP Tree.
2//!
3//! An LSP Tree is responsible for determining which language servers apply to a given project path.
4//!
5//! ## RPC
6//! LSP Tree is transparent to RPC peers; when clients ask host to spawn a new language server, the host will perform LSP Tree lookup for provided path; it may decide
7//! to reuse existing language server. The client maintains it's own LSP Tree that is a subset of host LSP Tree. Done this way, the client does not need to
8//! ask about suitable language server for each path it interacts with; it can resolve most of the queries locally.
9//! This module defines a Project Tree.
10
11use std::{
12 collections::{BTreeMap, BTreeSet},
13 path::Path,
14 sync::{Arc, Weak},
15};
16
17use collections::{HashMap, IndexMap};
18use gpui::{App, AppContext as _, Entity, Subscription};
19use itertools::Itertools;
20use language::{
21 language_settings::AllLanguageSettings, Attach, LanguageName, LanguageRegistry,
22 LspAdapterDelegate,
23};
24use lsp::LanguageServerName;
25use settings::{Settings, SettingsLocation, WorktreeId};
26use std::sync::OnceLock;
27
28use crate::{project_settings::LspSettings, LanguageServerId, ProjectPath};
29
30use super::{AdapterWrapper, ProjectTree, ProjectTreeEvent};
31
32#[derive(Debug, Default)]
33struct ServersForWorktree {
34 roots: BTreeMap<
35 Arc<Path>,
36 BTreeMap<LanguageServerName, (Arc<InnerTreeNode>, BTreeSet<LanguageName>)>,
37 >,
38}
39
40pub struct LanguageServerTree {
41 project_tree: Entity<ProjectTree>,
42 instances: BTreeMap<WorktreeId, ServersForWorktree>,
43 attach_kind_cache: HashMap<LanguageServerName, Attach>,
44 languages: Arc<LanguageRegistry>,
45 _subscriptions: Subscription,
46}
47
48/// A node in language server tree represents either:
49/// - A language server that has already been initialized/updated for a given project
50/// - A soon-to-be-initialized language server.
51#[derive(Clone)]
52pub(crate) struct LanguageServerTreeNode(Weak<InnerTreeNode>);
53
54/// Describes a request to launch a language server.
55#[derive(Debug)]
56pub(crate) struct LaunchDisposition<'a> {
57 pub(crate) server_name: &'a LanguageServerName,
58 pub(crate) attach: Attach,
59 pub(crate) path: ProjectPath,
60 pub(crate) settings: Arc<LspSettings>,
61}
62
63impl<'a> From<&'a InnerTreeNode> for LaunchDisposition<'a> {
64 fn from(value: &'a InnerTreeNode) -> Self {
65 LaunchDisposition {
66 server_name: &value.name,
67 attach: value.attach,
68 path: value.path.clone(),
69 settings: value.settings.clone(),
70 }
71 }
72}
73impl LanguageServerTreeNode {
74 /// Returns a language server ID for this node if there is one.
75 /// Returns None if this node has not been initialized yet or it is no longer in the tree.
76 pub(crate) fn server_id(&self) -> Option<LanguageServerId> {
77 self.0.upgrade()?.id.get().copied()
78 }
79 /// Returns a language server ID for this node if it has already been initialized; otherwise runs the provided closure to initialize the language server node in a tree.
80 /// May return None if the node no longer belongs to the server tree it was created in.
81 pub(crate) fn server_id_or_init(
82 &self,
83 init: impl FnOnce(LaunchDisposition) -> LanguageServerId,
84 ) -> Option<LanguageServerId> {
85 let this = self.0.upgrade()?;
86 Some(
87 *this
88 .id
89 .get_or_init(|| init(LaunchDisposition::from(&*this))),
90 )
91 }
92}
93
94impl From<Weak<InnerTreeNode>> for LanguageServerTreeNode {
95 fn from(weak: Weak<InnerTreeNode>) -> Self {
96 LanguageServerTreeNode(weak)
97 }
98}
99
100#[derive(Debug)]
101struct InnerTreeNode {
102 id: OnceLock<LanguageServerId>,
103 name: LanguageServerName,
104 attach: Attach,
105 path: ProjectPath,
106 settings: Arc<LspSettings>,
107}
108
109impl InnerTreeNode {
110 fn new(
111 name: LanguageServerName,
112 attach: Attach,
113 path: ProjectPath,
114 settings: impl Into<Arc<LspSettings>>,
115 ) -> Self {
116 InnerTreeNode {
117 id: Default::default(),
118 name,
119 attach,
120 path,
121 settings: settings.into(),
122 }
123 }
124}
125
126/// Determines how the list of adapters to query should be constructed.
127pub(crate) enum AdapterQuery<'a> {
128 /// Search for roots of all adapters associated with a given language name.
129 Language(&'a LanguageName),
130 /// Search for roots of adapter with a given name.
131 Adapter(&'a LanguageServerName),
132}
133
134impl LanguageServerTree {
135 pub(crate) fn new(
136 project_tree: Entity<ProjectTree>,
137 languages: Arc<LanguageRegistry>,
138 cx: &mut App,
139 ) -> Entity<Self> {
140 cx.new(|cx| Self {
141 _subscriptions: cx.subscribe(
142 &project_tree,
143 |_: &mut Self, _, event, _| {
144 if event == &ProjectTreeEvent::Cleared {}
145 },
146 ),
147 project_tree,
148 instances: Default::default(),
149 attach_kind_cache: Default::default(),
150 languages,
151 })
152 }
153 /// Memoize calls to attach_kind on LspAdapter (which might be a WASM extension, thus ~expensive to call).
154 fn attach_kind(&mut self, adapter: &AdapterWrapper) -> Attach {
155 *self
156 .attach_kind_cache
157 .entry(adapter.0.name.clone())
158 .or_insert_with(|| adapter.0.attach_kind())
159 }
160
161 /// Get all language server root points for a given path and language; the language servers might already be initialized at a given path.
162 pub(crate) fn get<'a>(
163 &'a mut self,
164 path: ProjectPath,
165 query: AdapterQuery<'_>,
166 delegate: Arc<dyn LspAdapterDelegate>,
167 cx: &mut App,
168 ) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
169 let settings_location = SettingsLocation {
170 worktree_id: path.worktree_id,
171 path: &path.path,
172 };
173 let adapters = match query {
174 AdapterQuery::Language(language_name) => {
175 self.adapters_for_language(settings_location, language_name, cx)
176 }
177 AdapterQuery::Adapter(language_server_name) => IndexMap::from_iter(
178 self.adapter_for_name(language_server_name)
179 .map(|adapter| (adapter, (LspSettings::default(), BTreeSet::new()))),
180 ),
181 };
182 self.get_with_adapters(path, adapters, delegate, cx)
183 }
184
185 fn get_with_adapters<'a>(
186 &'a mut self,
187 path: ProjectPath,
188 adapters: IndexMap<AdapterWrapper, (LspSettings, BTreeSet<LanguageName>)>,
189 delegate: Arc<dyn LspAdapterDelegate>,
190 cx: &mut App,
191 ) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
192 let worktree_id = path.worktree_id;
193 #[allow(clippy::mutable_key_type)]
194 let mut roots = self.project_tree.update(cx, |this, cx| {
195 this.root_for_path(
196 path,
197 adapters
198 .iter()
199 .map(|(adapter, _)| adapter.0.clone())
200 .collect(),
201 delegate,
202 cx,
203 )
204 });
205 let mut root_path = None;
206 // Backwards-compat: Fill in any adapters for which we did not detect the root as having the project root at the root of a worktree.
207 for (adapter, _) in adapters.iter() {
208 roots.entry(adapter.clone()).or_insert_with(|| {
209 root_path
210 .get_or_insert_with(|| ProjectPath {
211 worktree_id,
212 path: Arc::from("".as_ref()),
213 })
214 .clone()
215 });
216 }
217
218 roots
219 .into_iter()
220 .filter_map(move |(adapter, root_path)| {
221 let attach = self.attach_kind(&adapter);
222 let (index, _, (settings, new_languages)) = adapters.get_full(&adapter)?;
223 let inner_node = self
224 .instances
225 .entry(root_path.worktree_id)
226 .or_default()
227 .roots
228 .entry(root_path.path.clone())
229 .or_default()
230 .entry(adapter.0.name.clone());
231 let (node, languages) = inner_node.or_insert_with(move || {
232 (
233 Arc::new(InnerTreeNode::new(
234 adapter.0.name(),
235 attach,
236 root_path,
237 settings.clone(),
238 )),
239 Default::default(),
240 )
241 });
242 languages.extend(new_languages.iter().cloned());
243 Some((index, Arc::downgrade(&node).into()))
244 })
245 .sorted_by_key(|(index, _)| *index)
246 .map(|(_, node)| node)
247 }
248
249 fn adapter_for_name(&self, name: &LanguageServerName) -> Option<AdapterWrapper> {
250 self.languages.adapter_for_name(name).map(AdapterWrapper)
251 }
252
253 fn adapters_for_language(
254 &self,
255 settings_location: SettingsLocation,
256 language_name: &LanguageName,
257 cx: &App,
258 ) -> IndexMap<AdapterWrapper, (LspSettings, BTreeSet<LanguageName>)> {
259 let settings = AllLanguageSettings::get(Some(settings_location), cx).language(
260 Some(settings_location),
261 Some(language_name),
262 cx,
263 );
264 if !settings.enable_language_server {
265 return Default::default();
266 }
267 let available_lsp_adapters = self.languages.lsp_adapters(&language_name);
268 let available_language_servers = available_lsp_adapters
269 .iter()
270 .map(|lsp_adapter| lsp_adapter.name.clone())
271 .collect::<Vec<_>>();
272
273 let desired_language_servers =
274 settings.customized_language_servers(&available_language_servers);
275 let adapters_with_settings = desired_language_servers
276 .into_iter()
277 .filter_map(|desired_adapter| {
278 let adapter = if let Some(adapter) = available_lsp_adapters
279 .iter()
280 .find(|adapter| adapter.name == desired_adapter)
281 {
282 Some(adapter.clone())
283 } else if let Some(adapter) =
284 self.languages.load_available_lsp_adapter(&desired_adapter)
285 {
286 self.languages
287 .register_lsp_adapter(language_name.clone(), adapter.adapter.clone());
288 Some(adapter)
289 } else {
290 None
291 }?;
292 let adapter_settings = crate::lsp_store::language_server_settings_for(
293 settings_location,
294 &adapter.name,
295 cx,
296 )
297 .cloned()
298 .unwrap_or_default();
299 Some((
300 AdapterWrapper(adapter),
301 (
302 adapter_settings,
303 BTreeSet::from_iter([language_name.clone()]),
304 ),
305 ))
306 })
307 .collect::<IndexMap<_, _>>();
308 // After starting all the language servers, reorder them to reflect the desired order
309 // based on the settings.
310 //
311 // This is done, in part, to ensure that language servers loaded at different points
312 // (e.g., native vs extension) still end up in the right order at the end, rather than
313 // it being based on which language server happened to be loaded in first.
314 self.languages.reorder_language_servers(
315 &language_name,
316 adapters_with_settings
317 .keys()
318 .map(|wrapper| wrapper.0.clone())
319 .collect(),
320 );
321
322 adapters_with_settings
323 }
324
325 // Rebasing a tree:
326 // - Clears it out
327 // - Provides you with the indirect access to the old tree while you're reinitializing a new one (by querying it).
328 pub(crate) fn rebase(&mut self) -> ServerTreeRebase<'_> {
329 ServerTreeRebase::new(self)
330 }
331
332 /// Remove nodes with a given ID from the tree.
333 pub(crate) fn remove_nodes(&mut self, ids: &BTreeSet<LanguageServerId>) {
334 for (_, servers) in &mut self.instances {
335 for (_, nodes) in &mut servers.roots {
336 nodes.retain(|_, (node, _)| node.id.get().map_or(true, |id| !ids.contains(&id)));
337 }
338 }
339 }
340}
341
342pub(crate) struct ServerTreeRebase<'a> {
343 old_contents: BTreeMap<WorktreeId, ServersForWorktree>,
344 new_tree: &'a mut LanguageServerTree,
345 /// All server IDs seen in the old tree.
346 all_server_ids: BTreeMap<LanguageServerId, LanguageServerName>,
347 /// Server IDs we've preserved for a new iteration of the tree. `all_server_ids - rebased_server_ids` is the
348 /// set of server IDs that can be shut down.
349 rebased_server_ids: BTreeSet<LanguageServerId>,
350}
351
352impl<'tree> ServerTreeRebase<'tree> {
353 fn new(new_tree: &'tree mut LanguageServerTree) -> Self {
354 let old_contents = std::mem::take(&mut new_tree.instances);
355 new_tree.attach_kind_cache.clear();
356 let all_server_ids = old_contents
357 .values()
358 .flat_map(|nodes| {
359 nodes.roots.values().flat_map(|servers| {
360 servers.values().filter_map(|server| {
361 server
362 .0
363 .id
364 .get()
365 .copied()
366 .map(|id| (id, server.0.name.clone()))
367 })
368 })
369 })
370 .collect();
371 Self {
372 old_contents,
373 new_tree,
374 all_server_ids,
375 rebased_server_ids: BTreeSet::new(),
376 }
377 }
378
379 pub(crate) fn get<'a>(
380 &'a mut self,
381 path: ProjectPath,
382 query: AdapterQuery<'_>,
383 delegate: Arc<dyn LspAdapterDelegate>,
384 cx: &mut App,
385 ) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
386 let settings_location = SettingsLocation {
387 worktree_id: path.worktree_id,
388 path: &path.path,
389 };
390 let adapters = match query {
391 AdapterQuery::Language(language_name) => {
392 self.new_tree
393 .adapters_for_language(settings_location, language_name, cx)
394 }
395 AdapterQuery::Adapter(language_server_name) => IndexMap::from_iter(
396 self.new_tree
397 .adapter_for_name(language_server_name)
398 .map(|adapter| (adapter, (LspSettings::default(), BTreeSet::new()))),
399 ),
400 };
401
402 self.new_tree
403 .get_with_adapters(path, adapters, delegate, cx)
404 .filter_map(|node| {
405 // Inspect result of the query and initialize it ourselves before
406 // handing it off to the caller.
407 let disposition = node.0.upgrade()?;
408
409 if disposition.id.get().is_some() {
410 return Some(node);
411 }
412 let Some((existing_node, _)) = self
413 .old_contents
414 .get(&disposition.path.worktree_id)
415 .and_then(|worktree_nodes| worktree_nodes.roots.get(&disposition.path.path))
416 .and_then(|roots| roots.get(&disposition.name))
417 .filter(|(old_node, _)| {
418 disposition.attach == old_node.attach
419 && disposition.settings == old_node.settings
420 })
421 else {
422 return Some(node);
423 };
424 if let Some(existing_id) = existing_node.id.get() {
425 self.rebased_server_ids.insert(*existing_id);
426 disposition.id.set(*existing_id).ok();
427 }
428
429 Some(node)
430 })
431 }
432
433 /// Returns IDs of servers that are no longer referenced (and can be shut down).
434 pub(crate) fn finish(self) -> BTreeMap<LanguageServerId, LanguageServerName> {
435 self.all_server_ids
436 .into_iter()
437 .filter(|(id, _)| !self.rebased_server_ids.contains(id))
438 .collect()
439 }
440}