Mountain/Environment/
WorkSpaceProvider.rs1#![allow(non_snake_case, non_camel_case_types)]
16
17use std::{path::PathBuf, sync::Arc};
18
19use Common::{
20 CustomEditor::CustomEditorProvider::CustomEditorProvider,
21 DTO::WorkSpaceEditDTO::WorkSpaceEditDTO,
22 Document::DocumentProvider::DocumentProvider,
23 Environment::Requires::Requires,
24 Error::CommonError::CommonError,
25 WebView::WebViewProvider::WebViewProvider,
26 WorkSpace::{WorkSpaceEditApplier::WorkSpaceEditApplier, WorkSpaceProvider::WorkSpaceProvider},
27};
28use async_trait::async_trait;
29use globset::{Glob, GlobMatcher};
30use ignore::WalkBuilder;
31use log::{info, warn};
32use serde_json::{Value, json};
33use url::Url;
34
35use super::{MountainEnvironment::MountainEnvironment, Utility};
36
37#[async_trait]
38impl WorkSpaceProvider for MountainEnvironment {
39 async fn GetWorkSpaceFoldersInfo(&self) -> Result<Vec<(Url, String, usize)>, CommonError> {
41 info!("[WorkSpaceProvider] Getting workspace folders info.");
42 let FoldersGuard = self
43 .ApplicationState
44 .WorkSpaceFolders
45 .lock()
46 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
47 Ok(FoldersGuard.iter().map(|f| (f.URI.clone(), f.Name.clone(), f.Index)).collect())
48 }
49
50 async fn GetWorkSpaceFolderInfo(&self, URIToMatch:Url) -> Result<Option<(Url, String, usize)>, CommonError> {
53 let FoldersGuard = self
54 .ApplicationState
55 .WorkSpaceFolders
56 .lock()
57 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
58 for Folder in FoldersGuard.iter() {
59 if URIToMatch.as_str().starts_with(Folder.URI.as_str()) {
60 return Ok(Some((Folder.URI.clone(), Folder.Name.clone(), Folder.Index)));
61 }
62 }
63
64 Ok(None)
65 }
66
67 async fn GetWorkSpaceName(&self) -> Result<Option<String>, CommonError> {
69 self.ApplicationState.GetWorkSpaceIdentifier().map(Some)
70 }
71
72 async fn GetWorkSpaceConfigurationPath(&self) -> Result<Option<PathBuf>, CommonError> {
74 Ok(self
75 .ApplicationState
76 .WorkSpaceConfigurationPath
77 .lock()
78 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?
79 .clone())
80 }
81
82 async fn IsWorkSpaceTrusted(&self) -> Result<bool, CommonError> {
84 Ok(self.ApplicationState.IsTrusted.load(std::sync::atomic::Ordering::Relaxed))
85 }
86
87 async fn RequestWorkSpaceTrust(&self, _Options:Option<Value>) -> Result<bool, CommonError> {
89 warn!("[WorkSpaceProvider] RequestWorkSpaceTrust is not implemented; defaulting to trusted.");
90 self.ApplicationState
93 .IsTrusted
94 .store(true, std::sync::atomic::Ordering::Relaxed);
95 Ok(true)
96 }
97
98 async fn FindFilesInWorkSpace(
100 &self,
101
102 IncludePatternDTO:Value,
103
104 ExcludePatternDTO:Option<Value>,
105
106 MaxResults:Option<usize>,
107
108 UseIgnoreFiles:bool,
109
110 FollowSymlinks:bool,
111 ) -> Result<Vec<Url>, CommonError> {
112 info!(
113 "[WorkSpaceProvider] Finding files with include pattern: {:?}",
114 IncludePatternDTO
115 );
116 let FoldersGuard = self
117 .ApplicationState
118 .WorkSpaceFolders
119 .lock()
120 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
121 if FoldersGuard.is_empty() {
122 return Ok(vec![]);
123 }
124
125 let IncludeMatcher = BuildGlobMatcher(IncludePatternDTO)?;
126 let ExcludeMatcher = ExcludePatternDTO.map(BuildGlobMatcher).transpose()?.flatten();
127 let mut Results:Vec<Url> = Vec::new();
128 let MaxResultsCap = MaxResults.unwrap_or(usize::MAX);
129
130 for Folder in FoldersGuard.iter() {
131 if Results.len() >= MaxResultsCap {
132 break;
133 }
134
135 let FolderPath = match Folder.URI.to_file_path() {
136 Ok(path) => path,
137
138 Err(_) => continue,
139 };
140 let mut WalkerBuilder = WalkBuilder::new(&FolderPath);
141 WalkerBuilder.standard_filters(UseIgnoreFiles).follow_links(FollowSymlinks);
142
143 for EntryResult in WalkerBuilder.build() {
144 if Results.len() >= MaxResultsCap {
145 break;
146 }
147
148 if let Ok(Entry) = EntryResult {
149 let Path = Entry.path();
150 if Path.is_dir() {
151 continue;
152 }
153
154 if !IncludeMatcher.as_ref().map_or(true, |g| g.is_match(Path)) {
155 continue;
156 }
157
158 if let Some(ref exclude) = ExcludeMatcher {
159 if exclude.is_match(Path) {
160 continue;
161 }
162 }
163
164 if let Ok(URL) = Url::from_file_path(Path) {
165 Results.push(URL);
166 }
167 }
168 }
169 }
170
171 Ok(Results)
172 }
173
174 async fn OpenFile(&self, Path:PathBuf) -> Result<(), CommonError> {
177 let URI = Url::from_file_path(Path.clone()).map_err(|_| {
178 CommonError::InvalidArgument { ArgumentName:"Path".into(), Reason:"Could not convert path to URI.".into() }
179 })?;
180
181 let CustomEditorViewType:Option<String> = None;
184
185 if let Some(ViewType) = CustomEditorViewType {
186 info!(
187 "[WorkSpaceProvider] Found custom editor '{}' for file '{}'",
188 ViewType,
189 Path.display()
190 );
191 let WebViewProvider:Arc<dyn WebViewProvider> = self.Require();
192 let Handle = WebViewProvider
193 .CreateWebViewPanel(
194 json!({ "id": "placeholder.extension" }),
195 ViewType.clone(),
196 Path.file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or_default(),
197 json!({ "viewColumn": -1 }),
198 json!({}),
199 json!({ "enableScripts": true }),
200 )
201 .await?;
202 let CustomEditorProvider:Arc<dyn CustomEditorProvider> = self.Require();
203 CustomEditorProvider.ResolveCustomEditor(ViewType, URI, Handle).await?;
204 return Ok(());
205 }
206
207 info!(
208 "[WorkSpaceProvider] No custom editor found. Opening '{}' as text.",
209 Path.display()
210 );
211 let URIComponents = json!({ "external": URI.to_string(), "$mid": 1 });
212 let DocProvider:Arc<dyn DocumentProvider> = self.Require();
213 DocProvider.OpenDocument(URIComponents, None, None).await?;
214 Ok(())
215 }
216}
217
218fn BuildGlobMatcher(GlobValue:Value) -> Result<Option<GlobMatcher>, CommonError> {
219 GlobValue
220 .as_str()
221 .map(|Pattern| {
222 Glob::new(Pattern).map(|g| g.compile_matcher()).map_err(|Error| {
223 CommonError::InvalidArgument { ArgumentName:"GlobPattern".to_string(), Reason:Error.to_string() }
224 })
225 })
226 .transpose()
227}
228
229#[async_trait]
230impl WorkSpaceEditApplier for MountainEnvironment {
231 async fn ApplyWorkSpaceEdit(&self, EditDTO:WorkSpaceEditDTO) -> Result<bool, CommonError> {
232 let DocProvider:Arc<dyn DocumentProvider> = self.Require();
233 for (URIValue, Edits) in EditDTO.Edits {
234 let URI = serde_json::from_value::<Url>(URIValue)?;
235 let Document = {
236 self.ApplicationState
237 .OpenDocuments
238 .lock()
239 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?
240 .get(URI.as_str())
241 .cloned()
242 };
243
244 if let Some(Doc) = Document {
245 let NewVersionID = Doc.Version + 1;
246 DocProvider
247 .ApplyDocumentChanges(URI.clone(), NewVersionID, json!(Edits), true, false, false)
248 .await?;
249 } else {
250 warn!("[WorkSpaceProvider] Attempted to apply edit to non-open document: {}", URI);
251 }
252 }
253
254 Ok(true)
255 }
256}