@@ -61,7 +61,40 @@ export function MentionInput({
61
61
if ( / \s / . test ( charBeforeAt ) || atIndex === 0 ) {
62
62
// Only show if there's no space after @ (still building the mention)
63
63
if ( ! afterAt . includes ( " " ) ) {
64
- return { atIndex, searchText : afterAt } ;
64
+ console . log ( "🔍 [detectAtTrigger] Found @ trigger - afterAt:" , JSON . stringify ( afterAt ) ) ;
65
+
66
+ // Parse the path to find directory and search filter
67
+ const lastSlashIndex = afterAt . lastIndexOf ( '/' ) ;
68
+ if ( lastSlashIndex !== - 1 ) {
69
+ const directory = afterAt . substring ( 0 , lastSlashIndex + 1 ) ; // Include the trailing slash
70
+ const searchFilter = afterAt . substring ( lastSlashIndex + 1 ) ;
71
+
72
+ // Special case: if search filter is empty and we end with slash,
73
+ // we're actually trying to complete the folder name, not search inside it
74
+ if ( searchFilter === "" ) {
75
+ // Remove the trailing slash and treat the last part as search filter INCLUDING the slash
76
+ const folderPath = afterAt . substring ( 0 , lastSlashIndex ) ;
77
+ const parentSlashIndex = folderPath . lastIndexOf ( '/' ) ;
78
+
79
+ if ( parentSlashIndex !== - 1 ) {
80
+ const parentDirectory = folderPath . substring ( 0 , parentSlashIndex + 1 ) ;
81
+ const folderName = folderPath . substring ( parentSlashIndex + 1 ) ;
82
+ console . log ( "🔍 [detectAtTrigger] Folder completion - parentDirectory:" , JSON . stringify ( parentDirectory ) , "folderName:" , JSON . stringify ( folderName + "/" ) ) ;
83
+ return { atIndex, searchText : afterAt , directory : parentDirectory , searchFilter : folderName + "/" } ;
84
+ } else {
85
+ // No parent directory, completing folder in root
86
+ console . log ( "🔍 [detectAtTrigger] Root folder completion - folderName:" , JSON . stringify ( folderPath + "/" ) ) ;
87
+ return { atIndex, searchText : afterAt , directory : "" , searchFilter : folderPath + "/" } ;
88
+ }
89
+ } else {
90
+ console . log ( "🔍 [detectAtTrigger] File search - directory:" , JSON . stringify ( directory ) , "searchFilter:" , JSON . stringify ( searchFilter ) ) ;
91
+ return { atIndex, searchText : afterAt , directory, searchFilter } ;
92
+ }
93
+ } else {
94
+ // No slash, so it's just a search in the current directory
95
+ console . log ( "🔍 [detectAtTrigger] No slash found, treating as root search:" , JSON . stringify ( afterAt ) ) ;
96
+ return { atIndex, searchText : afterAt , directory : "" , searchFilter : afterAt } ;
97
+ }
65
98
}
66
99
}
67
100
}
@@ -74,9 +107,15 @@ export function MentionInput({
74
107
return navState . entries ;
75
108
}
76
109
77
- return navState . entries . filter ( entry =>
78
- entry . name . toLowerCase ( ) . includes ( searchFilter . toLowerCase ( ) )
79
- ) ;
110
+ return navState . entries . filter ( entry => {
111
+ const entryDisplayName = entry . is_directory ? `${ entry . name } /` : entry . name ;
112
+ const searchLower = searchFilter . toLowerCase ( ) ;
113
+ const entryLower = entry . name . toLowerCase ( ) ;
114
+ const entryDisplayLower = entryDisplayName . toLowerCase ( ) ;
115
+
116
+ // Match against both the raw name and the display name (with slash for directories)
117
+ return entryLower . includes ( searchLower ) || entryDisplayLower . includes ( searchLower ) ;
118
+ } ) ;
80
119
} , [ navState . entries , searchFilter ] ) ;
81
120
82
121
// Reset selection when search filter changes or entries change
@@ -109,9 +148,22 @@ export function MentionInput({
109
148
navActions . loadDirectory ( workingDirectory ) ;
110
149
}
111
150
151
+ // If we have a directory path, navigate to that directory
152
+ if ( atTrigger . directory && atTrigger . directory !== "" ) {
153
+ const targetDir = workingDirectory + "/" + atTrigger . directory . replace ( / \/ $ / , "" ) ; // Remove trailing slash
154
+ console . log ( "🔍 [MentionInput] Directory path detected, navigating to:" , targetDir ) ;
155
+ if ( navState . currentPath !== targetDir ) {
156
+ navActions . loadDirectory ( targetDir ) ;
157
+ }
158
+ // Set the search filter to just the filename part
159
+ setSearchFilter ( atTrigger . searchFilter || "" ) ;
160
+ } else {
161
+ // No directory, just search in current directory
162
+ setSearchFilter ( atTrigger . searchFilter || atTrigger . searchText ) ;
163
+ }
164
+
112
165
console . log ( "🔥 [MentionInput] Setting @ trigger state" ) ;
113
166
setAtPosition ( atTrigger . atIndex ) ;
114
- setSearchFilter ( atTrigger . searchText ) ;
115
167
setShowFilePicker ( true ) ;
116
168
setFilteredSelectedIndex ( 0 ) ;
117
169
} else {
@@ -153,11 +205,46 @@ export function MentionInput({
153
205
154
206
const mentionText = entry . is_directory ? `${ entry . name } /` : entry . name ;
155
207
156
- // If this is the first mention (Tab from initial directory), just use the entry name
157
- // If continuing a path, append to the existing mention
158
- const newMentionText = existingMentionText . length === 0 ? mentionText : `${ existingMentionText } ${ mentionText } ` ;
208
+ // Determine if we're completing a partial match or continuing a path
209
+ let newMentionText ;
159
210
160
- const newValue = `${ beforeAt } @${ newMentionText } ${ afterAtAndMention } ` ;
211
+ if ( existingMentionText . length === 0 ) {
212
+ // No existing mention, just use the entry name
213
+ newMentionText = mentionText ;
214
+ } else {
215
+ // Check if the existing text exactly matches the selected entry
216
+ // This handles the case where user types "@.folder/" and presses Enter on ".folder"
217
+ if ( existingMentionText === mentionText ) {
218
+ // Exact match - user has already typed the complete name, don't duplicate
219
+ newMentionText = mentionText ;
220
+ } else {
221
+ // Check if we're completing a partial match (e.g., ".gi" -> ".git")
222
+ // or continuing a path (e.g., ".git/" -> ".git/hooks")
223
+ const lastSlashIndex = existingMentionText . lastIndexOf ( '/' ) ;
224
+
225
+ if ( lastSlashIndex === existingMentionText . length - 1 ) {
226
+ // Existing text ends with slash, we're continuing a path
227
+ newMentionText = `${ existingMentionText } ${ mentionText } ` ;
228
+ } else {
229
+ // No trailing slash, we're completing a partial match
230
+ // Replace the partial match with the full entry name
231
+ if ( lastSlashIndex !== - 1 ) {
232
+ // There's a path before the partial match
233
+ const pathPrefix = existingMentionText . substring ( 0 , lastSlashIndex + 1 ) ;
234
+ newMentionText = `${ pathPrefix } ${ mentionText } ` ;
235
+ } else {
236
+ // No path, just replace the entire partial match
237
+ newMentionText = mentionText ;
238
+ }
239
+ }
240
+ }
241
+ }
242
+
243
+ // Only add space after mention if it's not a folder (folders end with / and shouldn't have space)
244
+ const addSpace = ! newMentionText . endsWith ( '/' ) ;
245
+ const newValue = addSpace
246
+ ? `${ beforeAt } @${ newMentionText } ${ afterAtAndMention } `
247
+ : `${ beforeAt } @${ newMentionText } ${ afterAtAndMention } ` ;
161
248
162
249
console . log ( "🔥 [MentionInput] Building mention - existing:" , existingMentionText , "entry:" , mentionText , "final:" , newMentionText ) ;
163
250
console . log ( "🔥 [MentionInput] New value:" , newValue ) ;
@@ -249,23 +336,38 @@ export function MentionInput({
249
336
} else if ( e . key === "Tab" ) {
250
337
e . preventDefault ( ) ;
251
338
console . log ( "🔥 [MentionInput] Tab pressed" ) ;
252
- if ( selectedEntry && selectedEntry . is_directory ) {
253
- console . log ( "⌨️ [MentionInput] Tab: Adding folder to mentions AND navigating to directory:" , selectedEntry . name ) ;
339
+
340
+ if ( selectedEntry ) {
341
+ const filteredEntries = getFilteredEntries ( ) ;
342
+ const hasDirectories = filteredEntries . some ( entry => entry . is_directory ) ;
254
343
255
- // First, add the folder to mentions (but keep picker open)
256
- console . log ( "🔥 [MentionInput] About to call addMentionToInput with closePicker=false" ) ;
257
- addMentionToInput ( selectedEntry , false ) ;
344
+ console . log ( "🎯 [MentionInput] Smart Tab logic - hasDirectories:" , hasDirectories , "selectedEntry.is_directory:" , selectedEntry . is_directory ) ;
258
345
259
- // Then navigate into the folder
260
- console . log ( "🔥 [MentionInput] About to call navigateToFolder to show contents" ) ;
261
- navActions . navigateToFolder ( selectedEntry . name ) ;
262
- console . log ( "🔥 [MentionInput] Clearing search filter" ) ;
263
- setSearchFilter ( "" ) ; // Clear search when navigating
264
- console . log ( "🔥 [MentionInput] Resetting filteredSelectedIndex to 0" ) ;
265
- setFilteredSelectedIndex ( 0 ) ;
266
- console . log ( "🔥 [MentionInput] Tab completed - added mention and navigated" ) ;
346
+ if ( selectedEntry . is_directory && hasDirectories ) {
347
+ // There are directories to navigate into, so navigate
348
+ console . log ( "⌨️ [MentionInput] Tab: Adding folder to mentions AND navigating to directory:" , selectedEntry . name ) ;
349
+
350
+ // First, add the folder to mentions (but keep picker open)
351
+ console . log ( "🔥 [MentionInput] About to call addMentionToInput with closePicker=false" ) ;
352
+ addMentionToInput ( selectedEntry , false ) ;
353
+
354
+ // Then navigate into the folder
355
+ console . log ( "🔥 [MentionInput] About to call navigateToFolder to show contents" ) ;
356
+ navActions . navigateToFolder ( selectedEntry . name ) ;
357
+ console . log ( "🔥 [MentionInput] Clearing search filter" ) ;
358
+ setSearchFilter ( "" ) ; // Clear search when navigating
359
+ console . log ( "🔥 [MentionInput] Resetting filteredSelectedIndex to 0" ) ;
360
+ setFilteredSelectedIndex ( 0 ) ;
361
+ console . log ( "🔥 [MentionInput] Tab completed - added mention and navigated" ) ;
362
+ } else {
363
+ // No directories to navigate into, or selected item is a file - behave like Enter
364
+ console . log ( "⌨️ [MentionInput] Tab: No directories to navigate, selecting item like Enter:" , selectedEntry . name ) ;
365
+ console . log ( "🔥 [MentionInput] About to call handleFileSelection" ) ;
366
+ handleFileSelection ( selectedEntry ) ;
367
+ console . log ( "🔥 [MentionInput] Tab selection completed" ) ;
368
+ }
267
369
} else {
268
- console . log ( "🔥 [MentionInput] Tab pressed but selectedEntry is not a directory or null " ) ;
370
+ console . log ( "🔥 [MentionInput] Tab pressed but no selectedEntry " ) ;
269
371
}
270
372
return ;
271
373
} else if ( e . key === "Escape" ) {
0 commit comments