project_tree.rs

  1//! This module defines a Project Tree.
  2//!
  3//! A Project Tree is responsible for determining where the roots of subprojects are located in a project.
  4
  5mod path_trie;
  6mod server_tree;
  7
  8use std::{
  9    borrow::Borrow,
 10    collections::{hash_map::Entry, BTreeMap},
 11    ops::ControlFlow,
 12    sync::Arc,
 13};
 14
 15use collections::HashMap;
 16use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription};
 17use language::{CachedLspAdapter, LspAdapterDelegate};
 18use lsp::LanguageServerName;
 19use path_trie::{LabelPresence, RootPathTrie, TriePath};
 20use settings::{SettingsStore, WorktreeId};
 21use worktree::{Event as WorktreeEvent, Worktree};
 22
 23use crate::{
 24    worktree_store::{WorktreeStore, WorktreeStoreEvent},
 25    ProjectPath,
 26};
 27
 28pub(crate) use server_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition};
 29
 30struct WorktreeRoots {
 31    roots: RootPathTrie<LanguageServerName>,
 32    worktree_store: Entity<WorktreeStore>,
 33    _worktree_subscription: Subscription,
 34}
 35
 36impl WorktreeRoots {
 37    fn new(
 38        worktree_store: Entity<WorktreeStore>,
 39        worktree: Entity<Worktree>,
 40        cx: &mut App,
 41    ) -> Entity<Self> {
 42        cx.new(|cx| Self {
 43            roots: RootPathTrie::new(),
 44            worktree_store,
 45            _worktree_subscription: cx.subscribe(&worktree, |this: &mut Self, _, event, cx| {
 46                match event {
 47                    WorktreeEvent::UpdatedEntries(changes) => {
 48                        for (path, _, kind) in changes.iter() {
 49                            match kind {
 50                                worktree::PathChange::Removed => {
 51                                    let path = TriePath::from(path.as_ref());
 52                                    this.roots.remove(&path);
 53                                }
 54                                _ => {}
 55                            }
 56                        }
 57                    }
 58                    WorktreeEvent::UpdatedGitRepositories(_) => {}
 59                    WorktreeEvent::DeletedEntry(entry_id) => {
 60                        let Some(entry) = this.worktree_store.read(cx).entry_for_id(*entry_id, cx)
 61                        else {
 62                            return;
 63                        };
 64                        let path = TriePath::from(entry.path.as_ref());
 65                        this.roots.remove(&path);
 66                    }
 67                }
 68            }),
 69        })
 70    }
 71}
 72
 73pub struct ProjectTree {
 74    root_points: HashMap<WorktreeId, Entity<WorktreeRoots>>,
 75    worktree_store: Entity<WorktreeStore>,
 76    _subscriptions: [Subscription; 2],
 77}
 78
 79#[derive(Debug, Clone)]
 80struct AdapterWrapper(Arc<CachedLspAdapter>);
 81impl PartialEq for AdapterWrapper {
 82    fn eq(&self, other: &Self) -> bool {
 83        self.0.name.eq(&other.0.name)
 84    }
 85}
 86
 87impl Eq for AdapterWrapper {}
 88
 89impl std::hash::Hash for AdapterWrapper {
 90    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
 91        self.0.name.hash(state);
 92    }
 93}
 94
 95impl PartialOrd for AdapterWrapper {
 96    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
 97        Some(self.0.name.cmp(&other.0.name))
 98    }
 99}
100
101impl Ord for AdapterWrapper {
102    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
103        self.0.name.cmp(&other.0.name)
104    }
105}
106
107impl Borrow<LanguageServerName> for AdapterWrapper {
108    fn borrow(&self) -> &LanguageServerName {
109        &self.0.name
110    }
111}
112
113#[derive(PartialEq)]
114pub(crate) enum ProjectTreeEvent {
115    WorktreeRemoved(WorktreeId),
116    Cleared,
117}
118
119impl EventEmitter<ProjectTreeEvent> for ProjectTree {}
120
121impl ProjectTree {
122    pub(crate) fn new(worktree_store: Entity<WorktreeStore>, cx: &mut App) -> Entity<Self> {
123        cx.new(|cx| Self {
124            root_points: Default::default(),
125            _subscriptions: [
126                cx.subscribe(&worktree_store, Self::on_worktree_store_event),
127                cx.observe_global::<SettingsStore>(|this, cx| {
128                    for (_, roots) in &mut this.root_points {
129                        roots.update(cx, |worktree_roots, _| {
130                            worktree_roots.roots = RootPathTrie::new();
131                        })
132                    }
133                    cx.emit(ProjectTreeEvent::Cleared);
134                }),
135            ],
136            worktree_store,
137        })
138    }
139    #[allow(clippy::mutable_key_type)]
140    fn root_for_path(
141        &mut self,
142        ProjectPath { worktree_id, path }: ProjectPath,
143        adapters: Vec<Arc<CachedLspAdapter>>,
144        delegate: Arc<dyn LspAdapterDelegate>,
145        cx: &mut App,
146    ) -> BTreeMap<AdapterWrapper, ProjectPath> {
147        debug_assert_eq!(delegate.worktree_id(), worktree_id);
148        #[allow(clippy::mutable_key_type)]
149        let mut roots = BTreeMap::from_iter(
150            adapters
151                .into_iter()
152                .map(|adapter| (AdapterWrapper(adapter), (None, LabelPresence::KnownAbsent))),
153        );
154        let worktree_roots = match self.root_points.entry(worktree_id) {
155            Entry::Occupied(occupied_entry) => occupied_entry.get().clone(),
156            Entry::Vacant(vacant_entry) => {
157                let Some(worktree) = self
158                    .worktree_store
159                    .read(cx)
160                    .worktree_for_id(worktree_id, cx)
161                else {
162                    return Default::default();
163                };
164                let roots = WorktreeRoots::new(self.worktree_store.clone(), worktree, cx);
165                vacant_entry.insert(roots).clone()
166            }
167        };
168
169        let key = TriePath::from(&*path);
170        worktree_roots.update(cx, |this, _| {
171            this.roots.walk(&key, &mut |path, labels| {
172                for (label, presence) in labels {
173                    if let Some((marked_path, current_presence)) = roots.get_mut(label) {
174                        if *current_presence > *presence {
175                            debug_assert!(false, "RootPathTrie precondition violation; while walking the tree label presence is only allowed to increase");
176                        }
177                        *marked_path = Some(ProjectPath {worktree_id, path: path.clone()});
178                        *current_presence = *presence;
179                    }
180
181                }
182                ControlFlow::Continue(())
183            });
184        });
185        for (adapter, (root_path, presence)) in &mut roots {
186            if *presence == LabelPresence::Present {
187                continue;
188            }
189
190            let depth = root_path
191                .as_ref()
192                .map(|root_path| {
193                    path.strip_prefix(&root_path.path)
194                        .unwrap()
195                        .components()
196                        .count()
197                })
198                .unwrap_or_else(|| path.components().count() + 1);
199
200            if depth > 0 {
201                let root = adapter.0.find_project_root(&path, depth, &delegate);
202                match root {
203                    Some(known_root) => worktree_roots.update(cx, |this, _| {
204                        let root = TriePath::from(&*known_root);
205                        this.roots
206                            .insert(&root, adapter.0.name(), LabelPresence::Present);
207                        *presence = LabelPresence::Present;
208                        *root_path = Some(ProjectPath {
209                            worktree_id,
210                            path: known_root,
211                        });
212                    }),
213                    None => worktree_roots.update(cx, |this, _| {
214                        this.roots
215                            .insert(&key, adapter.0.name(), LabelPresence::KnownAbsent);
216                    }),
217                }
218            }
219        }
220
221        roots
222            .into_iter()
223            .filter_map(|(k, (path, presence))| {
224                let path = path?;
225                presence.eq(&LabelPresence::Present).then(|| (k, path))
226            })
227            .collect()
228    }
229    fn on_worktree_store_event(
230        &mut self,
231        _: Entity<WorktreeStore>,
232        evt: &WorktreeStoreEvent,
233        cx: &mut Context<Self>,
234    ) {
235        match evt {
236            WorktreeStoreEvent::WorktreeRemoved(_, worktree_id) => {
237                self.root_points.remove(&worktree_id);
238                cx.emit(ProjectTreeEvent::WorktreeRemoved(*worktree_id));
239            }
240            _ => {}
241        }
242    }
243}