Mountain/RPC/CocoonService/FileSystem/
FindTextInFiles.rs1use tonic::{Response, Status};
6use ::Vine::Generated::{FindTextInFilesRequest, FindTextInFilesResponse, Position, Range, TextMatch, Uri};
7
8use crate::{RPC::CocoonService::CocoonServiceImpl, dev_log};
9
10pub async fn Fn(
11 Service:&CocoonServiceImpl,
12
13 Request:FindTextInFilesRequest,
14) -> Result<Response<FindTextInFilesResponse>, Status> {
15 if Request.pattern.is_empty() {
16 return Ok(Response::new(FindTextInFilesResponse::default()));
17 }
18
19 dev_log!("cocoon", "[CocoonService] find_text_in_files: pattern='{}'", Request.pattern);
20
21 let Roots:Vec<std::path::PathBuf> = {
22 match Service.environment.ApplicationState.Workspace.WorkspaceFolders.lock() {
23 Ok(Guard) => Guard.iter().map(|F| std::path::PathBuf::from(F.URI.path())).collect(),
24
25 Err(_) => Vec::new(),
26 }
27 };
28
29 let SearchRoots = if Roots.is_empty() {
30 vec![std::env::current_dir().unwrap_or_default()]
31 } else {
32 Roots
33 };
34
35 let Pattern = Request.pattern.clone();
36
37 let Matches = tokio::task::spawn_blocking(move || {
38 let mut Results:Vec<TextMatch> = Vec::new();
39
40 const MAX_MATCHES:usize = 1000;
41
42 fn WalkAndSearch(Directory:&std::path::Path, Pattern:&str, Results:&mut Vec<TextMatch>) {
43 if Results.len() >= MAX_MATCHES {
44 return;
45 }
46
47 if let Ok(Entries) = std::fs::read_dir(Directory) {
48 for Entry in Entries.flatten() {
49 if Results.len() >= MAX_MATCHES {
50 break;
51 }
52
53 let Path = Entry.path();
54
55 if Path.is_dir() {
56 let Name = Path.file_name().and_then(|N| N.to_str()).unwrap_or("");
57
58 if Name.starts_with('.') || Name == "node_modules" || Name == "target" {
59 continue;
60 }
61
62 WalkAndSearch(&Path, Pattern, Results);
63 } else if Path.is_file() {
64 if let Ok(Content) = std::fs::read_to_string(&Path) {
65 for (LineIndex, Line) in Content.lines().enumerate() {
66 if Results.len() >= MAX_MATCHES {
67 break;
68 }
69
70 if let Some(ColumnIndex) = Line.find(Pattern) {
71 Results.push(TextMatch {
72 uri:Some(Uri { value:format!("file://{}", Path.display()) }),
73 range:Some(Range {
74 start:Some(Position {
75 line:LineIndex as u32,
76 character:ColumnIndex as u32,
77 }),
78 end:Some(Position {
79 line:LineIndex as u32,
80 character:(ColumnIndex + Pattern.len()) as u32,
81 }),
82 }),
83 preview:Line.to_string(),
84 });
85 }
86 }
87 }
88 }
89 }
90 }
91 }
92
93 for Root in &SearchRoots {
94 WalkAndSearch(Root, &Pattern, &mut Results);
95
96 if Results.len() >= MAX_MATCHES {
97 break;
98 }
99 }
100
101 Results
102 })
103 .await
104 .unwrap_or_default();
105
106 dev_log!(
107 "cocoon",
108 "[CocoonService] find_text_in_files: {} matches for '{}'",
109 Matches.len(),
110 Request.pattern
111 );
112
113 Ok(Response::new(FindTextInFilesResponse { matches:Matches }))
114}