@@ -46,24 +46,116 @@ def glob_filtered_to_repo(pattern: str, root: str, repo) -> List[Path]:
4646 else :
4747 try :
4848 raw_matched_files = list (Path (root ).glob (pattern ))
49- except (IndexError , AttributeError ):
49+ except (IndexError , AttributeError , ValueError ):
5050 # Handle patterns like "**/*.py" that might fail on empty dirs
5151 raw_matched_files = []
5252
53- # Filter out directories and ignored files
53+ # Expand directories and filter
5454 matched_files = []
5555 for f in raw_matched_files :
56- if not f .is_file ():
57- continue
58- if repo and repo .ignored_file (f ):
59- continue
60- matched_files .append (f )
56+ matched_files .extend (expand_subdir (f ))
57+
58+ # Filter to repository files
59+ matched_files = [fn .relative_to (root ) for fn in matched_files if fn .is_relative_to (root )]
60+
61+ # if repo, filter against it
62+ if repo :
63+ git_files = repo .get_tracked_files ()
64+ matched_files = [fn for fn in matched_files if str (fn ) in git_files ]
6165
6266 return matched_files
6367 except Exception as e :
6468 raise CommandError (f"Error processing pattern '{ pattern } ': { e } " )
6569
6670
71+ def get_file_completions (
72+ coder ,
73+ args : str = "" ,
74+ completion_type : str = "all" ,
75+ include_directories : bool = False ,
76+ filter_in_chat : bool = False ,
77+ ) -> List [str ]:
78+ """
79+ Get file completions for command line arguments.
80+
81+ This function provides unified file completion logic that can be used by
82+ multiple commands (add, read-only, read-only-stub, etc.).
83+
84+ Args:
85+ coder: Coder instance
86+ args: Command arguments to complete
87+ completion_type: Type of completion to perform:
88+ - "all": Return all available files (default)
89+ - "glob": Treat args as glob pattern and expand
90+ - "directory": Perform directory-based prefix matching
91+ include_directories: Whether to include directories in results
92+ filter_in_chat: Whether to filter out files already in chat
93+
94+ Returns:
95+ List of completion strings (quoted if needed)
96+ """
97+ from pathlib import Path
98+
99+ root = Path (coder .root ) if hasattr (coder , "root" ) else Path .cwd ()
100+
101+ if completion_type == "glob" :
102+ # Handle glob pattern completion
103+ if not args .strip ():
104+ return []
105+
106+ try :
107+ matched_files = glob_filtered_to_repo (args , str (root ), coder .repo )
108+ completions = [str (fn ) for fn in matched_files ]
109+ except CommandError :
110+ completions = []
111+
112+ elif completion_type == "directory" :
113+ # Handle directory-based prefix matching (like read-only commands)
114+ if "/" in args :
115+ # Has directory component
116+ dir_part , file_part = args .rsplit ("/" , 1 )
117+ if dir_part == "" :
118+ search_dir = Path ("/" )
119+ path_prefix = "/"
120+ else :
121+ search_dir = (root / dir_part ).resolve ()
122+ path_prefix = dir_part + "/"
123+ search_prefix = file_part .lower ()
124+ else :
125+ search_dir = root
126+ search_prefix = args .lower ()
127+ path_prefix = ""
128+
129+ completions = []
130+ try :
131+ if search_dir .exists () and search_dir .is_dir ():
132+ for entry in search_dir .iterdir ():
133+ name = entry .name
134+ if search_prefix and not name .lower ().startswith (search_prefix ):
135+ continue
136+
137+ # Add trailing slash for directories if requested
138+ if entry .is_dir () and include_directories :
139+ completions .append (path_prefix + name + "/" )
140+ elif entry .is_file ():
141+ completions .append (path_prefix + name )
142+ except (PermissionError , OSError ):
143+ pass
144+
145+ else : # "all" completion type
146+ # Get all available files
147+ if filter_in_chat :
148+ files = set (coder .get_all_relative_files ())
149+ files = files - set (coder .get_inchat_relative_files ())
150+ completions = list (files )
151+ else :
152+ completions = coder .get_all_relative_files ()
153+
154+ # Quote filenames with spaces
155+ completions = [quote_filename (fn ) for fn in completions ]
156+ return sorted (completions )
157+
158+
67159def validate_file_access (io , coder , file_path : str , require_in_chat : bool = False ) -> bool :
68160 """
69161 Validate file access permissions and state.
@@ -130,13 +222,16 @@ def get_available_files(coder, in_chat: bool = False) -> List[str]:
130222 return coder .get_all_relative_files ()
131223
132224
133- def expand_subdir (file_path ) :
225+ def expand_subdir (file_path : Path ) -> List [ Path ] :
134226 """Expand a directory path to all files within it."""
135227 if file_path .is_file ():
136- yield file_path
137- return
228+ return [file_path ]
138229
139230 if file_path .is_dir ():
231+ files = []
140232 for file in file_path .rglob ("*" ):
141233 if file .is_file ():
142- yield file
234+ files .append (file )
235+ return files
236+
237+ return []
0 commit comments