Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/RPC/CocoonService/FileSystem/
FindTextInFiles.rs

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