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		let IsCase = Arguments.get(2).and_then(|V| V.as_bool()).unwrap_or(false);
49		let IsWord = Arguments.get(3).and_then(|V| V.as_bool()).unwrap_or(false);
50		json!({
51			"pattern": Pattern,
52			"isRegex": IsRegex,
53			"isCaseSensitive": IsCase,
54			"isWordMatch": IsWord,
55		})
56	} else {
57		return Err("search:findInFiles requires pattern or TextSearchQuery".to_string());
58	};
59
60	let OptionsValue = Arguments.into_iter().next().unwrap_or(Value::Null);
61
62	dev_log!("search", "search:textSearch delegating to SearchProvider::TextSearch");
63
64	RunTime
65		.Environment
66		.TextSearch(QueryValue, OptionsValue)
67		.await
68		.map_err(|Error| Error.to_string())
69}
70
71/// `search:findFiles` / `search:fileSearch` / `search:searchFile`.
72///
73/// Wire contract: positional Arguments = [includePattern, excludePattern?,
74/// maxResults?, useIgnoreFiles?, followSymlinks?]. Delegates to
75/// `WorkspaceProvider::FindFilesInWorkspace` which returns `Vec<Url>`;
76/// we reshape to `Vec<String>` for the renderer.
77pub async fn SearchFindFiles(RunTime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
78	let IncludePattern = Arguments
79		.first()
80		.cloned()
81		.ok_or_else(|| "search:findFiles requires include pattern in slot 0".to_string())?;
82	let ExcludePattern = Arguments.get(1).cloned().filter(|V| !V.is_null());
83	let MaxResults = Arguments.get(2).and_then(|V| V.as_u64()).map(|N| N as usize);
84	let UseIgnoreFiles = Arguments.get(3).and_then(|V| V.as_bool()).unwrap_or(true);
85	let FollowSymlinks = Arguments.get(4).and_then(|V| V.as_bool()).unwrap_or(false);
86
87	dev_log!(
88		"search",
89		"search:fileSearch delegating to WorkspaceProvider::FindFilesInWorkspace (ignore={}, symlinks={})",
90		UseIgnoreFiles,
91		FollowSymlinks
92	);
93
94	let Urls = RunTime
95		.Environment
96		.FindFilesInWorkspace(IncludePattern, ExcludePattern, MaxResults, UseIgnoreFiles, FollowSymlinks)
97		.await
98		.map_err(|Error| Error.to_string())?;
99
100	Ok(json!(Urls.into_iter().map(|U| U.to_string()).collect::<Vec<_>>()))
101}