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)
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.
25 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
27 VOID
CompleteFilename (LPTSTR str
, UINT charcount
)
36 BOOL found_dot
= FALSE
;
37 BOOL perfectmatch
= TRUE
;
39 TCHAR fname
[MAX_PATH
];
40 TCHAR maxmatch
[MAX_PATH
] = _T("");
41 TCHAR directory
[MAX_PATH
];
44 /* expand current file name */
45 count
= charcount
- 1;
49 /* find how many '"'s there is typed already. */
53 if (str
[step
] == _T('"'))
57 /* if c is odd, then user typed " before name, else not. */
59 /* find front of word */
60 if (str
[count
] == _T('"') || (c
% 2))
63 while (count
> 0 && str
[count
] != _T('"'))
68 while (count
> 0 && str
[count
] != _T(' '))
72 /* if not at beginning, go forward 1 */
73 if (str
[count
] == _T(' '))
78 if (str
[count
] == _T('"'))
79 count
++; /* don't increment start */
81 /* extract directory from word */
82 _tcscpy (directory
, &str
[count
]);
83 curplace
= _tcslen (directory
) - 1;
85 if (curplace
>= 0 && directory
[curplace
] == _T('"'))
86 directory
[curplace
--] = _T('\0');
88 _tcscpy (path
, directory
);
90 while (curplace
>= 0 && directory
[curplace
] != _T('\\') &&
91 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
);
179 /* no match found - search for internal command */
180 for (cmds_ptr
= cmds
; cmds_ptr
->name
; cmds_ptr
++)
182 if (!_tcsnicmp (&str
[start
], cmds_ptr
->name
,
183 _tcslen (&str
[start
])))
185 /* return the mach only if it is unique */
186 if (_tcsnicmp (&str
[start
], (cmds_ptr
+1)->name
, _tcslen (&str
[start
])))
187 _tcscpy (&str
[start
], cmds_ptr
->name
);
198 * returns 1 if at least one match, else returns 0
200 BOOL
ShowCompletionMatches (LPTSTR str
, INT charcount
)
202 WIN32_FIND_DATA file
;
204 BOOL found_dot
= FALSE
;
207 TCHAR path
[MAX_PATH
];
208 TCHAR fname
[MAX_PATH
];
209 TCHAR directory
[MAX_PATH
];
212 /* expand current file name */
213 count
= charcount
- 1;
217 /* find front of word */
218 if (str
[count
] == _T('"'))
221 while (count
> 0 && str
[count
] != _T('"'))
226 while (count
> 0 && str
[count
] != _T(' '))
230 /* if not at beginning, go forward 1 */
231 if (str
[count
] == _T(' '))
234 if (str
[count
] == _T('"'))
237 /* extract directory from word */
238 _tcscpy (directory
, &str
[count
]);
239 curplace
= _tcslen (directory
) - 1;
241 if (curplace
>= 0 && directory
[curplace
] == _T('"'))
242 directory
[curplace
--] = _T('\0');
244 _tcscpy (path
, directory
);
246 while (curplace
>= 0 &&
247 directory
[curplace
] != _T('\\') &&
248 directory
[curplace
] != _T(':'))
250 directory
[curplace
] = 0;
254 /* look for a . in the filename */
255 for (count
= _tcslen (directory
); path
[count
] != _T('\0'); count
++)
257 if (path
[count
] == _T('.'))
265 _tcscat (path
, _T("*"));
267 _tcscat (path
, _T("*.*"));
272 hFile
= FindFirstFile (path
, &file
);
273 if (hFile
!= INVALID_HANDLE_VALUE
)
275 UINT longestfname
= 0;
276 /* Get the size of longest filename first. */
279 if (_tcslen(file
.cFileName
) > longestfname
)
281 longestfname
= _tcslen(file
.cFileName
);
282 /* Directories get extra brackets around them. */
283 if (file
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
287 while (FindNextFile (hFile
, &file
));
290 hFile
= FindFirstFile (path
, &file
);
292 /* Count the highest number of columns */
293 GetScreenSize(&screenwidth
, NULL
);
295 /* For counting columns of output */
298 /* Increase by the number of spaces behind file name */
302 ConOutChar(_T('\n'));
305 /* ignore . and .. */
306 if (!_tcscmp (file
.cFileName
, _T(".")) ||
307 !_tcscmp (file
.cFileName
, _T("..")))
310 if (file
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
311 _stprintf (fname
, _T("[%s]"), file
.cFileName
);
313 _tcscpy (fname
, file
.cFileName
);
315 ConOutPrintf (_T("%*s"), - longestfname
, fname
);
317 /* output as much columns as fits on the screen */
318 if (count
>= (screenwidth
/ longestfname
))
320 /* print the new line only if we aren't on the
321 * last column, in this case it wraps anyway */
322 if (count
* longestfname
!= (UINT
)screenwidth
)
323 ConOutChar(_T('\n'));
327 while (FindNextFile (hFile
, &file
));
332 ConOutChar(_T('\n'));
345 #ifdef FEATURE_4NT_FILENAME_COMPLETION
347 typedef struct _FileName
349 TCHAR Name
[MAX_PATH
];
352 VOID
FindPrefixAndSuffix(LPTSTR strIN
, LPTSTR szPrefix
, LPTSTR szSuffix
)
354 /* String that is to be examined */
356 /* temp pointers to used to find needed parts */
361 /* number of quotes in the string */
363 /* used in for loops */
365 /* Char number to break the string at */
368 /* when phrasing a string, this tells weather
369 you are inside quotes ot not. */
370 BOOL bInside
= FALSE
;
372 szPrefix
[0] = _T('\0');
373 szSuffix
[0] = _T('\0');
375 /* Copy over the string to later be edited */
378 /* Count number of " */
379 for(i
= 0; i
< _tcslen(str
); i
++)
381 if (str
[i
] == _T('\"'))
385 /* Find the prefix and suffix */
386 if (nQuotes
% 2 && nQuotes
>= 1)
388 /* Odd number of quotes. Just start from the last " */
389 /* THis is the way MS does it, and is an easy way out */
390 szSearch
= _tcsrchr(str
, _T('\"'));
391 /* Move to the next char past the " */
393 _tcscpy(szSuffix
,szSearch
);
394 /* Find the one closest to end */
395 szSearch1
= _tcsrchr(str
, _T('\"'));
396 szSearch2
= _tcsrchr(str
, _T('\\'));
397 szSearch3
= _tcsrchr(str
, _T('.'));
398 if (szSearch2
!= NULL
&& _tcslen(szSearch1
) > _tcslen(szSearch2
))
399 szSearch
= szSearch2
;
400 else if (szSearch3
!= NULL
&& _tcslen(szSearch1
) > _tcslen(szSearch3
))
401 szSearch
= szSearch3
;
403 szSearch
= szSearch1
;
404 /* Move one char past */
406 szSearch
[0] = _T('\0');
407 _tcscpy(szPrefix
,str
);
412 if (!_tcschr(str
, _T(' ')))
414 /* No spaces, everything goes to Suffix */
415 _tcscpy(szSuffix
,str
);
416 /* look for a slash just in case */
417 szSearch
= _tcsrchr(str
, _T('\\'));
421 szSearch
[0] = _T('\0');
422 _tcscpy(szPrefix
,str
);
426 szPrefix
[0] = _T('\0');
433 /* No quotes, and there is a space*/
434 /* Take it after the last space */
435 szSearch
= _tcsrchr(str
, _T(' '));
437 _tcscpy(szSuffix
,szSearch
);
438 /* Find the closest to the end space or \ */
440 szSearch1
= _tcsrchr(str
, _T(' '));
441 szSearch2
= _tcsrchr(str
, _T('\\'));
442 szSearch3
= _tcsrchr(str
, _T('/'));
443 if (szSearch2
!= NULL
&& _tcslen(szSearch1
) > _tcslen(szSearch2
))
444 szSearch
= szSearch2
;
445 else if (szSearch3
!= NULL
&& _tcslen(szSearch1
) > _tcslen(szSearch3
))
446 szSearch
= szSearch3
;
448 szSearch
= szSearch1
;
450 szSearch
[0] = _T('\0');
451 _tcscpy(szPrefix
,str
);
455 /* All else fails and there is a lot of quotes, spaces and |
456 Then we search through and find the last space or \ that is
457 not inside a quotes */
458 for(i
= 0; i
< _tcslen(str
); i
++)
460 if (str
[i
] == _T('\"'))
462 if (str
[i
] == _T(' ') && !bInside
)
464 if ((str
[i
] == _T(' ') || str
[i
] == _T('\\')) && !bInside
)
469 _tcscpy(szSuffix
,&strIN
[SBreak
]);
470 strIN
[PBreak
] = _T('\0');
471 _tcscpy(szPrefix
,strIN
);
472 if (szPrefix
[_tcslen(szPrefix
) - 2] == _T('\"') &&
473 szPrefix
[_tcslen(szPrefix
) - 1] != _T(' '))
475 /* need to remove the " right before a \ at the end to
476 allow the next stuff to stay inside one set of quotes
477 otherwise you would have multiple sets of quotes*/
478 _tcscpy(&szPrefix
[_tcslen(szPrefix
) - 2],_T("\\"));
482 int __cdecl
compare(const void *arg1
,const void *arg2
)
488 File1
= cmd_alloc(sizeof(FileName
));
492 File2
= cmd_alloc(sizeof(FileName
));
499 memcpy(File1
,arg1
,sizeof(FileName
));
500 memcpy(File2
,arg2
,sizeof(FileName
));
502 /* ret = _tcsicmp(File1->Name, File2->Name); */
503 ret
= lstrcmpi(File1
->Name
, File2
->Name
);
511 FileNameContainsSpecialCharacters(LPTSTR pszFileName
)
515 while ((chr
= *pszFileName
++) != _T('\0'))
517 if ((chr
== _T(' ')) ||
535 (chr
== 0xB4)) // '´'
545 VOID
CompleteFilename (LPTSTR strIN
, BOOL bNext
, LPTSTR strOut
, UINT cusor
)
547 /* Length of string before we complete it */
549 /* Length of string after completed */
551 /* The number of chars added too it */
552 //static INT DiffLength = 0;
553 /* Used to find and assemble the string that is returned */
554 TCHAR szBaseWord
[MAX_PATH
];
555 TCHAR szPrefix
[MAX_PATH
];
556 TCHAR szOriginal
[MAX_PATH
];
557 TCHAR szSearchPath
[MAX_PATH
];
558 /* Save the strings used last time, so if they hit tab again */
559 static TCHAR LastReturned
[MAX_PATH
];
560 static TCHAR LastSearch
[MAX_PATH
];
561 static TCHAR LastPrefix
[MAX_PATH
];
562 /* Used to search for files */
564 WIN32_FIND_DATA file
;
565 /* List of all the files */
566 FileName
* FileList
= NULL
;
567 /* Number of files */
568 INT FileListSize
= 0;
571 /* Editable string of what was passed in */
573 /* Keeps track of what element was last selected */
575 BOOL NeededQuote
= FALSE
;
577 TCHAR
* line
= strIN
;
579 strOut
[0] = _T('\0');
581 while (_istspace (*line
))
583 if (!_tcsnicmp (line
, _T("rd "), 3) || !_tcsnicmp (line
, _T("cd "), 3))
586 /* Copy the string, str can be edited and original should not be */
588 _tcscpy(szOriginal
,strIN
);
590 /* Look to see if the cusor is not at the end of the string */
591 if ((cusor
+ 1) < _tcslen(str
))
592 str
[cusor
] = _T('\0');
594 /* Look to see if they hit tab again, if so cut off the diff length */
595 if (_tcscmp(str
,LastReturned
) || !_tcslen(str
))
597 /* We need to know how many chars we added from the start */
598 StartLength
= _tcslen(str
);
600 /* no string, we need all files in that directory */
603 _tcscat(str
,_T("*"));
606 /* Zero it out first */
607 szBaseWord
[0] = _T('\0');
608 szPrefix
[0] = _T('\0');
610 /*What comes out of this needs to be:
611 szBaseWord = path no quotes to the object
612 szPrefix = what leads up to the filename
613 no quote at the END of the full name */
614 FindPrefixAndSuffix(str
,szPrefix
,szBaseWord
);
616 for(i
= 0; i
< _tcslen(szBaseWord
); )
618 if (szBaseWord
[i
] == _T('\"'))
619 memmove(&szBaseWord
[i
],&szBaseWord
[i
+ 1], _tcslen(&szBaseWord
[i
]) * sizeof(TCHAR
));
625 memset(szSearchPath
, 0, sizeof(szSearchPath
));
627 /* Start the search for all the files */
628 GetFullPathName(szBaseWord
, MAX_PATH
, szSearchPath
, NULL
);
630 /* Got a device path? Fallback to the the current dir plus the short path */
631 if (szSearchPath
[0] == _T('\\') && szSearchPath
[1] == _T('\\') &&
632 szSearchPath
[2] == _T('.') && szSearchPath
[3] == _T('\\'))
634 GetCurrentDirectory(MAX_PATH
, szSearchPath
);
635 _tcscat(szSearchPath
, _T("\\"));
636 _tcscat(szSearchPath
, szBaseWord
);
641 _tcscat(szSearchPath
,_T("*"));
643 _tcscpy(LastSearch
,szSearchPath
);
644 _tcscpy(LastPrefix
,szPrefix
);
648 _tcscpy(szSearchPath
, LastSearch
);
649 _tcscpy(szPrefix
, LastPrefix
);
652 /* search for the files it might be */
653 hFile
= FindFirstFile (szSearchPath
, &file
);
654 if (hFile
== INVALID_HANDLE_VALUE
)
656 /* Assemble the original string and return */
657 _tcscpy(strOut
,szOriginal
);
661 /* assemble a list of all files names */
664 FileName
* oldFileList
= FileList
;
666 if (!_tcscmp (file
.cFileName
, _T(".")) ||
667 !_tcscmp (file
.cFileName
, _T("..")))
670 /* Don't show files when they are doing 'cd' or 'rd' */
672 file
.dwFileAttributes
!= 0xFFFFFFFF &&
673 !(file
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
678 /* Add the file to the list of files */
679 FileList
= cmd_realloc(FileList
, ++FileListSize
* sizeof(FileName
));
681 if (FileList
== NULL
)
683 /* Don't leak old buffer */
684 cmd_free(oldFileList
);
685 /* Assemble the original string and return */
686 _tcscpy(strOut
,szOriginal
);
688 ConOutFormatMessage (GetLastError());
691 /* Copies the file name into the struct */
692 _tcscpy(FileList
[FileListSize
-1].Name
,file
.cFileName
);
694 } while(FindNextFile(hFile
,&file
));
698 /* Check the size of the list to see if we found any matches */
699 if (FileListSize
== 0)
701 _tcscpy(strOut
,szOriginal
);
702 if (FileList
!= NULL
)
708 qsort(FileList
,FileListSize
,sizeof(FileName
), compare
);
710 /* Find the next/previous */
711 if (_tcslen(szOriginal
) && !_tcscmp(szOriginal
,LastReturned
))
715 if (FileListSize
- 1 == Sel
)
723 Sel
= FileListSize
- 1;
733 /* nothing found that matched last time so return the first thing in the list */
734 strOut
[0] = _T('\0');
736 /* Special character in the name */
737 if (FileNameContainsSpecialCharacters(FileList
[Sel
].Name
))
741 /* It needs a " at the end */
745 /* Find the place to put the " at the start */
746 for(i
= 0; i
< _tcslen(szPrefix
); i
++)
748 if (szPrefix
[i
] == _T('\"'))
750 if (szPrefix
[i
] == _T(' ') && !bInside
)
754 /* insert the quotation and move things around */
755 if (szPrefix
[LastSpace
+ 1] != _T('\"') && LastSpace
!= -1)
757 memmove ( &szPrefix
[LastSpace
+1], &szPrefix
[LastSpace
], (_tcslen(szPrefix
)-LastSpace
+1) * sizeof(TCHAR
) );
759 if ((UINT
)(LastSpace
+ 1) == _tcslen(szPrefix
))
761 _tcscat(szPrefix
,_T("\""));
763 szPrefix
[LastSpace
+ 1] = _T('\"');
765 else if (LastSpace
== -1)
767 /* Add quotation only if none exists already */
768 if (szPrefix
[0] != _T('\"'))
770 _tcscpy(szBaseWord
,_T("\""));
771 _tcscat(szBaseWord
,szPrefix
);
772 _tcscpy(szPrefix
,szBaseWord
);
777 _tcscpy(strOut
,szPrefix
);
778 _tcscat(strOut
,FileList
[Sel
].Name
);
780 /* check for odd number of quotes means we need to close them */
783 for(i
= 0; i
< _tcslen(strOut
); i
++)
785 if (strOut
[i
] == _T('\"'))
786 NeededQuote
= !NeededQuote
;
790 if (NeededQuote
|| (_tcslen(szPrefix
) && szPrefix
[_tcslen(szPrefix
) - 1] == _T('\"')))
791 _tcscat(strOut
,_T("\""));
793 _tcscpy(LastReturned
,strOut
);
794 //EndLength = _tcslen(strOut);
795 //DiffLength = EndLength - StartLength;
796 if (FileList
!= NULL
)