Skip to main content

Mountain/IPC/WindServiceHandlers/
Search.rs

1#![allow(non_snake_case, unused_variables, dead_code, unused_imports)]
2
3//! Search handlers - find in files, find files by glob.
4//!
5//! **Both handlers now delegate to the properly-implemented trait
6//! methods on `MountainEnvironment`** instead of carrying their own
7//! inline fs-walk. The inline versions used naive `starts_with('.')`
8//! hidden-file skipping (doesn't honour `.gitignore`), no regex engine,
9//! a bogus `format!("file://{}", path)` URI constructor, and a single-
10//! threaded walker. The trait impls live in:
11//!
12//! - `Environment/SearchProvider.rs` (`TextSearch`) - `grep-searcher` +
13//!   `RegexMatcherBuilder` + `ignore::WalkBuilder::build_parallel()` with
14//!   `PerFileSink` collection.
15//! - `Environment/WorkspaceProvider.rs` (`FindFilesInWorkspace`) -
16//!   `ignore`-aware glob walker with `.gitignore` support, max-result cap,
17//!   symlink handling, and proper `Url::from_file_path` URI construction.
18//!
19//! This wiring was the "lot of dead code that needs to be connected"
20//! the user flagged - the trait impls were reachable only through
21//! `Environment.Require<dyn SearchProvider>()` / `WorkspaceProvider`
22//! calls and no IPC handler ever issued those calls.
23
24use std::sync::Arc;
25
26use serde_json::{Value, json};
27use CommonLibrary::{Search::SearchProvider::SearchProvider, Workspace::WorkspaceProvider::WorkspaceProvider};
28
29use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, dev_log};
30
31/// `search:findInFiles` / `search:textSearch` / `search:searchText`.
32///
33/// Wire contract (VS Code's `ProxyChannel.toService(search)` path):
34/// positional Arguments = [TextSearchQuery, TextSearchOptions]. The trait
35/// method `SearchProvider::TextSearch` accepts the raw JSON and does
36/// its own `serde_json::from_value::<TextSearchQuery>` so callers can
37/// keep sending arbitrary shapes - we pass through directly.
38pub async fn SearchFindInFiles(RunTime:Arc<ApplicationRunTime>, mut Arguments:Vec<Value>) -> Result<Value, String> {
39	// Positional → named translation. VS Code's SearchService sends the
40	// query object in slot 0; older Wind Effect callers passed flat
41	// positional Arguments (pattern, isRegex, isCase, isWord, include,
42	// exclude, maxResults). Accept both by promoting flat Arguments into a
43	// TextSearchQuery-shaped object.
44	let QueryValue = if Arguments.first().map(|V| V.is_object()).unwrap_or(false) {
45		Arguments.remove(0)
46	} else if let Some(Pattern) = Arguments.first().and_then(|V| V.as_str()) {
47		let IsRegex = Arguments.get(1).and_then(|V| V.as_bool()).unwrap_or(false);
48
49		let IsCase = Arguments.get(2).and_then(|V| V.as_bool()).unwrap_or(false);
50
51		let IsWord = Arguments.get(3).and_then(|V| V.as_bool()).unwrap_or(false);
52
53		json!({
54			"pattern": Pattern,
55			"isRegex": IsRegex,
56			"isCaseSensitive": IsCase,
57			"isWordMatch": IsWord,
58		})
59	} else {
60		return Err("search:findInFiles requires pattern or TextSearchQuery".to_string());
61	};
62
63	let OptionsValue = Arguments.into_iter().next().unwrap_or(Value::Null);
64
65	dev_log!("search", "search:textSearch delegating to SearchProvider::TextSearch");
66
67	RunTime
68		.Environment
69		.TextSearch(QueryValue, OptionsValue)
70		.await
71		.map_err(|Error| Error.to_string())
72}
73
74/// `search:findFiles` / `search:fileSearch` / `search:searchFile`.
75///
76/// Wire contract: positional Arguments = [includePattern, excludePattern?,
77/// maxResults?, useIgnoreFiles?, followSymlinks?]. Delegates to
78/// `WorkspaceProvider::FindFilesInWorkspace` which returns `Vec<Url>`;
79/// we reshape to `Vec<String>` for the renderer.
80pub async fn SearchFindFiles(RunTime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
81	let IncludePattern = Arguments
82		.first()
83		.cloned()
84		.ok_or_else(|| "search:findFiles requires include pattern in slot 0".to_string())?;
85
86	let ExcludePattern = Arguments.get(1).cloned().filter(|V| !V.is_null());
87
88	let MaxResults = Arguments.get(2).and_then(|V| V.as_u64()).map(|N| N as usize);
89
90	let UseIgnoreFiles = Arguments.get(3).and_then(|V| V.as_bool()).unwrap_or(true);
91
92	let FollowSymlinks = Arguments.get(4).and_then(|V| V.as_bool()).unwrap_or(false);
93
94	dev_log!(
95		"search",
96		"search:fileSearch delegating to WorkspaceProvider::FindFilesInWorkspace (ignore={}, symlinks={})",
97		UseIgnoreFiles,
98		FollowSymlinks
99	);
100
101	let Urls = RunTime
102		.Environment
103		.FindFilesInWorkspace(IncludePattern, ExcludePattern, MaxResults, UseIgnoreFiles, FollowSymlinks)
104		.await
105		.map_err(|Error| Error.to_string())?;
106
107	Ok(json!(Urls.into_iter().map(|U| U.to_string()).collect::<Vec<_>>()))
108}