Skip to main content

lspkit_server/
workspace.rs

1//! Multi-root workspace abstraction.
2//!
3//! Modern LSPs support multi-root workspaces (a single client may open many
4//! folders at once). This type tracks the configured roots and exposes
5//! folder-scoped iteration so handlers can route queries correctly.
6
7use std::path::{Path, PathBuf};
8use std::sync::{Arc, RwLock};
9
10/// A named workspace folder.
11#[non_exhaustive]
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub struct WorkspaceFolder {
14    /// Absolute root path.
15    pub root: PathBuf,
16    /// Human-readable name supplied by the client.
17    pub name: String,
18}
19
20impl WorkspaceFolder {
21    /// Construct a folder.
22    #[must_use]
23    pub fn new(root: impl Into<PathBuf>, name: impl Into<String>) -> Self {
24        Self {
25            root: root.into(),
26            name: name.into(),
27        }
28    }
29}
30
31/// Multi-root workspace state.
32#[derive(Debug, Clone, Default)]
33pub struct Workspace {
34    folders: Arc<RwLock<Vec<WorkspaceFolder>>>,
35}
36
37impl Workspace {
38    /// New empty workspace.
39    #[must_use]
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    /// Replace the folder set wholesale (used at `initialize` time).
45    pub fn set_folders(&self, folders: Vec<WorkspaceFolder>) {
46        if let Ok(mut guard) = self.folders.write() {
47            *guard = folders;
48        }
49    }
50
51    /// Append a folder.
52    pub fn add(&self, folder: WorkspaceFolder) {
53        if let Ok(mut guard) = self.folders.write() {
54            guard.push(folder);
55        }
56    }
57
58    /// Remove a folder by root path.
59    pub fn remove(&self, root: &Path) {
60        if let Ok(mut guard) = self.folders.write() {
61            guard.retain(|f| f.root != root);
62        }
63    }
64
65    /// Snapshot the current folder list.
66    #[must_use]
67    pub fn folders(&self) -> Vec<WorkspaceFolder> {
68        self.folders
69            .read()
70            .map_or_else(|_| Vec::new(), |g| g.clone())
71    }
72
73    /// Find the folder containing `path`, if any.
74    #[must_use]
75    pub fn folder_for(&self, path: &Path) -> Option<WorkspaceFolder> {
76        self.folders
77            .read()
78            .ok()
79            .and_then(|guard| guard.iter().find(|f| path.starts_with(&f.root)).cloned())
80    }
81
82    /// Number of configured folders.
83    #[must_use]
84    pub fn len(&self) -> usize {
85        self.folders.read().map_or(0, |g| g.len())
86    }
87
88    /// `true` if no folders are configured.
89    #[must_use]
90    pub fn is_empty(&self) -> bool {
91        self.len() == 0
92    }
93}