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