[DDK]: Merge 46183 from header-branch.
[reactos.git] / reactos / base / shell / 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)
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
25 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
26
27 VOID CompleteFilename (LPTSTR str, UINT charcount)
28 {
29 WIN32_FIND_DATA file;
30 HANDLE hFile;
31 INT curplace = 0;
32 INT start;
33 INT count;
34 INT step;
35 INT c = 0;
36 BOOL found_dot = FALSE;
37 BOOL perfectmatch = TRUE;
38 TCHAR path[MAX_PATH];
39 TCHAR fname[MAX_PATH];
40 TCHAR maxmatch[MAX_PATH] = _T("");
41 TCHAR directory[MAX_PATH];
42 LPCOMMAND cmds_ptr;
43
44 /* expand current file name */
45 count = charcount - 1;
46 if (count < 0)
47 count = 0;
48
49 /* find how many '"'s there is typed already.*/
50 step = count;
51 while (step > 0)
52 {
53 if (str[step] == _T('"'))
54 c++;
55 step--;
56 }
57 /* if c is odd, then user typed " before name, else not.*/
58
59 /* find front of word */
60 if (str[count] == _T('"') || (c % 2))
61 {
62 count--;
63 while (count > 0 && str[count] != _T('"'))
64 count--;
65 }
66 else
67 {
68 while (count > 0 && str[count] != _T(' '))
69 count--;
70 }
71
72 /* if not at beginning, go forward 1 */
73 if (str[count] == _T(' '))
74 count++;
75
76 start = count;
77
78 if (str[count] == _T('"'))
79 count++; /* don't increment start */
80
81 /* extract directory from word */
82 _tcscpy (directory, &str[count]);
83 curplace = _tcslen (directory) - 1;
84
85 if (curplace >= 0 && directory[curplace] == _T('"'))
86 directory[curplace--] = _T('\0');
87
88 _tcscpy (path, directory);
89
90 while (curplace >= 0 && directory[curplace] != _T('\\') &&
91 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 INT 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 TCHAR * szSearch3;
365 /* number of quotes in the string */
366 INT nQuotes = 0;
367 /* used in for loops */
368 UINT i;
369 /* Char number to break the string at */
370 INT PBreak = 0;
371 INT SBreak = 0;
372 /* when phrasing a string, this tells weather
373 you are inside quotes ot not. */
374 BOOL bInside = FALSE;
375
376 szPrefix[0] = _T('\0');
377 szSuffix[0] = _T('\0');
378
379 /* Copy over the string to later be edited */
380 _tcscpy(str,strIN);
381
382 /* Count number of " */
383 for(i = 0; i < _tcslen(str); i++)
384 if(str[i] == _T('\"'))
385 nQuotes++;
386
387 /* Find the prefix and suffix */
388 if(nQuotes % 2 && nQuotes >= 1)
389 {
390 /* Odd number of quotes. Just start from the last " */
391 /* THis is the way MS does it, and is an easy way out */
392 szSearch = _tcsrchr(str, _T('\"'));
393 /* Move to the next char past the " */
394 szSearch++;
395 _tcscpy(szSuffix,szSearch);
396 /* Find the one closest to end */
397 szSearch1 = _tcsrchr(str, _T('\"'));
398 szSearch2 = _tcsrchr(str, _T('\\'));
399 szSearch3 = _tcsrchr(str, _T('.'));
400 if(szSearch2 != NULL && _tcslen(szSearch1) > _tcslen(szSearch2))
401 szSearch = szSearch2;
402 else if(szSearch3 != NULL && _tcslen(szSearch1) > _tcslen(szSearch3))
403 szSearch = szSearch3;
404 else
405 szSearch = szSearch1;
406 /* Move one char past */
407 szSearch++;
408 szSearch[0] = _T('\0');
409 _tcscpy(szPrefix,str);
410 return;
411
412 }
413
414 if(!_tcschr(str, _T(' ')))
415 {
416 /* No spaces, everything goes to Suffix */
417 _tcscpy(szSuffix,str);
418 /* look for a slash just in case */
419 szSearch = _tcsrchr(str, _T('\\'));
420 if(szSearch)
421 {
422 szSearch++;
423 szSearch[0] = _T('\0');
424 _tcscpy(szPrefix,str);
425 }
426 else
427 {
428 szPrefix[0] = _T('\0');
429 }
430 return;
431 }
432
433 if(!nQuotes)
434 {
435 /* No quotes, and there is a space*/
436 /* Take it after the last space */
437 szSearch = _tcsrchr(str, _T(' '));
438 szSearch++;
439 _tcscpy(szSuffix,szSearch);
440 /* Find the closest to the end space or \ */
441 _tcscpy(str,strIN);
442 szSearch1 = _tcsrchr(str, _T(' '));
443 szSearch2 = _tcsrchr(str, _T('\\'));
444 szSearch3 = _tcsrchr(str, _T('/'));
445 if(szSearch2 != NULL && _tcslen(szSearch1) > _tcslen(szSearch2))
446 szSearch = szSearch2;
447 else if(szSearch3 != NULL && _tcslen(szSearch1) > _tcslen(szSearch3))
448 szSearch = szSearch3;
449 else
450 szSearch = szSearch1;
451 szSearch++;
452 szSearch[0] = _T('\0');
453 _tcscpy(szPrefix,str);
454 return;
455 }
456
457 /* All else fails and there is a lot of quotes, spaces and |
458 Then we search through and find the last space or \ that is
459 not inside a quotes */
460 for(i = 0; i < _tcslen(str); i++)
461 {
462 if(str[i] == _T('\"'))
463 bInside = !bInside;
464 if(str[i] == _T(' ') && !bInside)
465 SBreak = i;
466 if((str[i] == _T(' ') || str[i] == _T('\\')) && !bInside)
467 PBreak = i;
468
469 }
470 SBreak++;
471 PBreak++;
472 _tcscpy(szSuffix,&strIN[SBreak]);
473 strIN[PBreak] = _T('\0');
474 _tcscpy(szPrefix,strIN);
475 if (szPrefix[_tcslen(szPrefix) - 2] == _T('\"') &&
476 szPrefix[_tcslen(szPrefix) - 1] != _T(' '))
477 {
478 /* need to remove the " right before a \ at the end to
479 allow the next stuff to stay inside one set of quotes
480 otherwise you would have multiple sets of quotes*/
481 _tcscpy(&szPrefix[_tcslen(szPrefix) - 2],_T("\\"));
482
483 }
484
485 }
486
487 int __cdecl compare(const void *arg1,const void *arg2)
488 {
489 FileName * File1;
490 FileName * File2;
491 INT ret;
492
493 File1 = cmd_alloc(sizeof(FileName));
494 File2 = cmd_alloc(sizeof(FileName));
495 if(!File1 || !File2)
496 return 0;
497
498 memcpy(File1,arg1,sizeof(FileName));
499 memcpy(File2,arg2,sizeof(FileName));
500
501 /* ret = _tcsicmp(File1->Name, File2->Name); */
502 ret = lstrcmpi(File1->Name, File2->Name);
503
504 cmd_free(File1);
505 cmd_free(File2);
506 return ret;
507 }
508
509 VOID CompleteFilename (LPTSTR strIN, BOOL bNext, LPTSTR strOut, UINT cusor)
510 {
511 /* Length of string before we complete it */
512 INT StartLength;
513 /* Length of string after completed */
514 INT EndLength;
515 /* The number of chars added too it */
516 static INT DiffLength = 0;
517 /* Used to find and assemble the string that is returned */
518 TCHAR szBaseWord[MAX_PATH];
519 TCHAR szPrefix[MAX_PATH];
520 TCHAR szOrginal[MAX_PATH];
521 TCHAR szSearchPath[MAX_PATH];
522 /* Save the strings used last time, so if they hit tab again */
523 static TCHAR LastReturned[MAX_PATH];
524 static TCHAR LastSearch[MAX_PATH];
525 static TCHAR LastPrefix[MAX_PATH];
526 /* Used to search for files */
527 HANDLE hFile;
528 WIN32_FIND_DATA file;
529 /* List of all the files */
530 FileName * FileList = NULL;
531 /* Number of files */
532 INT FileListSize = 0;
533 /* Used for loops */
534 UINT i;
535 /* Editable string of what was passed in */
536 TCHAR str[MAX_PATH];
537 /* Keeps track of what element was last selected */
538 static INT Sel;
539 BOOL NeededQuote = FALSE;
540 BOOL ShowAll = TRUE;
541 TCHAR * line = strIN;
542
543 strOut[0] = _T('\0');
544
545 while (_istspace (*line))
546 line++;
547 if(!_tcsnicmp (line, _T("rd "), 3) || !_tcsnicmp (line, _T("cd "), 3))
548 ShowAll = FALSE;
549
550 /* Copy the string, str can be edited and orginal should not be */
551 _tcscpy(str,strIN);
552 _tcscpy(szOrginal,strIN);
553
554 /* Look to see if the cusor is not at the end of the string */
555 if((cusor + 1) < _tcslen(str))
556 str[cusor] = _T('\0');
557
558 /* Look to see if they hit tab again, if so cut off the diff length */
559 if(_tcscmp(str,LastReturned) || !_tcslen(str))
560 {
561 /* We need to know how many chars we added from the start */
562 StartLength = _tcslen(str);
563
564 /* no string, we need all files in that directory */
565 if(!StartLength)
566 {
567 _tcscat(str,_T("*"));
568 }
569
570 /* Zero it out first */
571 szBaseWord[0] = _T('\0');
572 szPrefix[0] = _T('\0');
573
574 /*What comes out of this needs to be:
575 szBaseWord = path no quotes to the object
576 szPrefix = what leads up to the filename
577 no quote at the END of the full name */
578 FindPrefixAndSuffix(str,szPrefix,szBaseWord);
579 /* Strip quotes */
580 for(i = 0; i < _tcslen(szBaseWord); )
581 {
582 if(szBaseWord[i] == _T('\"'))
583 memmove(&szBaseWord[i],&szBaseWord[i + 1], _tcslen(&szBaseWord[i]) * sizeof(TCHAR));
584 else
585 i++;
586 }
587
588 /* clear it out */
589 memset(szSearchPath, 0, sizeof(szSearchPath));
590
591 /* Start the search for all the files */
592 GetFullPathName(szBaseWord, MAX_PATH, szSearchPath, NULL);
593 if(StartLength > 0)
594 {
595 _tcscat(szSearchPath,_T("*"));
596 }
597 _tcscpy(LastSearch,szSearchPath);
598 _tcscpy(LastPrefix,szPrefix);
599 }
600 else
601 {
602 _tcscpy(szSearchPath, LastSearch);
603 _tcscpy(szPrefix, LastPrefix);
604 StartLength = 0;
605 }
606 /* search for the files it might be */
607 hFile = FindFirstFile (szSearchPath, &file);
608 if(hFile == INVALID_HANDLE_VALUE)
609 {
610 /* Assemble the orginal string and return */
611 _tcscpy(strOut,szOrginal);
612 return;
613 }
614
615 /* aseemble a list of all files names */
616 do
617 {
618 if(!_tcscmp (file.cFileName, _T(".")) ||
619 !_tcscmp (file.cFileName, _T("..")))
620 continue;
621
622 /* Don't show files when they are doing 'cd' or 'rd' */
623 if(!ShowAll &&
624 file.dwFileAttributes != 0xFFFFFFFF &&
625 !(file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
626 {
627 continue;
628 }
629
630 /* Add the file to the list of files */
631 FileList = cmd_realloc(FileList, ++FileListSize * sizeof(FileName));
632
633 if(FileList == NULL)
634 {
635 /* Assemble the orginal string and return */
636 _tcscpy(strOut,szOrginal);
637 FindClose(hFile);
638 ConOutFormatMessage (GetLastError());
639 return;
640 }
641 /* Copies the file name into the struct */
642 _tcscpy(FileList[FileListSize-1].Name,file.cFileName);
643
644 } while(FindNextFile(hFile,&file));
645
646 FindClose(hFile);
647
648 /* Check the size of the list to see if we
649 found any matches */
650 if(FileListSize == 0)
651 {
652 _tcscpy(strOut,szOrginal);
653 if(FileList != NULL)
654 cmd_free(FileList);
655 return;
656
657 }
658 /* Sort the files */
659 qsort(FileList,FileListSize,sizeof(FileName), compare);
660
661 /* Find the next/previous */
662 if(!_tcscmp(szOrginal,LastReturned))
663 {
664 if(bNext)
665 {
666 if(FileListSize - 1 == Sel)
667 Sel = 0;
668 else
669 Sel++;
670 }
671 else
672 {
673 if(!Sel)
674 Sel = FileListSize - 1;
675 else
676 Sel--;
677 }
678 }
679 else
680 {
681 Sel = 0;
682 }
683
684 /* nothing found that matched last time
685 so return the first thing in the list */
686 strOut[0] = _T('\0');
687
688 /* space in the name */
689 if(_tcschr(FileList[Sel].Name, _T(' ')))
690 {
691 INT LastSpace;
692 BOOL bInside;
693 /* It needs a " at the end */
694 NeededQuote = TRUE;
695 LastSpace = -1;
696 bInside = FALSE;
697 /* Find the place to put the " at the start */
698 for(i = 0; i < _tcslen(szPrefix); i++)
699 {
700 if(szPrefix[i] == _T('\"'))
701 bInside = !bInside;
702 if(szPrefix[i] == _T(' ') && !bInside)
703 LastSpace = i;
704
705 }
706 /* insert the quoation and move things around */
707 if(szPrefix[LastSpace + 1] != _T('\"') && LastSpace != -1)
708 {
709 memmove ( &szPrefix[LastSpace+1], &szPrefix[LastSpace], (_tcslen(szPrefix)-LastSpace+1) * sizeof(TCHAR) );
710
711 if((UINT)(LastSpace + 1) == _tcslen(szPrefix))
712 {
713 _tcscat(szPrefix,_T("\""));
714 }
715 szPrefix[LastSpace + 1] = _T('\"');
716 }
717 else if(LastSpace == -1)
718 {
719 _tcscpy(szBaseWord,_T("\""));
720 _tcscat(szBaseWord,szPrefix);
721 _tcscpy(szPrefix,szBaseWord);
722
723 }
724 }
725
726 _tcscpy(strOut,szPrefix);
727 _tcscat(strOut,FileList[Sel].Name);
728
729 /* check for odd number of quotes means we need to close them */
730 if(!NeededQuote)
731 {
732 for(i = 0; i < _tcslen(strOut); i++)
733 if(strOut[i] == _T('\"'))
734 NeededQuote = !NeededQuote;
735 }
736
737 if(szPrefix[_tcslen(szPrefix) - 1] == _T('\"') || NeededQuote)
738 _tcscat(strOut,_T("\""));
739
740 _tcscpy(LastReturned,strOut);
741 EndLength = _tcslen(strOut);
742 DiffLength = EndLength - StartLength;
743 if(FileList != NULL)
744 cmd_free(FileList);
745
746 }
747 #endif