auto_lsp_default/server/
workspace_init.rs1use auto_lsp_core::errors::{ExtensionError, FileSystemError, RuntimeError};
20use auto_lsp_core::parsers::Parsers;
21use auto_lsp_server::Session;
22use lsp_types::{InitializeParams, Url};
23use rayon::prelude::*;
24use std::path::Path;
25use std::{fs::File, io::Read};
26use texter::core::text::Text;
27use walkdir::WalkDir;
28
29use crate::db::BaseDatabase;
30use crate::db::FileManager;
31
32pub trait WorkspaceInit {
33 fn init_workspace(
34 &mut self,
35 params: InitializeParams,
36 ) -> Result<Vec<Result<(), RuntimeError>>, RuntimeError>;
37
38 fn read_file(&self, file: &Path) -> Result<(&'static Parsers, Url, Text), FileSystemError>;
39}
40
41impl<Db: BaseDatabase> WorkspaceInit for Session<Db> {
42 fn init_workspace(
44 &mut self,
45 params: InitializeParams,
46 ) -> Result<Vec<Result<(), RuntimeError>>, RuntimeError> {
47 let mut errors: Vec<Result<(), RuntimeError>> = vec![];
48
49 if let Some(folders) = params.workspace_folders {
50 let files = folders
51 .into_iter()
52 .flat_map(|folder| {
53 WalkDir::new(folder.uri.path())
54 .into_iter()
55 .filter_map(Result::ok)
56 .filter(|entry| {
57 entry.file_type().is_file()
58 && entry.path().extension().is_some_and(|ext| {
59 self.extensions.contains_key(ext.to_string_lossy().as_ref())
60 })
61 })
62 })
63 .collect::<Vec<_>>();
64
65 errors.extend(rayon_par_bridge::par_bridge(
66 16,
67 files.into_par_iter(),
68 |file_iter| {
69 file_iter
70 .map(|file| match self.read_file(&file.into_path()) {
71 Ok((parsers, url, text)) => self
72 .db
73 .add_file_from_texter(parsers, &url, text)
74 .map_err(RuntimeError::from),
75 Err(err) => Err(RuntimeError::from(err)),
76 })
77 .collect::<Vec<_>>()
78 },
79 ));
80 }
81
82 Ok(errors)
83 }
84
85 fn read_file(&self, file: &Path) -> Result<(&'static Parsers, Url, Text), FileSystemError> {
86 let url = Url::from_file_path(file).map_err(|_| FileSystemError::FilePathToUrl {
87 path: file.to_path_buf(),
88 })?;
89
90 let mut open_file = File::open(file).map_err(|e| FileSystemError::FileOpen {
91 path: url.clone(),
92 error: e.to_string(),
93 })?;
94 let mut buffer = String::new();
95 open_file
96 .read_to_string(&mut buffer)
97 .map_err(|e| FileSystemError::FileRead {
98 path: url.clone(),
99 error: e.to_string(),
100 })?;
101
102 let extension = get_extension(&url)?;
103
104 let text = (self.text_fn)(buffer.to_string());
105 let extension = match self.extensions.get(&extension) {
106 Some(extension) => extension,
107 None => {
108 return Err(FileSystemError::from(ExtensionError::UnknownExtension {
109 extension: extension.clone(),
110 available: self.extensions.clone(),
111 }))
112 }
113 };
114
115 let parsers = self
116 .init_options
117 .parsers
118 .get(extension.as_str())
119 .ok_or_else(|| {
120 FileSystemError::from(ExtensionError::UnknownParser {
121 extension: extension.clone(),
122 available: self.init_options.parsers.keys().cloned().collect(),
123 })
124 })?;
125 Ok((parsers, url, text))
126 }
127}
128
129#[cfg(windows)]
131pub(crate) fn get_extension(path: &Url) -> Result<String, FileSystemError> {
132 if let Some(host) = path.host_str() {
134 if !host.is_empty() && host != "localhost" {
135 return Err(FileSystemError::FileUrlHost {
136 host: host.to_string(),
137 path: path.clone(),
138 });
139 }
140 }
141
142 path.to_file_path()
143 .map_err(|_| FileSystemError::FileUrlToFilePath { path: path.clone() })?
144 .extension()
145 .map_or_else(
146 || Err(FileSystemError::FileExtension { path: path.clone() }),
147 |ext| Ok(ext.to_string_lossy().to_string()),
148 )
149}
150
151#[cfg(not(windows))]
152pub(crate) fn get_extension(path: &Url) -> Result<String, FileSystemError> {
153 path.to_file_path()
154 .map_err(|_| FileSystemError::FileUrlToFilePath { path: path.clone() })?
155 .extension()
156 .map_or_else(
157 || Err(FileSystemError::FileExtension { path: path.clone() }),
158 |ext| Ok(ext.to_string_lossy().to_string()),
159 )
160}
161
162#[cfg(test)]
163mod tests {
164 use super::get_extension;
165 use auto_lsp_core::errors::FileSystemError;
166 use lsp_types::Url;
167
168 #[cfg(windows)]
169 #[test]
170 fn test_get_extension_windows() {
171 assert_eq!(
173 get_extension(&Url::parse("file:///C:/path/to/file.rs").unwrap())
174 .unwrap()
175 .as_str(),
176 "rs"
177 );
178
179 assert_eq!(
180 get_extension(&Url::parse("file:///C:/path/to/file.with.multiple.dots").unwrap())
181 .unwrap()
182 .as_str(),
183 "dots"
184 );
185
186 assert_eq!(
188 get_extension(&Url::parse("file:///C:/path/to/file").unwrap()),
189 Err(FileSystemError::FileExtension {
190 path: Url::parse("file:///C:/path/to/file").unwrap()
191 })
192 );
193 }
194
195 #[cfg(not(windows))]
196 #[test]
197 fn test_get_extension_non_windows() {
198 assert_eq!(
201 get_extension(&Url::parse("file:///path/to/file.rs").unwrap())
202 .unwrap()
203 .as_str(),
204 "rs"
205 );
206
207 assert_eq!(
208 get_extension(&Url::parse("file:///path/to/file.with.multiple.dots").unwrap())
209 .unwrap()
210 .as_str(),
211 "dots"
212 );
213
214 assert_eq!(
216 get_extension(&Url::parse("file:///path/to/file").unwrap()),
217 Err(FileSystemError::FileExtension {
218 path: Url::parse("file:///path/to/file").unwrap()
219 })
220 );
221
222 assert_eq!(
224 get_extension(&Url::parse("file://localhost/path/to/file.rs").unwrap())
225 .unwrap()
226 .as_str(),
227 "rs"
228 );
229 }
230}