@@ -16,6 +16,10 @@ export class KhojSearchModal extends SuggestModal<SearchResult> {
1616 currentController : AbortController | null = null ; // To cancel requests
1717 isLoading : boolean = false ;
1818 loadingEl : HTMLElement ;
19+ private isFileFilterMode : boolean = false ;
20+ private fileSelected : string = "" ;
21+ private allFiles : Array < { path : string , inVault : boolean } > = [ ] ;
22+ private resultsTitle : HTMLDivElement ;
1923
2024 constructor ( app : App , setting : KhojSetting , find_similar_notes : boolean = false ) {
2125 super ( app ) ;
@@ -85,6 +89,46 @@ export class KhojSearchModal extends SuggestModal<SearchResult> {
8589
8690 // Set Placeholder Text for Modal
8791 this . setPlaceholder ( 'Search with Khoj...' ) ;
92+
93+ // Initialize allFiles with files in vault
94+ this . allFiles = this . app . vault . getFiles ( ) . map ( file => ( {
95+ path : file . path ,
96+ inVault : true
97+ } ) ) ;
98+
99+ // Update isFileFilterMode when input changes
100+ this . inputEl . addEventListener ( 'input' , ( ) => {
101+ // Match file: at the end of input, with an optional unquoted partial path
102+ const fileFilterMatch = this . inputEl . value . match ( / f i l e : ( [ ^ " \s ] * ) $ / ) ;
103+ if ( fileFilterMatch ) {
104+ // Enter file filter mode when we see an unquoted file: token
105+ this . isFileFilterMode = true ;
106+ } else {
107+ // Exit file filter mode when input no longer ends with an unquoted file: token
108+ this . isFileFilterMode = false ;
109+ this . fileSelected = "" ;
110+ }
111+ } ) ;
112+
113+ // Override selectSuggestion to prevent modal close during file filter selection
114+ const originalSelectSuggestion = this . selectSuggestion . bind ( this ) ;
115+ this . selectSuggestion = async ( value : SearchResult & { inVault : boolean } , evt : MouseEvent | KeyboardEvent ) => {
116+ if ( this . isFileFilterMode ) {
117+ // In file filter mode, handle selection without closing the modal
118+ await this . onChooseSuggestion ( value , evt ) ;
119+ } else {
120+ // For normal search results, use the original behavior
121+ originalSelectSuggestion ( value , evt ) ;
122+ }
123+ } ;
124+
125+ // Add title element
126+ this . resultsTitle = createDiv ( ) ;
127+ this . resultsTitle . style . padding = "8px" ;
128+ this . resultsTitle . style . fontWeight = "bold" ;
129+
130+ // Insert title before results container
131+ this . resultContainerEl . parentElement ?. insertBefore ( this . resultsTitle , this . resultContainerEl ) ;
88132 }
89133
90134 // Check if the file exists in the vault
@@ -99,7 +143,30 @@ export class KhojSearchModal extends SuggestModal<SearchResult> {
99143 }
100144
101145 async getSuggestions ( query : string ) : Promise < SearchResult [ ] > {
102- // Do not show loading if the query is empty
146+ // Check if we are in file filter mode and input matches file filter pattern
147+ const fileFilterMatch = query . match ( / f i l e : ( [ ^ " \s ] * ) $ / ) ;
148+ if ( this . isFileFilterMode && fileFilterMatch ) {
149+ const partialPath = fileFilterMatch [ 1 ] || '' ;
150+ // Update title for file filter mode
151+ this . resultsTitle . setText ( "Select a file:" ) ;
152+ // Return filtered file suggestions
153+ return this . allFiles
154+ . filter ( file => file . path . toLowerCase ( ) . includes ( partialPath . toLowerCase ( ) . trim ( ) ) )
155+ . map ( file => ( {
156+ entry : file . path ,
157+ file : file . path ,
158+ inVault : file . inVault
159+ } ) ) ;
160+ }
161+
162+ // Update title for search results
163+ if ( query . trim ( ) ) {
164+ this . resultsTitle . setText ( "Search results:" ) ;
165+ } else {
166+ this . resultsTitle . setText ( "" ) ;
167+ }
168+
169+ // If not in file filter mode, continue with normal search
103170 if ( ! query . trim ( ) ) {
104171 this . isLoading = false ;
105172 this . updateLoadingState ( ) ;
@@ -138,22 +205,29 @@ export class KhojSearchModal extends SuggestModal<SearchResult> {
138205
139206 const data = await response . json ( ) ;
140207
141- // Parse search results
208+ // Parse search results and update allFiles with any new non-vault files
142209 let results = data
143210 . filter ( ( result : any ) =>
144211 ! this . find_similar_notes || ! result . additional . file . endsWith ( this . app . workspace . getActiveFile ( ) ?. path )
145212 )
146213 . map ( ( result : any ) => {
214+ const isInVault = this . isFileInVault ( result . additional . file ) ;
215+
216+ // Add new non-vault files to allFiles if they don't exist
217+ if ( ! this . allFiles . some ( file => file . path === result . additional . file ) ) {
218+ this . allFiles . push ( {
219+ path : result . additional . file ,
220+ inVault : isInVault
221+ } ) ;
222+ }
223+
147224 return {
148225 entry : result . entry ,
149226 file : result . additional . file ,
150- inVault : this . isFileInVault ( result . additional . file )
227+ inVault : isInVault
151228 } as SearchResult & { inVault : boolean } ;
152229 } )
153- . sort ( ( a : SearchResult & { inVault : boolean } , b : SearchResult & { inVault : boolean } ) => {
154- if ( a . inVault === b . inVault ) return 0 ;
155- return a . inVault ? - 1 : 1 ;
156- } ) ;
230+ . sort ( ( a : SearchResult & { inVault : boolean } , b : SearchResult & { inVault : boolean } ) => Number ( b . inVault ) - Number ( a . inVault ) ) ;
157231
158232 this . query = query ;
159233
@@ -203,6 +277,15 @@ export class KhojSearchModal extends SuggestModal<SearchResult> {
203277 }
204278
205279 async renderSuggestion ( result : SearchResult & { inVault : boolean } , el : HTMLElement ) {
280+ if ( this . isFileFilterMode ) {
281+ // Render file suggestions
282+ el . createEl ( "div" , {
283+ text : result . entry ,
284+ cls : "khoj-file-suggestion"
285+ } ) ;
286+ return ;
287+ }
288+
206289 // Max number of lines to render
207290 let lines_to_render = 8 ;
208291
@@ -251,6 +334,20 @@ export class KhojSearchModal extends SuggestModal<SearchResult> {
251334 }
252335
253336 async onChooseSuggestion ( result : SearchResult & { inVault : boolean } , _ : MouseEvent | KeyboardEvent ) {
337+ if ( this . isFileFilterMode ) {
338+ // When a file suggestion is selected, append it to the current input
339+ const currentValue = this . inputEl . value ;
340+ const beforeFile = currentValue . substring ( 0 , currentValue . lastIndexOf ( 'file:' ) ) ;
341+ this . inputEl . value = `${ beforeFile } file:"${ result . entry } "` ;
342+ // Set fileSelected to the selected file
343+ this . fileSelected = result . entry ;
344+ // Reset isFileFilterMode when a file is selected
345+ this . isFileFilterMode = false ;
346+ // Trigger input event to refresh suggestions
347+ this . inputEl . dispatchEvent ( new Event ( 'input' ) ) ;
348+ return ;
349+ }
350+
254351 // Only open files that are in the vault
255352 if ( ! result . inVault ) {
256353 new Notice ( "This file is not in your vault" ) ;
0 commit comments