Mountain/IPC/WindServiceHandler/
Search.rs1#![allow(non_snake_case)]
2
3use std::{path::PathBuf, sync::Arc};
6
7use serde_json::{Value, json};
8use tokio::fs;
9
10use crate::RunTime::ApplicationRunTime::ApplicationRunTime;
11
12pub async fn handle_search_find_in_files(Runtime:Arc<ApplicationRunTime>, Args:Vec<Value>) -> Result<Value, String> {
15 use globset::GlobBuilder;
16
17 let Pattern = Args
18 .first()
19 .and_then(|V| V.as_str())
20 .ok_or("search:findInFiles requires pattern".to_string())?
21 .to_string();
22 let IsRegex = Args.get(1).and_then(|V| V.as_bool()).unwrap_or(false);
23 let IsCaseSensitive = Args.get(2).and_then(|V| V.as_bool()).unwrap_or(false);
24 let _IsWordMatch = Args.get(3).and_then(|V| V.as_bool()).unwrap_or(false);
25 let IncludeGlob = Args.get(4).and_then(|V| V.as_str()).unwrap_or("**").to_string();
26 let ExcludeGlob = Args.get(5).and_then(|V| V.as_str()).unwrap_or("").to_string();
27 let MaxResults = Args.get(6).and_then(|V| V.as_u64()).unwrap_or(1000) as usize;
28
29 let WorkspaceFolders = Runtime.Environment.ApplicationState.Workspace.GetWorkspaceFolders();
30
31 if WorkspaceFolders.is_empty() {
32 return Ok(json!([]));
33 }
34
35 let RootPath = PathBuf::from(&WorkspaceFolders[0].URI.to_string().replace("file://", ""));
36
37 let IncludeMatcher = GlobBuilder::new(&IncludeGlob)
38 .literal_separator(false)
39 .build()
40 .map(|G| G.compile_matcher())
41 .ok();
42
43 let ExcludeMatcher = if !ExcludeGlob.is_empty() {
44 GlobBuilder::new(&ExcludeGlob)
45 .literal_separator(false)
46 .build()
47 .map(|G| G.compile_matcher())
48 .ok()
49 } else {
50 None
51 };
52
53 let SearchText = Pattern.clone();
54 let mut Matches = Vec::new();
55
56 let mut Stack = vec![RootPath.clone()];
57 while let Some(Dir) = Stack.pop() {
58 let mut Entries = match fs::read_dir(&Dir).await {
59 Ok(E) => E,
60 Err(_) => continue,
61 };
62
63 while let Ok(Some(Entry)) = Entries.next_entry().await {
64 let Path = Entry.path();
65 let RelPath = Path.strip_prefix(&RootPath).unwrap_or(&Path).to_string_lossy().to_string();
66
67 if Path.file_name().map(|N| N.to_string_lossy().starts_with('.')).unwrap_or(false) {
68 continue;
69 }
70
71 if Path.is_dir() {
72 Stack.push(Path);
73 continue;
74 }
75
76 if let Some(Ref) = &IncludeMatcher {
77 if !Ref.is_match(&RelPath) {
78 continue;
79 }
80 }
81 if let Some(Ref) = &ExcludeMatcher {
82 if Ref.is_match(&RelPath) {
83 continue;
84 }
85 }
86
87 let Content = match fs::read_to_string(&Path).await {
88 Ok(C) => C,
89 Err(_) => continue,
90 };
91
92 for (LineIndex, Line) in Content.lines().enumerate() {
93 let Hit = if IsRegex {
94 Line.contains(&SearchText)
95 } else if IsCaseSensitive {
96 Line.contains(&SearchText)
97 } else {
98 Line.to_lowercase().contains(&SearchText.to_lowercase())
99 };
100
101 if Hit {
102 let Uri = format!("file://{}", Path.to_string_lossy());
103 Matches.push(json!({
104 "uri": Uri,
105 "lineNumber": LineIndex + 1,
106 "preview": Line.trim(),
107 }));
108
109 if Matches.len() >= MaxResults {
110 return Ok(json!(Matches));
111 }
112 }
113 }
114 }
115 }
116
117 Ok(json!(Matches))
118}
119
120pub async fn handle_search_find_files(Runtime:Arc<ApplicationRunTime>, Args:Vec<Value>) -> Result<Value, String> {
122 use globset::GlobBuilder;
123
124 let Pattern = Args
125 .first()
126 .and_then(|V| V.as_str())
127 .ok_or("search:findFiles requires pattern".to_string())?
128 .to_string();
129 let MaxResults = Args.get(1).and_then(|V| V.as_u64()).unwrap_or(500) as usize;
130
131 let WorkspaceFolders = Runtime.Environment.ApplicationState.Workspace.GetWorkspaceFolders();
132
133 if WorkspaceFolders.is_empty() {
134 return Ok(json!([]));
135 }
136
137 let RootPath = PathBuf::from(&WorkspaceFolders[0].URI.to_string().replace("file://", ""));
138
139 let Matcher = GlobBuilder::new(&Pattern)
140 .literal_separator(false)
141 .build()
142 .map(|G| G.compile_matcher())
143 .map_err(|Error| format!("Invalid glob pattern: {}", Error))?;
144
145 let mut Files = Vec::new();
146 let mut Stack = vec![RootPath.clone()];
147
148 while let Some(Dir) = Stack.pop() {
149 let mut Entries = match fs::read_dir(&Dir).await {
150 Ok(E) => E,
151 Err(_) => continue,
152 };
153
154 while let Ok(Some(Entry)) = Entries.next_entry().await {
155 let Path = Entry.path();
156
157 if Path.file_name().map(|N| N.to_string_lossy().starts_with('.')).unwrap_or(false) {
158 continue;
159 }
160
161 if Path.is_dir() {
162 Stack.push(Path);
163 continue;
164 }
165
166 let RelPath = Path.strip_prefix(&RootPath).unwrap_or(&Path).to_string_lossy().to_string();
167
168 if Matcher.is_match(&RelPath) {
169 Files.push(format!("file://{}", Path.to_string_lossy()));
170
171 if Files.len() >= MaxResults {
172 return Ok(json!(Files));
173 }
174 }
175 }
176 }
177
178 Ok(json!(Files))
179}