shuck_server/
workspace.rs1#![allow(dead_code)]
2
3use std::ops::Deref;
4use std::path::PathBuf;
5
6use lsp_types::{Url, WorkspaceFolder};
7
8use crate::session::{ClientOptions, WorkspaceOptionsMap};
9
10#[derive(Debug)]
12pub struct Workspaces(Vec<Workspace>);
13
14impl Workspaces {
15 pub fn new(workspaces: Vec<Workspace>) -> Self {
17 Self(workspaces)
18 }
19
20 pub(crate) fn from_workspace_folders(
21 workspace_folders: Option<Vec<WorkspaceFolder>>,
22 root_uri: Option<Url>,
23 root_path: Option<String>,
24 mut workspace_options: WorkspaceOptionsMap,
25 ) -> crate::Result<Self> {
26 let mut client_options_for_url =
27 |url: &Url| workspace_options.remove(url).unwrap_or_default();
28
29 let workspaces =
30 if let Some(folders) = workspace_folders.filter(|folders| !folders.is_empty()) {
31 folders
32 .into_iter()
33 .map(|folder| {
34 let options = client_options_for_url(&folder.uri);
35 Workspace::new(folder.uri).with_options(options)
36 })
37 .collect()
38 } else {
39 let uri = default_workspace_uri(root_uri, root_path)?;
40 let options = client_options_for_url(&uri);
41 vec![Workspace::default(uri).with_options(options)]
42 };
43
44 Ok(Self(workspaces))
45 }
46}
47
48fn default_workspace_uri(root_uri: Option<Url>, root_path: Option<String>) -> crate::Result<Url> {
49 if let Some(root_uri) = root_uri {
50 return Ok(root_uri);
51 }
52
53 if let Some(root_path) = root_path
54 && let Ok(root_uri) = Url::from_file_path(PathBuf::from(root_path))
55 {
56 return Ok(root_uri);
57 }
58
59 let current_dir = std::env::current_dir()?;
60 Url::from_file_path(current_dir)
61 .map_err(|()| anyhow::anyhow!("failed to create URL from current directory"))
62}
63
64impl Deref for Workspaces {
65 type Target = [Workspace];
66
67 fn deref(&self) -> &Self::Target {
68 &self.0
69 }
70}
71
72#[derive(Debug)]
74pub struct Workspace {
75 url: Url,
76 options: Option<ClientOptions>,
77 is_default: bool,
78}
79
80impl Workspace {
81 pub fn new(url: Url) -> Self {
83 Self {
84 url,
85 options: None,
86 is_default: false,
87 }
88 }
89
90 pub fn default(url: Url) -> Self {
92 Self {
93 url,
94 options: None,
95 is_default: true,
96 }
97 }
98
99 #[must_use]
101 pub fn with_options(mut self, options: ClientOptions) -> Self {
102 self.options = Some(options);
103 self
104 }
105
106 pub(crate) fn url(&self) -> &Url {
107 &self.url
108 }
109
110 pub(crate) fn options(&self) -> Option<&ClientOptions> {
111 self.options.as_ref()
112 }
113
114 pub(crate) fn is_default(&self) -> bool {
115 self.is_default
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn uses_root_uri_when_workspace_folders_are_missing() {
125 let root_uri = Url::parse("file:///tmp/shuck-root").expect("test URI should parse");
126
127 let workspaces = Workspaces::from_workspace_folders(
128 None,
129 Some(root_uri.clone()),
130 None,
131 WorkspaceOptionsMap::default(),
132 )
133 .expect("workspace fallback should succeed");
134
135 assert_eq!(workspaces.len(), 1);
136 assert_eq!(workspaces[0].url(), &root_uri);
137 assert!(workspaces[0].is_default());
138 }
139
140 #[test]
141 fn uses_root_path_when_root_uri_is_missing() {
142 let root_path = std::env::temp_dir().join("shuck-server-root-path");
143 let expected_uri =
144 Url::from_file_path(&root_path).expect("temporary directory should convert to a URL");
145
146 let workspaces = Workspaces::from_workspace_folders(
147 None,
148 None,
149 Some(root_path.display().to_string()),
150 WorkspaceOptionsMap::default(),
151 )
152 .expect("workspace fallback should succeed");
153
154 assert_eq!(workspaces.len(), 1);
155 assert_eq!(workspaces[0].url(), &expected_uri);
156 assert!(workspaces[0].is_default());
157 }
158}