1+ use std:: sync:: Arc ;
2+ use tokio:: sync:: RwLock as ARwLock ;
3+
4+ use crate :: at_commands:: at_file:: { file_repair_candidates, return_one_candidate_or_a_good_error} ;
5+ use crate :: files_correction:: { correct_to_nearest_dir_path, get_project_dirs} ;
6+ use crate :: global_context:: GlobalContext ;
7+
8+ /// Resolves a scope string into a list of files to search.
9+ ///
10+ /// # Arguments
11+ ///
12+ /// * `gcx` - Global context
13+ /// * `scope` - Scope string, can be "workspace", a directory path (ending with / or \), or a file path
14+ ///
15+ /// # Returns
16+ ///
17+ /// * `Ok(Vec<String>)` - List of file paths to search
18+ /// * `Err(String)` - Error message if scope resolution fails
19+ ///
20+ /// # Examples
21+ ///
22+ /// ```
23+ /// let files = resolve_scope(gcx.clone(), "workspace").await?;
24+ /// let files = resolve_scope(gcx.clone(), "src/").await?;
25+ /// let files = resolve_scope(gcx.clone(), "src/main.rs").await?;
26+ /// ```
27+ pub async fn resolve_scope (
28+ gcx : Arc < ARwLock < GlobalContext > > ,
29+ scope : & str ,
30+ ) -> Result < Vec < String > , String > {
31+ let scope_string = scope. to_string ( ) ;
32+ // Case 1: Workspace scope
33+ if scope == "workspace" {
34+ let workspace_files = gcx. read ( ) . await . documents_state . workspace_files . lock ( ) . unwrap ( ) . clone ( ) ;
35+ return Ok ( workspace_files. into_iter ( )
36+ . map ( |f| f. to_string_lossy ( ) . to_string ( ) )
37+ . collect :: < Vec < _ > > ( ) ) ;
38+ }
39+
40+ // Check if scope is a directory (ends with / or \)
41+ let scope_is_dir = scope. ends_with ( '/' ) || scope. ends_with ( '\\' ) ;
42+
43+ // Case 2: Directory scope
44+ if scope_is_dir {
45+ let dir_path = return_one_candidate_or_a_good_error (
46+ gcx. clone ( ) ,
47+ & scope_string,
48+ & correct_to_nearest_dir_path ( gcx. clone ( ) , & scope_string, false , 10 ) . await ,
49+ & get_project_dirs ( gcx. clone ( ) ) . await ,
50+ true ,
51+ ) . await ?;
52+
53+ let workspace_files = gcx. read ( ) . await . documents_state . workspace_files . lock ( ) . unwrap ( ) . clone ( ) ;
54+ return Ok ( workspace_files. into_iter ( )
55+ . filter ( |f| f. starts_with ( & dir_path) )
56+ . map ( |f| f. to_string_lossy ( ) . to_string ( ) )
57+ . collect :: < Vec < _ > > ( ) ) ;
58+ }
59+
60+ // Case 3: File scope (with fallback to directory if file not found)
61+ match return_one_candidate_or_a_good_error (
62+ gcx. clone ( ) ,
63+ & scope_string,
64+ & file_repair_candidates ( gcx. clone ( ) , & scope_string, 10 , false ) . await ,
65+ & get_project_dirs ( gcx. clone ( ) ) . await ,
66+ false ,
67+ ) . await {
68+ // File found
69+ Ok ( file_path) => Ok ( vec ! [ file_path] ) ,
70+
71+ // File not found, try as directory
72+ Err ( file_err) => {
73+ match return_one_candidate_or_a_good_error (
74+ gcx. clone ( ) ,
75+ & scope_string,
76+ & correct_to_nearest_dir_path ( gcx. clone ( ) , & scope_string, false , 10 ) . await ,
77+ & get_project_dirs ( gcx. clone ( ) ) . await ,
78+ true ,
79+ ) . await {
80+ // Directory found
81+ Ok ( dir_path) => {
82+ let workspace_files = gcx. read ( ) . await . documents_state . workspace_files . lock ( ) . unwrap ( ) . clone ( ) ;
83+ Ok ( workspace_files. into_iter ( )
84+ . filter ( |f| f. starts_with ( & dir_path) )
85+ . map ( |f| f. to_string_lossy ( ) . to_string ( ) )
86+ . collect :: < Vec < _ > > ( ) )
87+ } ,
88+ // Neither file nor directory found
89+ Err ( _) => Err ( file_err) ,
90+ }
91+ } ,
92+ }
93+ }
94+
95+ /// Creates a SQL-like filter string for the given scope.
96+ /// This is specifically for the search tool which uses SQL-like filters.
97+ ///
98+ /// # Arguments
99+ ///
100+ /// * `gcx` - Global context
101+ /// * `scope` - Scope string
102+ ///
103+ /// # Returns
104+ ///
105+ /// * `Ok(Option<String>)` - SQL-like filter string, or None for workspace scope
106+ /// * `Err(String)` - Error message if scope resolution fails
107+ pub async fn create_scope_filter (
108+ gcx : Arc < ARwLock < GlobalContext > > ,
109+ scope : & str ,
110+ ) -> Result < Option < String > , String > {
111+ let scope_string = scope. to_string ( ) ;
112+ if scope == "workspace" {
113+ return Ok ( None ) ;
114+ }
115+
116+ let scope_is_dir = scope. ends_with ( '/' ) || scope. ends_with ( '\\' ) ;
117+
118+ if scope_is_dir {
119+ let dir_path = return_one_candidate_or_a_good_error (
120+ gcx. clone ( ) ,
121+ & scope_string,
122+ & correct_to_nearest_dir_path ( gcx. clone ( ) , & scope_string, false , 10 ) . await ,
123+ & get_project_dirs ( gcx. clone ( ) ) . await ,
124+ true ,
125+ ) . await ?;
126+
127+ return Ok ( Some ( format ! ( "(scope LIKE '{}%')" , dir_path) ) ) ;
128+ }
129+
130+ match return_one_candidate_or_a_good_error (
131+ gcx. clone ( ) ,
132+ & scope_string,
133+ & file_repair_candidates ( gcx. clone ( ) , & scope_string, 10 , false ) . await ,
134+ & get_project_dirs ( gcx. clone ( ) ) . await ,
135+ false ,
136+ ) . await {
137+ Ok ( file_path) => Ok ( Some ( format ! ( "(scope = \" {}\" )" , file_path) ) ) ,
138+ Err ( file_err) => {
139+ match return_one_candidate_or_a_good_error (
140+ gcx. clone ( ) ,
141+ & scope_string,
142+ & correct_to_nearest_dir_path ( gcx. clone ( ) , & scope_string, false , 10 ) . await ,
143+ & get_project_dirs ( gcx. clone ( ) ) . await ,
144+ true ,
145+ ) . await {
146+ Ok ( dir_path) => Ok ( Some ( format ! ( "(scope LIKE '{}%')" , dir_path) ) ) ,
147+ Err ( _) => Err ( file_err) ,
148+ }
149+ } ,
150+ }
151+ }
152+
153+ /// Validates that the scope is not empty and returns an appropriate error message if it is.
154+ ///
155+ /// # Arguments
156+ ///
157+ /// * `files` - List of files resolved from the scope
158+ /// * `scope` - Original scope string for error reporting
159+ ///
160+ /// # Returns
161+ ///
162+ /// * `Ok(Vec<String>)` - The same list of files if not empty
163+ /// * `Err(String)` - Error message if the list is empty
164+ pub fn validate_scope_files (
165+ files : Vec < String > ,
166+ scope : & str ,
167+ ) -> Result < Vec < String > , String > {
168+ if files. is_empty ( ) {
169+ Err ( format ! ( "No files found in scope: {}" , scope) )
170+ } else {
171+ Ok ( files)
172+ }
173+ }
0 commit comments