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