Mountain/Environment/
SearchProvider.rs1#![allow(non_snake_case, non_camel_case_types)]
15
16use std::{
17 io,
18 path::PathBuf,
19 sync::{Arc, Mutex},
20};
21
22use Common::{Error::CommonError::CommonError, Search::SearchProvider::SearchProvider};
23use async_trait::async_trait;
24use grep_regex::RegexMatcherBuilder;
25use grep_searcher::{Searcher, Sink, SinkMatch};
26use ignore::WalkBuilder;
27use log::{info, warn};
28use serde::{Deserialize, Serialize};
29use serde_json::{Value, json};
30
31use super::{MountainEnvironment::MountainEnvironment, Utility};
32
33#[derive(Deserialize, Debug)]
34#[serde(rename_all = "camelCase")]
35struct TextSearchQuery {
36 pattern:String,
37
38 is_case_sensitive:Option<bool>,
39
40 is_word_match:Option<bool>,
41}
42
43#[derive(Serialize, Clone, Debug)]
44#[serde(rename_all = "camelCase")]
45struct TextMatch {
46 preview:String,
47
48 line_number:u64,
49}
50
51#[derive(Serialize, Clone, Debug)]
52#[serde(rename_all = "camelCase")]
53struct FileMatch {
54 resource:String,
56
57 matches:Vec<TextMatch>,
58}
59
60struct PerFileSink {
63 path:PathBuf,
64
65 results:Arc<Mutex<Vec<FileMatch>>>,
66}
67
68impl Sink for PerFileSink {
69 type Error = io::Error;
70
71 fn matched(&mut self, _Searcher:&Searcher, Mat:&SinkMatch<'_>) -> Result<bool, Self::Error> {
72 let mut ResultsGuard = self
73 .results
74 .lock()
75 .map_err(|Error| io::Error::new(io::ErrorKind::Other, Error.to_string()))?;
76
77 let Preview = String::from_utf8_lossy(Mat.bytes()).to_string();
78
79 let LineNumber = Mat.line_number().unwrap_or(0);
80
81 let FileURI = url::Url::from_file_path(&self.path)
83 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Could not convert path to URL"))?
84 .to_string();
85
86 if let Some(FileMatch) = ResultsGuard.iter_mut().find(|fm| fm.resource == FileURI) {
88 FileMatch.matches.push(TextMatch { preview:Preview, line_number:LineNumber });
89 } else {
90 ResultsGuard.push(FileMatch {
91 resource:FileURI,
92
93 matches:vec![TextMatch { preview:Preview, line_number:LineNumber }],
94 });
95 }
96
97 Ok(true)
99 }
100}
101
102#[async_trait]
103impl SearchProvider for MountainEnvironment {
104 async fn TextSearch(&self, QueryValue:Value, _OptionsValue:Value) -> Result<Value, CommonError> {
105 let Query:TextSearchQuery = serde_json::from_value(QueryValue)?;
106
107 info!("[SearchProvider] Performing text search for: {:?}", Query);
108
109 let mut Builder = RegexMatcherBuilder::new();
110
111 Builder
112 .case_insensitive(!Query.is_case_sensitive.unwrap_or(false))
113 .word(Query.is_word_match.unwrap_or(false));
114
115 let Matcher = Builder.build(&Query.pattern).map_err(|Error| {
116 CommonError::InvalidArgument { ArgumentName:"pattern".into(), Reason:Error.to_string() }
117 })?;
118
119 let AllMatches = Arc::new(Mutex::new(Vec::<FileMatch>::new()));
120
121 let Folders = self
122 .ApplicationState
123 .WorkSpaceFolders
124 .lock()
125 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?
126 .clone();
127
128 if Folders.is_empty() {
129 warn!("[SearchProvider] No workspace folders to search in.");
130
131 return Ok(json!([]));
132 }
133
134 for Folder in Folders {
135 if let Ok(FolderPath) = Folder.URI.to_file_path() {
136 let Walker = WalkBuilder::new(FolderPath).build_parallel();
138
139 Walker.run(|| {
142 let mut Searcher = Searcher::new();
143
144 let Matcher = Matcher.clone();
145
146 let AllMatches = AllMatches.clone();
147
148 Box::new(move |EntryResult| {
149 if let Ok(Entry) = EntryResult {
150 if Entry.file_type().map_or(false, |ft| ft.is_file()) {
151 let Sink = PerFileSink { path:Entry.path().to_path_buf(), results:AllMatches.clone() };
153
154 if let Err(Error) = Searcher.search_path(&Matcher, Entry.path(), Sink) {
155 warn!(
156 "[SearchProvider] Error searching path {}: {}",
157 Entry.path().display(),
158 Error
159 );
160 }
161 }
162 }
163
164 ignore::WalkState::Continue
165 })
166 });
167 }
168 }
169
170 let FinalMatches = AllMatches
171 .lock()
172 .map_err(|Error| CommonError::StateLockPoisoned { Context:Error.to_string() })?
173 .clone();
174
175 Ok(json!(FinalMatches))
176 }
177}