gestura_core_tools/
code_async.rs

1//! Async code operations for pipeline integration
2//!
3//! Wraps the synchronous [`CodeTools`] via
4//! `tokio::task::spawn_blocking` for use in async contexts (pipeline, GUI).
5
6use crate::code::{CodeTools, EditOp};
7use crate::error::AppError;
8use crate::error::Result;
9use std::path::Path;
10
11/// Serialize a value to pretty JSON, wrapping errors.
12fn to_json<T: serde::Serialize>(val: &T) -> Result<String> {
13    serde_json::to_string_pretty(val)
14        .map_err(|e| AppError::Io(std::io::Error::other(format!("Serialize error: {e}"))))
15}
16
17/// Shorthand join-error mapper.
18fn join_err(e: tokio::task::JoinError) -> AppError {
19    AppError::Io(std::io::Error::other(format!("Task join error: {e}")))
20}
21
22/// Compute code statistics for `path` (file or directory).
23pub async fn stats_dir(path: &str) -> Result<String> {
24    let path = path.to_string();
25    tokio::task::spawn_blocking(move || {
26        let tools = CodeTools::default();
27        let stats = tools.stats(Path::new(&path))?;
28        to_json(&serde_json::json!({ "path": path, "stats": stats }))
29    })
30    .await
31    .map_err(join_err)?
32}
33
34/// Generate a repository map for `path` up to `max_depth` directory levels.
35pub async fn map(path: &str, max_depth: usize) -> Result<String> {
36    let path = path.to_string();
37    tokio::task::spawn_blocking(move || {
38        let tools = CodeTools::default();
39        let result = tools.repository_map(Path::new(&path), max_depth)?;
40        to_json(&result)
41    })
42    .await
43    .map_err(join_err)?
44}
45
46/// Extract top-level symbols from a file.
47pub async fn symbols(path: &str) -> Result<String> {
48    let path = path.to_string();
49    tokio::task::spawn_blocking(move || {
50        let tools = CodeTools::default();
51        let result = tools.symbols(Path::new(&path))?;
52        to_json(&result)
53    })
54    .await
55    .map_err(join_err)?
56}
57
58/// Find all references to `symbol` under `root`.
59pub async fn references(symbol: &str, root: &str) -> Result<String> {
60    let symbol = symbol.to_string();
61    let root = root.to_string();
62    tokio::task::spawn_blocking(move || {
63        let tools = CodeTools::default();
64        let result = tools.references(&symbol, Path::new(&root))?;
65        to_json(&result)
66    })
67    .await
68    .map_err(join_err)?
69}
70
71/// Find the first definition of `symbol` under `root`.
72pub async fn definition(symbol: &str, root: &str) -> Result<String> {
73    let symbol = symbol.to_string();
74    let root = root.to_string();
75    tokio::task::spawn_blocking(move || {
76        let tools = CodeTools::default();
77        let result = tools.definition(&symbol, Path::new(&root))?;
78        to_json(&result)
79    })
80    .await
81    .map_err(join_err)?
82}
83
84/// Parse Cargo dependencies from the manifest at `path`.
85pub async fn deps(path: &str) -> Result<String> {
86    let path = path.to_string();
87    tokio::task::spawn_blocking(move || {
88        let tools = CodeTools::default();
89        let result = tools.cargo_dependencies(Path::new(&path))?;
90        to_json(&result)
91    })
92    .await
93    .map_err(join_err)?
94}
95
96/// Run `cargo clippy` in `path`, optionally applying fixes.
97pub async fn lint(path: &str, fix: bool) -> Result<String> {
98    let path = path.to_string();
99    tokio::task::spawn_blocking(move || {
100        let tools = CodeTools::default();
101        let result = tools.cargo_clippy(Path::new(&path), fix)?;
102        to_json(&result)
103    })
104    .await
105    .map_err(join_err)?
106}
107
108/// Run `cargo test` in `path`, with an optional test name filter.
109pub async fn test(path: &str, filter: Option<String>) -> Result<String> {
110    let path = path.to_string();
111    tokio::task::spawn_blocking(move || {
112        let tools = CodeTools::default();
113        let result = tools.cargo_test(Path::new(&path), filter.as_deref())?;
114        to_json(&result)
115    })
116    .await
117    .map_err(join_err)?
118}
119
120/// Find files matching `pattern` under `root` (glob syntax).
121pub async fn glob_search(pattern: &str, root: &str, max_results: usize) -> Result<String> {
122    let pattern = pattern.to_string();
123    let root = root.to_string();
124    tokio::task::spawn_blocking(move || {
125        let tools = CodeTools::default();
126        let result = tools.glob_search(&pattern, Path::new(&root), max_results)?;
127        to_json(&result)
128    })
129    .await
130    .map_err(join_err)?
131}
132
133/// Search file contents under `root` for lines matching `pattern` (regex).
134pub async fn grep(
135    pattern: &str,
136    root: &str,
137    file_glob: Option<String>,
138    context_lines: usize,
139    case_sensitive: bool,
140    max_matches: usize,
141) -> Result<String> {
142    let pattern = pattern.to_string();
143    let root = root.to_string();
144    tokio::task::spawn_blocking(move || {
145        let tools = CodeTools::default();
146        let result = tools.grep(
147            &pattern,
148            Path::new(&root),
149            file_glob.as_deref(),
150            context_lines,
151            case_sensitive,
152            max_matches,
153        )?;
154        to_json(&result)
155    })
156    .await
157    .map_err(join_err)?
158}
159
160/// Read multiple files in a single call.
161pub async fn batch_read(paths: Vec<String>) -> Result<String> {
162    tokio::task::spawn_blocking(move || {
163        let tools = CodeTools::default();
164        let path_refs: Vec<&str> = paths.iter().map(|s| s.as_str()).collect();
165        let result = tools.batch_read(&path_refs);
166        to_json(&result)
167    })
168    .await
169    .map_err(join_err)?
170}
171
172/// Apply multiple str-replace edits across files.
173pub async fn batch_edit(edits: Vec<EditOp>) -> Result<String> {
174    tokio::task::spawn_blocking(move || {
175        let tools = CodeTools::default();
176        let result = tools.batch_edit(&edits);
177        to_json(&result)
178    })
179    .await
180    .map_err(join_err)?
181}
182
183/// Return a structured outline of symbols in `path`.
184pub async fn outline(path: &str) -> Result<String> {
185    let path = path.to_string();
186    tokio::task::spawn_blocking(move || {
187        let tools = CodeTools::default();
188        let result = tools.outline(Path::new(&path))?;
189        to_json(&result)
190    })
191    .await
192    .map_err(join_err)?
193}