2 * FILECOMP.C - handles filename completion.
7 * 30-Jul-1998 (John P Price <linux-guru@gcfl.net>)
8 * moved from command.c file
9 * made second TAB display list of filename matches
10 * made filename be lower case if last character typed is lower case
12 * 25-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
13 * Cleanup. Unicode safe!
15 * 30-Apr-2004 (Filip Navara <xnavara@volny.cz>)
16 * Make the file listing readable when there is a lot of long names.
19 * 05-Jul-2004 (Jens Collin <jens.collin@lakhei.com>)
20 * Now expands lfn even when trailing " is omitted.
26 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
28 VOID
CompleteFilename (LPTSTR str
, INT charcount
)
37 BOOL found_dot
= FALSE
;
38 BOOL perfectmatch
= TRUE
;
40 TCHAR fname
[MAX_PATH
];
41 TCHAR maxmatch
[MAX_PATH
] = _T("");
42 TCHAR directory
[MAX_PATH
];
45 /* expand current file name */
46 count
= charcount
- 1;
50 /* find how many '"'s there is typed already.*/
54 if (str
[step
] == _T('"'))
58 /* if c is odd, then user typed " before name, else not.*/
60 /* find front of word */
61 if (str
[count
] == _T('"') || (c
% 2))
64 while (count
> 0 && str
[count
] != _T('"'))
69 while (count
> 0 && str
[count
] != _T(' '))
73 /* if not at beginning, go forward 1 */
74 if (str
[count
] == _T(' '))
79 if (str
[count
] == _T('"'))
80 count
++; /* don't increment start */
82 /* extract directory from word */
83 _tcscpy (directory
, &str
[count
]);
84 curplace
= _tcslen (directory
) - 1;
86 if (curplace
>= 0 && directory
[curplace
] == _T('"'))
87 directory
[curplace
--] = _T('\0');
89 _tcscpy (path
, directory
);
91 while (curplace
>= 0 && directory
[curplace
] != _T('\\') &&
92 directory
[curplace
] != _T(':'))
94 directory
[curplace
] = 0;
98 /* look for a '.' in the filename */
99 for (count
= _tcslen (directory
); path
[count
] != _T('\0'); count
++)
101 if (path
[count
] == _T('.'))
109 _tcscat (path
, _T("*"));
111 _tcscat (path
, _T("*.*"));
116 hFile
= FindFirstFile (path
, &file
);
117 if (hFile
!= INVALID_HANDLE_VALUE
)
122 /* ignore "." and ".." */
123 if (!_tcscmp (file
.cFileName
, _T(".")) ||
124 !_tcscmp (file
.cFileName
, _T("..")))
127 _tcscpy (fname
, file
.cFileName
);
129 if (file
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
130 _tcscat (fname
, _T("\\"));
132 if (!maxmatch
[0] && perfectmatch
)
134 _tcscpy(maxmatch
, fname
);
138 for (count
= 0; maxmatch
[count
] && fname
[count
]; count
++)
140 if (tolower(maxmatch
[count
]) != tolower(fname
[count
]))
142 perfectmatch
= FALSE
;
148 if (maxmatch
[count
] == _T('\0') &&
149 fname
[count
] != _T('\0'))
150 perfectmatch
= FALSE
;
153 while (FindNextFile (hFile
, &file
));
157 /* only quote if the filename contains spaces */
158 if (_tcschr(directory
, _T(' ')) ||
159 _tcschr(maxmatch
, _T(' ')))
161 str
[start
] = _T('\"');
162 _tcscpy (&str
[start
+1], directory
);
163 _tcscat (&str
[start
], maxmatch
);
164 _tcscat (&str
[start
], _T("\"") );
168 _tcscpy (&str
[start
], directory
);
169 _tcscat (&str
[start
], maxmatch
);
183 /* no match found - search for internal command */
184 for (cmds_ptr
= cmds
; cmds_ptr
->name
; cmds_ptr
++)
186 if (!_tcsnicmp (&str
[start
], cmds_ptr
->name
,
187 _tcslen (&str
[start
])))
189 /* return the mach only if it is unique */
190 if (_tcsnicmp (&str
[start
], (cmds_ptr
+1)->name
, _tcslen (&str
[start
])))
191 _tcscpy (&str
[start
], cmds_ptr
->name
);
206 * returns 1 if at least one match, else returns 0
209 BOOL
ShowCompletionMatches (LPTSTR str
, INT charcount
)
211 WIN32_FIND_DATA file
;
213 BOOL found_dot
= FALSE
;
217 TCHAR path
[MAX_PATH
];
218 TCHAR fname
[MAX_PATH
];
219 TCHAR directory
[MAX_PATH
];
220 UINT longestfname
= 0;
223 /* expand current file name */
224 count
= charcount
- 1;
228 /* find front of word */
229 if (str
[count
] == _T('"'))
232 while (count
> 0 && str
[count
] != _T('"'))
237 while (count
> 0 && str
[count
] != _T(' '))
241 /* if not at beginning, go forward 1 */
242 if (str
[count
] == _T(' '))
247 if (str
[count
] == _T('"'))
248 count
++; /* don't increment start */
250 /* extract directory from word */
251 _tcscpy (directory
, &str
[count
]);
252 curplace
= _tcslen (directory
) - 1;
254 if (curplace
>= 0 && directory
[curplace
] == _T('"'))
255 directory
[curplace
--] = _T('\0');
257 _tcscpy (path
, directory
);
259 while (curplace
>= 0 &&
260 directory
[curplace
] != _T('\\') &&
261 directory
[curplace
] != _T(':'))
263 directory
[curplace
] = 0;
267 /* look for a . in the filename */
268 for (count
= _tcslen (directory
); path
[count
] != _T('\0'); count
++)
270 if (path
[count
] == _T('.'))
278 _tcscat (path
, _T("*"));
280 _tcscat (path
, _T("*.*"));
285 hFile
= FindFirstFile (path
, &file
);
286 if (hFile
!= INVALID_HANDLE_VALUE
)
288 /* Get the size of longest filename first. */
291 if (_tcslen(file
.cFileName
) > longestfname
)
293 longestfname
= _tcslen(file
.cFileName
);
294 /* Directories get extra brackets around them. */
295 if (file
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
299 while (FindNextFile (hFile
, &file
));
302 hFile
= FindFirstFile (path
, &file
);
304 /* Count the highest number of columns */
305 GetScreenSize(&screenwidth
, 0);
307 /* For counting columns of output */
310 /* Increase by the number of spaces behind file name */
314 ConOutChar (_T('\n'));
317 /* ignore . and .. */
318 if (!_tcscmp (file
.cFileName
, _T(".")) ||
319 !_tcscmp (file
.cFileName
, _T("..")))
322 if (file
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
323 _stprintf (fname
, _T("[%s]"), file
.cFileName
);
325 _tcscpy (fname
, file
.cFileName
);
327 ConOutPrintf (_T("%*s"), - longestfname
, fname
);
329 /* output as much columns as fits on the screen */
330 if (count
>= (screenwidth
/ longestfname
))
332 /* print the new line only if we aren't on the
333 * last column, in this case it wraps anyway */
334 if (count
* longestfname
!= (UINT
)screenwidth
)
335 ConOutPrintf (_T("\n"));
339 while (FindNextFile (hFile
, &file
));
344 ConOutChar (_T('\n'));
361 #ifdef FEATURE_4NT_FILENAME_COMPLETION
363 typedef struct _FileName
365 TCHAR Name
[MAX_PATH
];
368 VOID
FindPrefixAndSuffix(LPTSTR strIN
, LPTSTR szPrefix
, LPTSTR szSuffix
)
370 /* String that is to be examined */
372 /* temp pointers to used to find needed parts */
376 /* number of quotes in the string */
378 /* used in for loops */
380 /* Char number to break the string at */
383 /* when phrasing a string, this tells weather
384 you are inside quotes ot not. */
385 BOOL bInside
= FALSE
;
387 szPrefix
[0] = _T('\0');
388 szSuffix
[0] = _T('\0');
390 /* Copy over the string to later be edited */
393 /* Count number of " */
394 for(i
= 0; i
< _tcslen(str
); i
++)
395 if(str
[i
] == _T('\"'))
398 /* Find the prefix and suffix */
399 if(nQuotes
% 2 && nQuotes
> 1)
401 /* Odd number of quotes. Just start from the last " */
402 /* THis is the way MS does it, and is an easy way out */
403 szSearch
= _tcsrchr(str
, _T('\"'));
404 /* Move to the next char past the " */
406 _tcscpy(szSuffix
,szSearch
);
407 /* Find the one closest to end */
408 szSearch1
= _tcsrchr(str
, _T('\"'));
409 szSearch2
= _tcsrchr(str
, _T('\\'));
410 if(szSearch2
!= NULL
&& _tcslen(szSearch1
) > _tcslen(szSearch2
))
411 szSearch
= szSearch2
;
413 szSearch
= szSearch1
;
414 /* Move one char past */
416 szSearch
[0] = _T('\0');
417 _tcscpy(szPrefix
,str
);
422 if(!_tcschr(str
, _T(' ')))
424 /* No spaces, everything goes to Suffix */
425 _tcscpy(szSuffix
,str
);
426 /* look for a slash just in case */
427 szSearch
= _tcsrchr(str
, _T('\\'));
431 szSearch
[0] = _T('\0');
432 _tcscpy(szPrefix
,str
);
436 szPrefix
[0] = _T('\0');
443 /* No quotes, and there is a space*/
444 /* Take it after the last space */
445 szSearch
= _tcsrchr(str
, _T(' '));
447 _tcscpy(szSuffix
,szSearch
);
448 /* Find the closest to the end space or \ */
450 szSearch1
= _tcsrchr(str
, _T(' '));
451 szSearch2
= _tcsrchr(str
, _T('\\'));
452 if(szSearch2
!= NULL
&& _tcslen(szSearch1
) > _tcslen(szSearch2
))
453 szSearch
= szSearch2
;
455 szSearch
= szSearch1
;
457 szSearch
[0] = _T('\0');
458 _tcscpy(szPrefix
,str
);
462 /* All else fails and there is a lot of quotes, spaces and |
463 Then we search through and find the last space or \ that is
464 not inside a quotes */
465 for(i
= 0; i
< _tcslen(str
); i
++)
467 if(str
[i
] == _T('\"'))
469 if(str
[i
] == _T(' ') && !bInside
)
471 if((str
[i
] == _T(' ') || str
[i
] == _T('\\')) && !bInside
)
477 _tcscpy(szSuffix
,&strIN
[SBreak
]);
478 strIN
[PBreak
] = _T('\0');
479 _tcscpy(szPrefix
,strIN
);
480 if(szPrefix
[_tcslen(szPrefix
) - 2] == _T('\"'))
482 /* need to remove the " right before a \ at the end to
483 allow the next stuff to stay inside one set of quotes
484 otherwise you would have multiple sets of quotes*/
485 _tcscpy(&szPrefix
[_tcslen(szPrefix
) - 2],_T("\\"));
490 int compare(const void *arg1
,const void *arg2
)
496 File1
= malloc(sizeof(FileName
));
497 File2
= malloc(sizeof(FileName
));
501 memcpy(File1
,arg1
,sizeof(FileName
));
502 memcpy(File2
,arg2
,sizeof(FileName
));
504 /* ret = _tcsicmp(File1->Name, File2->Name); */
505 ret
= lstrcmpi(File1
->Name
, File2
->Name
);
512 VOID
CompleteFilename (LPTSTR strIN
, BOOL bNext
, LPTSTR strOut
, INT cusor
)
514 /* Length of string before we complete it */
516 /* Length of string after completed */
518 /* The number of chars added too it */
519 static INT DiffLength
= 0;
520 /* Used to find and assemble the string that is returned */
521 TCHAR szBaseWord
[MAX_PATH
];
522 TCHAR szPrefix
[MAX_PATH
];
523 TCHAR szOrginal
[MAX_PATH
];
524 TCHAR szSearchPath
[MAX_PATH
];
525 /* Save the strings used last time, so if they hit tab again */
526 static TCHAR LastReturned
[MAX_PATH
];
527 static TCHAR LastSearch
[MAX_PATH
];
528 static TCHAR LastPrefix
[MAX_PATH
];
529 /* Used to search for files */
531 WIN32_FIND_DATA file
;
532 /* List of all the files */
533 FileName
* FileList
= NULL
;
534 /* Number of files */
535 INT FileListSize
= 0;
538 /* Editable string of what was passed in */
540 /* Keeps track of what element was last selected */
542 BOOL NeededQuote
= FALSE
;
543 strOut
[0] = _T('\0');
545 /* Copy the string, str can be edited and orginal should not be */
547 _tcscpy(szOrginal
,strIN
);
549 /* Look to see if the cusor is not at the end of the string */
550 if((cusor
+ 1) < _tcslen(str
))
551 str
[cusor
] = _T('\0');
553 /* Look to see if they hit tab again, if so cut off the diff length */
554 if(_tcscmp(str
,LastReturned
) || !_tcslen(str
))
556 /* We need to know how many chars we added from the start */
557 StartLength
= _tcslen(str
);
559 /* no string, we need all files in that directory */
562 _tcscat(str
,_T("*"));
565 /* Zero it out first */
566 szBaseWord
[0] = _T('\0');
567 szPrefix
[0] = _T('\0');
569 /*What comes out of this needs to be:
570 szBaseWord = path no quotes to the object
571 szPrefix = what leads up to the filename
572 no quote at the END of the full name */
573 FindPrefixAndSuffix(str
,szPrefix
,szBaseWord
);
575 while(i
< _tcslen(szBaseWord
)+1)
577 if(szBaseWord
[i
] == _T('\"'))
578 memmove(&szBaseWord
[i
],&szBaseWord
[i
+ 1], _tcslen(&szBaseWord
[i
]) * sizeof(TCHAR
));
584 memset(szSearchPath
, 0, sizeof(szSearchPath
));
586 /* Start the search for all the files */
587 GetFullPathName(szBaseWord
, MAX_PATH
, szSearchPath
, NULL
);
589 _tcscat(szSearchPath
,_T("*"));
590 _tcscpy(LastSearch
,szSearchPath
);
591 _tcscpy(LastPrefix
,szPrefix
);
595 _tcscpy(szSearchPath
, LastSearch
);
596 _tcscpy(szPrefix
, LastPrefix
);
599 /* search for the files it might be */
600 hFile
= FindFirstFile (szSearchPath
, &file
);
602 /* aseemble a list of all files names */
605 if(hFile
== INVALID_HANDLE_VALUE
)
607 /* Assemble the orginal string and return */
608 _tcscpy(strOut
,szOrginal
);
615 if(!_tcscmp (file
.cFileName
, _T(".")) ||
616 !_tcscmp (file
.cFileName
, _T("..")))
618 /* Add the file to the list of files */
622 FileList
= malloc(FileListSize
* sizeof(FileName
));
627 FileList
= realloc(FileList
, FileListSize
* sizeof(FileName
));
632 /* Assemble the orginal string and return */
633 _tcscpy(strOut
,szOrginal
);
635 ConOutFormatMessage (GetLastError());
638 /* Copies the file name into the struct */
639 _tcscpy(FileList
[FileListSize
-1].Name
,file
.cFileName
);
641 }while(FindNextFile(hFile
,&file
));
645 qsort(FileList
,FileListSize
,sizeof(FileName
), compare
);
647 /* Find the next/previous */
648 if(!_tcscmp(szOrginal
,LastReturned
))
652 if(FileListSize
- 1 == Sel
)
660 Sel
= FileListSize
- 1;
670 /* nothing found that matched last time
671 so return the first thing in the list */
672 strOut
[0] = _T('\0');
674 /* space in the name */
675 if(_tcschr(FileList
[Sel
].Name
, _T(' ')))
679 /* It needs a " at the end */
683 /* Find the place to put the " at the start */
684 for(i
= 0; i
< _tcslen(szPrefix
); i
++)
686 if(szPrefix
[i
] == _T('\"'))
688 if(szPrefix
[i
] == _T(' ') && !bInside
)
692 /* insert the quoation and move things around */
693 if(szPrefix
[LastSpace
+ 1] == _T('\"') && LastSpace
!= -1)
695 /* add another char or you will lose a null char ending */
696 _tcsncat(szPrefix
,&szPrefix
[_tcslen(szPrefix
) - 1],1);
697 for(i
= _tcslen(szPrefix
) - 1; i
> LastSpace
; i
--)
699 szPrefix
[i
] = szPrefix
[i
- 1];
702 if(LastSpace
+ 1 == _tcslen(szPrefix
))
704 _tcscat(szPrefix
,_T("\""));
706 szPrefix
[LastSpace
+ 1] = _T('\"');
708 else if(LastSpace
== -1)
710 _tcscpy(szBaseWord
,_T("\""));
711 _tcscat(szBaseWord
,szPrefix
);
712 _tcscpy(szPrefix
,szBaseWord
);
717 _tcscpy(strOut
,szPrefix
);
718 _tcscat(strOut
,FileList
[Sel
].Name
);
720 /* check for odd number of quotes means we need to close them */
723 for(i
= 0; i
< _tcslen(strOut
); i
++)
724 if(strOut
[i
] == _T('\"'))
725 NeededQuote
= !NeededQuote
;
728 if(szPrefix
[_tcslen(szPrefix
) - 1] == _T('\"') || NeededQuote
)
729 _tcscat(strOut
,_T("\""));
731 _tcscpy(LastReturned
,strOut
);
732 EndLength
= _tcslen(strOut
);
733 DiffLength
= EndLength
- StartLength
;