Fix tab complete bugs, including 1238.
[reactos.git] / reactos / subsys / system / cmd / filecomp.c
1 /*
2 * FILECOMP.C - handles filename completion.
3 *
4 *
5 * Comments:
6 *
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
11 *
12 * 25-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
13 * Cleanup. Unicode safe!
14 *
15 * 30-Apr-2004 (Filip Navara <xnavara@volny.cz>)
16 * Make the file listing readable when there is a lot of long names.
17 *
18
19 * 05-Jul-2004 (Jens Collin <jens.collin@lakhei.com>)
20 * Now expands lfn even when trailing " is omitted.
21 */
22
23 #include <precomp.h>
24 #include "cmd.h"
25
26 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
27
28 VOID CompleteFilename (LPTSTR str, UINT charcount)
29 {
30 WIN32_FIND_DATA file;
31 HANDLE hFile;
32 INT curplace = 0;
33 INT start;
34 INT count;
35 INT step;
36 INT c = 0;
37 BOOL found_dot = FALSE;
38 BOOL perfectmatch = TRUE;
39 TCHAR path[MAX_PATH];
40 TCHAR fname[MAX_PATH];
41 TCHAR maxmatch[MAX_PATH] = _T("");
42 TCHAR directory[MAX_PATH];
43 LPCOMMAND cmds_ptr;
44
45 /* expand current file name */
46 count = charcount - 1;
47 if (count < 0)
48 count = 0;
49
50 /* find how many '"'s there is typed already.*/
51 step = count;
52 while (step > 0)
53 {
54 if (str[step] == _T('"'))
55 c++;
56 step--;
57 }
58 /* if c is odd, then user typed " before name, else not.*/
59
60 /* find front of word */
61 if (str[count] == _T('"') || (c % 2))
62 {
63 count--;
64 while (count > 0 && str[count] != _T('"'))
65 count--;
66 }
67 else
68 {
69 while (count > 0 && str[count] != _T(' '))
70 count--;
71 }
72
73 /* if not at beginning, go forward 1 */
74 if (str[count] == _T(' '))
75 count++;
76
77 start = count;
78
79 if (str[count] == _T('"'))
80 count++; /* don't increment start */
81
82 /* extract directory from word */
83 _tcscpy (directory, &str[count]);
84 curplace = _tcslen (directory) - 1;
85
86 if (curplace >= 0 && directory[curplace] == _T('"'))
87 directory[curplace--] = _T('\0');
88
89 _tcscpy (path, directory);
90
91 while (curplace >= 0 && directory[curplace] != _T('\\') &&
92 directory[curplace] != _T(':'))
93 {
94 directory[curplace] = 0;
95 curplace--;
96 }
97
98 /* look for a '.' in the filename */
99 for (count = _tcslen (directory); path[count] != _T('\0'); count++)
100 {
101 if (path[count] == _T('.'))
102 {
103 found_dot = TRUE;
104 break;
105 }
106 }
107
108 if (found_dot)
109 _tcscat (path, _T("*"));
110 else
111 _tcscat (path, _T("*.*"));
112
113 /* current fname */
114 curplace = 0;
115
116 hFile = FindFirstFile (path, &file);
117 if (hFile != INVALID_HANDLE_VALUE)
118 {
119 /* find anything */
120 do
121 {
122 /* ignore "." and ".." */
123 if (!_tcscmp (file.cFileName, _T(".")) ||
124 !_tcscmp (file.cFileName, _T("..")))
125 continue;
126
127 _tcscpy (fname, file.cFileName);
128
129 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
130 _tcscat (fname, _T("\\"));
131
132 if (!maxmatch[0] && perfectmatch)
133 {
134 _tcscpy(maxmatch, fname);
135 }
136 else
137 {
138 for (count = 0; maxmatch[count] && fname[count]; count++)
139 {
140 if (tolower(maxmatch[count]) != tolower(fname[count]))
141 {
142 perfectmatch = FALSE;
143 maxmatch[count] = 0;
144 break;
145 }
146 }
147
148 if (maxmatch[count] == _T('\0') &&
149 fname[count] != _T('\0'))
150 perfectmatch = FALSE;
151 }
152 }
153 while (FindNextFile (hFile, &file));
154
155 FindClose (hFile);
156
157 /* only quote if the filename contains spaces */
158 if (_tcschr(directory, _T(' ')) ||
159 _tcschr(maxmatch, _T(' ')))
160 {
161 str[start] = _T('\"');
162 _tcscpy (&str[start+1], directory);
163 _tcscat (&str[start], maxmatch);
164 _tcscat (&str[start], _T("\"") );
165 }
166 else
167 {
168 _tcscpy (&str[start], directory);
169 _tcscat (&str[start], maxmatch);
170 }
171
172 if(!perfectmatch)
173 {
174 MessageBeep (-1);
175 }
176 }
177 else
178 {
179 /* no match found - search for internal command */
180 for (cmds_ptr = cmds; cmds_ptr->name; cmds_ptr++)
181 {
182 if (!_tcsnicmp (&str[start], cmds_ptr->name,
183 _tcslen (&str[start])))
184 {
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);
188 break;
189 }
190 }
191
192 MessageBeep (-1);
193 }
194 }
195
196
197 /*
198 * returns 1 if at least one match, else returns 0
199 */
200
201 BOOL ShowCompletionMatches (LPTSTR str, INT charcount)
202 {
203 WIN32_FIND_DATA file;
204 HANDLE hFile;
205 BOOL found_dot = FALSE;
206 INT curplace = 0;
207 INT start;
208 UINT count;
209 TCHAR path[MAX_PATH];
210 TCHAR fname[MAX_PATH];
211 TCHAR directory[MAX_PATH];
212 UINT longestfname = 0;
213 SHORT screenwidth;
214
215 /* expand current file name */
216 count = charcount - 1;
217 if (count < 0)
218 count = 0;
219
220 /* find front of word */
221 if (str[count] == _T('"'))
222 {
223 count--;
224 while (count > 0 && str[count] != _T('"'))
225 count--;
226 }
227 else
228 {
229 while (count > 0 && str[count] != _T(' '))
230 count--;
231 }
232
233 /* if not at beginning, go forward 1 */
234 if (str[count] == _T(' '))
235 count++;
236
237 start = count;
238
239 if (str[count] == _T('"'))
240 count++; /* don't increment start */
241
242 /* extract directory from word */
243 _tcscpy (directory, &str[count]);
244 curplace = _tcslen (directory) - 1;
245
246 if (curplace >= 0 && directory[curplace] == _T('"'))
247 directory[curplace--] = _T('\0');
248
249 _tcscpy (path, directory);
250
251 while (curplace >= 0 &&
252 directory[curplace] != _T('\\') &&
253 directory[curplace] != _T(':'))
254 {
255 directory[curplace] = 0;
256 curplace--;
257 }
258
259 /* look for a . in the filename */
260 for (count = _tcslen (directory); path[count] != _T('\0'); count++)
261 {
262 if (path[count] == _T('.'))
263 {
264 found_dot = TRUE;
265 break;
266 }
267 }
268
269 if (found_dot)
270 _tcscat (path, _T("*"));
271 else
272 _tcscat (path, _T("*.*"));
273
274 /* current fname */
275 curplace = 0;
276
277 hFile = FindFirstFile (path, &file);
278 if (hFile != INVALID_HANDLE_VALUE)
279 {
280 /* Get the size of longest filename first. */
281 do
282 {
283 if (_tcslen(file.cFileName) > longestfname)
284 {
285 longestfname = _tcslen(file.cFileName);
286 /* Directories get extra brackets around them. */
287 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
288 longestfname += 2;
289 }
290 }
291 while (FindNextFile (hFile, &file));
292 FindClose (hFile);
293
294 hFile = FindFirstFile (path, &file);
295
296 /* Count the highest number of columns */
297 GetScreenSize(&screenwidth, 0);
298
299 /* For counting columns of output */
300 count = 0;
301
302 /* Increase by the number of spaces behind file name */
303 longestfname += 3;
304
305 /* find anything */
306 ConOutChar (_T('\n'));
307 do
308 {
309 /* ignore . and .. */
310 if (!_tcscmp (file.cFileName, _T(".")) ||
311 !_tcscmp (file.cFileName, _T("..")))
312 continue;
313
314 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
315 _stprintf (fname, _T("[%s]"), file.cFileName);
316 else
317 _tcscpy (fname, file.cFileName);
318
319 ConOutPrintf (_T("%*s"), - longestfname, fname);
320 count++;
321 /* output as much columns as fits on the screen */
322 if (count >= (screenwidth / longestfname))
323 {
324 /* print the new line only if we aren't on the
325 * last column, in this case it wraps anyway */
326 if (count * longestfname != (UINT)screenwidth)
327 ConOutPrintf (_T("\n"));
328 count = 0;
329 }
330 }
331 while (FindNextFile (hFile, &file));
332
333 FindClose (hFile);
334
335 if (count)
336 ConOutChar (_T('\n'));
337 }
338 else
339 {
340 /* no match found */
341 MessageBeep (-1);
342 return FALSE;
343 }
344
345 return TRUE;
346 }
347 #endif
348
349 #ifdef FEATURE_4NT_FILENAME_COMPLETION
350
351 typedef struct _FileName
352 {
353 TCHAR Name[MAX_PATH];
354 } FileName;
355
356 VOID FindPrefixAndSuffix(LPTSTR strIN, LPTSTR szPrefix, LPTSTR szSuffix)
357 {
358 /* String that is to be examined */
359 TCHAR str[MAX_PATH];
360 /* temp pointers to used to find needed parts */
361 TCHAR * szSearch;
362 TCHAR * szSearch1;
363 TCHAR * szSearch2;
364 /* number of quotes in the string */
365 INT nQuotes = 0;
366 /* used in for loops */
367 UINT i;
368 /* Char number to break the string at */
369 INT PBreak = 0;
370 INT SBreak = 0;
371 /* when phrasing a string, this tells weather
372 you are inside quotes ot not. */
373 BOOL bInside = FALSE;
374
375 szPrefix[0] = _T('\0');
376 szSuffix[0] = _T('\0');
377
378 /* Copy over the string to later be edited */
379 _tcscpy(str,strIN);
380
381 /* Count number of " */
382 for(i = 0; i < _tcslen(str); i++)
383 if(str[i] == _T('\"'))
384 nQuotes++;
385
386 /* Find the prefix and suffix */
387 if(nQuotes % 2 && nQuotes >= 1)
388 {
389 /* Odd number of quotes. Just start from the last " */
390 /* THis is the way MS does it, and is an easy way out */
391 szSearch = _tcsrchr(str, _T('\"'));
392 /* Move to the next char past the " */
393 szSearch++;
394 _tcscpy(szSuffix,szSearch);
395 /* Find the one closest to end */
396 szSearch1 = _tcsrchr(str, _T('\"'));
397 szSearch2 = _tcsrchr(str, _T('\\'));
398 if(szSearch2 != NULL && _tcslen(szSearch1) > _tcslen(szSearch2))
399 szSearch = szSearch2;
400 else
401 szSearch = szSearch1;
402 /* Move one char past */
403 szSearch++;
404 szSearch[0] = _T('\0');
405 _tcscpy(szPrefix,str);
406 return;
407
408 }
409
410 if(!_tcschr(str, _T(' ')))
411 {
412 /* No spaces, everything goes to Suffix */
413 _tcscpy(szSuffix,str);
414 /* look for a slash just in case */
415 szSearch = _tcsrchr(str, _T('\\'));
416 if(szSearch)
417 {
418 szSearch++;
419 szSearch[0] = _T('\0');
420 _tcscpy(szPrefix,str);
421 }
422 else
423 {
424 szPrefix[0] = _T('\0');
425 }
426 return;
427 }
428
429 if(!nQuotes)
430 {
431 /* No quotes, and there is a space*/
432 /* Take it after the last space */
433 szSearch = _tcsrchr(str, _T(' '));
434 szSearch++;
435 _tcscpy(szSuffix,szSearch);
436 /* Find the closest to the end space or \ */
437 _tcscpy(str,strIN);
438 szSearch1 = _tcsrchr(str, _T(' '));
439 szSearch2 = _tcsrchr(str, _T('\\'));
440 if(szSearch2 != NULL && _tcslen(szSearch1) > _tcslen(szSearch2))
441 szSearch = szSearch2;
442 else
443 szSearch = szSearch1;
444 szSearch++;
445 szSearch[0] = _T('\0');
446 _tcscpy(szPrefix,str);
447 return;
448 }
449
450 /* All else fails and there is a lot of quotes, spaces and |
451 Then we search through and find the last space or \ that is
452 not inside a quotes */
453 for(i = 0; i < _tcslen(str); i++)
454 {
455 if(str[i] == _T('\"'))
456 bInside = !bInside;
457 if(str[i] == _T(' ') && !bInside)
458 SBreak = i;
459 if((str[i] == _T(' ') || str[i] == _T('\\')) && !bInside)
460 PBreak = i;
461
462 }
463 SBreak++;
464 PBreak++;
465 _tcscpy(szSuffix,&strIN[SBreak]);
466 strIN[PBreak] = _T('\0');
467 _tcscpy(szPrefix,strIN);
468 if(szPrefix[_tcslen(szPrefix) - 2] == _T('\"'))
469 {
470 /* need to remove the " right before a \ at the end to
471 allow the next stuff to stay inside one set of quotes
472 otherwise you would have multiple sets of quotes*/
473 _tcscpy(&szPrefix[_tcslen(szPrefix) - 2],_T("\\"));
474
475 }
476
477 }
478 int __cdecl compare(const void *arg1,const void *arg2)
479 {
480 FileName * File1;
481 FileName * File2;
482 INT ret;
483
484 File1 = malloc(sizeof(FileName));
485 File2 = malloc(sizeof(FileName));
486 if(!File1 || !File2)
487 return 0;
488
489 memcpy(File1,arg1,sizeof(FileName));
490 memcpy(File2,arg2,sizeof(FileName));
491
492 /* ret = _tcsicmp(File1->Name, File2->Name); */
493 ret = lstrcmpi(File1->Name, File2->Name);
494
495 free(File1);
496 free(File2);
497 return ret;
498 }
499
500 VOID CompleteFilename (LPTSTR strIN, BOOL bNext, LPTSTR strOut, UINT cusor)
501 {
502 /* Length of string before we complete it */
503 INT StartLength;
504 /* Length of string after completed */
505 INT EndLength;
506 /* The number of chars added too it */
507 static INT DiffLength = 0;
508 /* Used to find and assemble the string that is returned */
509 TCHAR szBaseWord[MAX_PATH];
510 TCHAR szPrefix[MAX_PATH];
511 TCHAR szOrginal[MAX_PATH];
512 TCHAR szSearchPath[MAX_PATH];
513 /* Save the strings used last time, so if they hit tab again */
514 static TCHAR LastReturned[MAX_PATH];
515 static TCHAR LastSearch[MAX_PATH];
516 static TCHAR LastPrefix[MAX_PATH];
517 /* Used to search for files */
518 HANDLE hFile;
519 WIN32_FIND_DATA file;
520 /* List of all the files */
521 FileName * FileList = NULL;
522 /* Number of files */
523 INT FileListSize = 0;
524 /* Used for loops */
525 UINT i;
526 /* Editable string of what was passed in */
527 TCHAR str[MAX_PATH];
528 /* Keeps track of what element was last selected */
529 static INT Sel;
530 BOOL NeededQuote = FALSE;
531 BOOL ShowAll = TRUE;
532 TCHAR * line = strIN;
533
534 strOut[0] = _T('\0');
535
536 while (_istspace (*line))
537 line++;
538 if(!_tcsnicmp (line, _T("rd "), 3) || !_tcsnicmp (line, _T("cd "), 3))
539 ShowAll = FALSE;
540
541 /* Copy the string, str can be edited and orginal should not be */
542 _tcscpy(str,strIN);
543 _tcscpy(szOrginal,strIN);
544
545 /* Look to see if the cusor is not at the end of the string */
546 if((cusor + 1) < _tcslen(str))
547 str[cusor] = _T('\0');
548
549 /* Look to see if they hit tab again, if so cut off the diff length */
550 if(_tcscmp(str,LastReturned) || !_tcslen(str))
551 {
552 /* We need to know how many chars we added from the start */
553 StartLength = _tcslen(str);
554
555 /* no string, we need all files in that directory */
556 if(!StartLength)
557 {
558 _tcscat(str,_T("*"));
559 }
560
561 /* Zero it out first */
562 szBaseWord[0] = _T('\0');
563 szPrefix[0] = _T('\0');
564
565 /*What comes out of this needs to be:
566 szBaseWord = path no quotes to the object
567 szPrefix = what leads up to the filename
568 no quote at the END of the full name */
569 FindPrefixAndSuffix(str,szPrefix,szBaseWord);
570 /* Strip quotes */
571 for(i = 0; i < _tcslen(szBaseWord); )
572 {
573 if(szBaseWord[i] == _T('\"'))
574 memmove(&szBaseWord[i],&szBaseWord[i + 1], _tcslen(&szBaseWord[i]) * sizeof(TCHAR));
575 else
576 i++;
577 }
578
579 /* clear it out */
580 memset(szSearchPath, 0, sizeof(szSearchPath));
581
582 /* Start the search for all the files */
583 GetFullPathName(szBaseWord, MAX_PATH, szSearchPath, NULL);
584 if(StartLength > 0)
585 {
586 _tcscat(szSearchPath,_T("*"));
587 }
588 _tcscpy(LastSearch,szSearchPath);
589 _tcscpy(LastPrefix,szPrefix);
590 }
591 else
592 {
593 _tcscpy(szSearchPath, LastSearch);
594 _tcscpy(szPrefix, LastPrefix);
595 StartLength = 0;
596 }
597 /* search for the files it might be */
598 hFile = FindFirstFile (szSearchPath, &file);
599
600 /* aseemble a list of all files names */
601 do
602 {
603 if(hFile == INVALID_HANDLE_VALUE)
604 {
605 /* Assemble the orginal string and return */
606 _tcscpy(strOut,szOrginal);
607 CloseHandle(hFile);
608 if(FileList != NULL)
609 free(FileList);
610 return;
611 }
612
613 if(!_tcscmp (file.cFileName, _T(".")) ||
614 !_tcscmp (file.cFileName, _T("..")))
615 continue;
616
617 /* Don't show files when they are doing 'cd' or 'rd' */
618 if(!ShowAll &&
619 file.dwFileAttributes != 0xFFFFFFFF &&
620 !(file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
621 {
622 continue;
623 }
624
625 /* Add the file to the list of files */
626 if(FileList == NULL)
627 {
628 FileListSize = 1;
629 FileList = malloc(FileListSize * sizeof(FileName));
630 }
631 else
632 {
633 FileListSize++;
634 FileList = realloc(FileList, FileListSize * sizeof(FileName));
635 }
636
637 if(FileList == NULL)
638 {
639 /* Assemble the orginal string and return */
640 _tcscpy(strOut,szOrginal);
641 CloseHandle(hFile);
642 ConOutFormatMessage (GetLastError());
643 return;
644 }
645 /* Copies the file name into the struct */
646 _tcscpy(FileList[FileListSize-1].Name,file.cFileName);
647
648 }while(FindNextFile(hFile,&file));
649
650 /* Check the size of the list to see if we
651 found any matches */
652 if(FileListSize == 0)
653 {
654 _tcscpy(strOut,szOrginal);
655 CloseHandle(hFile);
656 if(FileList != NULL)
657 free(FileList);
658 return;
659
660 }
661 /* Sort the files */
662 qsort(FileList,FileListSize,sizeof(FileName), compare);
663
664 /* Find the next/previous */
665 if(!_tcscmp(szOrginal,LastReturned))
666 {
667 if(bNext)
668 {
669 if(FileListSize - 1 == Sel)
670 Sel = 0;
671 else
672 Sel++;
673 }
674 else
675 {
676 if(!Sel)
677 Sel = FileListSize - 1;
678 else
679 Sel--;
680 }
681 }
682 else
683 {
684 Sel = 0;
685 }
686
687 /* nothing found that matched last time
688 so return the first thing in the list */
689 strOut[0] = _T('\0');
690
691 /* space in the name */
692 if(_tcschr(FileList[Sel].Name, _T(' ')))
693 {
694 INT LastSpace;
695 BOOL bInside;
696 /* It needs a " at the end */
697 NeededQuote = TRUE;
698 LastSpace = -1;
699 bInside = FALSE;
700 /* Find the place to put the " at the start */
701 for(i = 0; i < _tcslen(szPrefix); i++)
702 {
703 if(szPrefix[i] == _T('\"'))
704 bInside = !bInside;
705 if(szPrefix[i] == _T(' ') && !bInside)
706 LastSpace = i;
707
708 }
709 /* insert the quoation and move things around */
710 if(szPrefix[LastSpace + 1] != _T('\"') && LastSpace != -1)
711 {
712 memmove ( &szPrefix[LastSpace+1], &szPrefix[LastSpace], (_tcslen(szPrefix)-LastSpace+1) * sizeof(TCHAR) );
713
714 if((UINT)(LastSpace + 1) == _tcslen(szPrefix))
715 {
716 _tcscat(szPrefix,_T("\""));
717 }
718 szPrefix[LastSpace + 1] = _T('\"');
719 }
720 else if(LastSpace == -1)
721 {
722 _tcscpy(szBaseWord,_T("\""));
723 _tcscat(szBaseWord,szPrefix);
724 _tcscpy(szPrefix,szBaseWord);
725
726 }
727 }
728
729 _tcscpy(strOut,szPrefix);
730 _tcscat(strOut,FileList[Sel].Name);
731
732 /* check for odd number of quotes means we need to close them */
733 if(!NeededQuote)
734 {
735 for(i = 0; i < _tcslen(strOut); i++)
736 if(strOut[i] == _T('\"'))
737 NeededQuote = !NeededQuote;
738 }
739
740 if(szPrefix[_tcslen(szPrefix) - 1] == _T('\"') || NeededQuote)
741 _tcscat(strOut,_T("\""));
742
743 _tcscpy(LastReturned,strOut);
744 EndLength = _tcslen(strOut);
745 DiffLength = EndLength - StartLength;
746 CloseHandle(hFile);
747 if(FileList != NULL)
748 free(FileList);
749
750 }
751 #endif