000418f761054e9ea9c4397b813399af591b19ba
[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, INT 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 #ifdef __REACTOS__
175 Beep (440, 50);
176 #else
177 MessageBeep (-1);
178 #endif
179 }
180 }
181 else
182 {
183 /* no match found - search for internal command */
184 for (cmds_ptr = cmds; cmds_ptr->name; cmds_ptr++)
185 {
186 if (!_tcsnicmp (&str[start], cmds_ptr->name,
187 _tcslen (&str[start])))
188 {
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);
192 break;
193 }
194 }
195
196 #ifdef __REACTOS__
197 Beep (440, 50);
198 #else
199 MessageBeep (-1);
200 #endif
201 }
202 }
203
204
205 /*
206 * returns 1 if at least one match, else returns 0
207 */
208
209 BOOL ShowCompletionMatches (LPTSTR str, INT charcount)
210 {
211 WIN32_FIND_DATA file;
212 HANDLE hFile;
213 BOOL found_dot = FALSE;
214 INT curplace = 0;
215 INT start;
216 UINT count;
217 TCHAR path[MAX_PATH];
218 TCHAR fname[MAX_PATH];
219 TCHAR directory[MAX_PATH];
220 UINT longestfname = 0;
221 SHORT screenwidth;
222
223 /* expand current file name */
224 count = charcount - 1;
225 if (count < 0)
226 count = 0;
227
228 /* find front of word */
229 if (str[count] == _T('"'))
230 {
231 count--;
232 while (count > 0 && str[count] != _T('"'))
233 count--;
234 }
235 else
236 {
237 while (count > 0 && str[count] != _T(' '))
238 count--;
239 }
240
241 /* if not at beginning, go forward 1 */
242 if (str[count] == _T(' '))
243 count++;
244
245 start = count;
246
247 if (str[count] == _T('"'))
248 count++; /* don't increment start */
249
250 /* extract directory from word */
251 _tcscpy (directory, &str[count]);
252 curplace = _tcslen (directory) - 1;
253
254 if (curplace >= 0 && directory[curplace] == _T('"'))
255 directory[curplace--] = _T('\0');
256
257 _tcscpy (path, directory);
258
259 while (curplace >= 0 &&
260 directory[curplace] != _T('\\') &&
261 directory[curplace] != _T(':'))
262 {
263 directory[curplace] = 0;
264 curplace--;
265 }
266
267 /* look for a . in the filename */
268 for (count = _tcslen (directory); path[count] != _T('\0'); count++)
269 {
270 if (path[count] == _T('.'))
271 {
272 found_dot = TRUE;
273 break;
274 }
275 }
276
277 if (found_dot)
278 _tcscat (path, _T("*"));
279 else
280 _tcscat (path, _T("*.*"));
281
282 /* current fname */
283 curplace = 0;
284
285 hFile = FindFirstFile (path, &file);
286 if (hFile != INVALID_HANDLE_VALUE)
287 {
288 /* Get the size of longest filename first. */
289 do
290 {
291 if (_tcslen(file.cFileName) > longestfname)
292 {
293 longestfname = _tcslen(file.cFileName);
294 /* Directories get extra brackets around them. */
295 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
296 longestfname += 2;
297 }
298 }
299 while (FindNextFile (hFile, &file));
300 FindClose (hFile);
301
302 hFile = FindFirstFile (path, &file);
303
304 /* Count the highest number of columns */
305 GetScreenSize(&screenwidth, 0);
306
307 /* For counting columns of output */
308 count = 0;
309
310 /* Increase by the number of spaces behind file name */
311 longestfname += 3;
312
313 /* find anything */
314 ConOutChar (_T('\n'));
315 do
316 {
317 /* ignore . and .. */
318 if (!_tcscmp (file.cFileName, _T(".")) ||
319 !_tcscmp (file.cFileName, _T("..")))
320 continue;
321
322 if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
323 _stprintf (fname, _T("[%s]"), file.cFileName);
324 else
325 _tcscpy (fname, file.cFileName);
326
327 ConOutPrintf (_T("%*s"), - longestfname, fname);
328 count++;
329 /* output as much columns as fits on the screen */
330 if (count >= (screenwidth / longestfname))
331 {
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"));
336 count = 0;
337 }
338 }
339 while (FindNextFile (hFile, &file));
340
341 FindClose (hFile);
342
343 if (count)
344 ConOutChar (_T('\n'));
345 }
346 else
347 {
348 /* no match found */
349 #ifdef __REACTOS__
350 Beep (440, 50);
351 #else
352 MessageBeep (-1);
353 #endif
354 return FALSE;
355 }
356
357 return TRUE;
358 }
359 #endif
360
361 #ifdef FEATURE_4NT_FILENAME_COMPLETION
362
363 typedef struct _FileName
364 {
365 TCHAR Name[MAX_PATH];
366 } FileName;
367
368 VOID FindPrefixAndSuffix(LPTSTR strIN, LPTSTR szPrefix, LPTSTR szSuffix)
369 {
370 /* String that is to be examined */
371 TCHAR str[MAX_PATH];
372 /* temp pointers to used to find needed parts */
373 TCHAR * szSearch;
374 TCHAR * szSearch1;
375 TCHAR * szSearch2;
376 /* number of quotes in the string */
377 INT nQuotes = 0;
378 /* used in for loops */
379 INT i;
380 /* Char number to break the string at */
381 INT PBreak = 0;
382 INT SBreak = 0;
383 /* when phrasing a string, this tells weather
384 you are inside quotes ot not. */
385 BOOL bInside = FALSE;
386
387 szPrefix[0] = _T('\0');
388 szSuffix[0] = _T('\0');
389
390 /* Copy over the string to later be edited */
391 _tcscpy(str,strIN);
392
393 /* Count number of " */
394 for(i = 0; i < _tcslen(str); i++)
395 if(str[i] == _T('\"'))
396 nQuotes++;
397
398 /* Find the prefix and suffix */
399 if(nQuotes % 2 && nQuotes > 1)
400 {
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 " */
405 szSearch++;
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;
412 else
413 szSearch = szSearch1;
414 /* Move one char past */
415 szSearch++;
416 szSearch[0] = _T('\0');
417 _tcscpy(szPrefix,str);
418 return;
419
420 }
421
422 if(!_tcschr(str, _T(' ')))
423 {
424 /* No spaces, everything goes to Suffix */
425 _tcscpy(szSuffix,str);
426 /* look for a slash just in case */
427 szSearch = _tcsrchr(str, _T('\\'));
428 if(szSearch)
429 {
430 szSearch++;
431 szSearch[0] = _T('\0');
432 _tcscpy(szPrefix,str);
433 }
434 else
435 {
436 szPrefix[0] = _T('\0');
437 }
438 return;
439 }
440
441 if(!nQuotes)
442 {
443 /* No quotes, and there is a space*/
444 /* Take it after the last space */
445 szSearch = _tcsrchr(str, _T(' '));
446 szSearch++;
447 _tcscpy(szSuffix,szSearch);
448 /* Find the closest to the end space or \ */
449 _tcscpy(str,strIN);
450 szSearch1 = _tcsrchr(str, _T(' '));
451 szSearch2 = _tcsrchr(str, _T('\\'));
452 if(szSearch2 != NULL && _tcslen(szSearch1) > _tcslen(szSearch2))
453 szSearch = szSearch2;
454 else
455 szSearch = szSearch1;
456 szSearch++;
457 szSearch[0] = _T('\0');
458 _tcscpy(szPrefix,str);
459 return;
460 }
461
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++)
466 {
467 if(str[i] == _T('\"'))
468 bInside = !bInside;
469 if(str[i] == _T(' ') && !bInside)
470 SBreak = i;
471 if((str[i] == _T(' ') || str[i] == _T('\\')) && !bInside)
472 PBreak = i;
473
474 }
475 SBreak++;
476 PBreak++;
477 _tcscpy(szSuffix,&strIN[SBreak]);
478 strIN[PBreak] = _T('\0');
479 _tcscpy(szPrefix,strIN);
480 if(szPrefix[_tcslen(szPrefix) - 2] == _T('\"'))
481 {
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("\\"));
486
487 }
488
489 }
490 int compare(const void *arg1,const void *arg2)
491 {
492 FileName * File1;
493 FileName * File2;
494 INT ret;
495
496 File1 = malloc(sizeof(FileName));
497 File2 = malloc(sizeof(FileName));
498 if(!File1 || !File2)
499 return 0;
500
501 memcpy(File1,arg1,sizeof(FileName));
502 memcpy(File2,arg2,sizeof(FileName));
503
504 /* ret = _tcsicmp(File1->Name, File2->Name); */
505 ret = lstrcmpi(File1->Name, File2->Name);
506
507 free(File1);
508 free(File2);
509 return ret;
510 }
511
512 VOID CompleteFilename (LPTSTR strIN, BOOL bNext, LPTSTR strOut, INT cusor)
513 {
514 /* Length of string before we complete it */
515 INT StartLength;
516 /* Length of string after completed */
517 INT EndLength;
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 */
530 HANDLE hFile;
531 WIN32_FIND_DATA file;
532 /* List of all the files */
533 FileName * FileList = NULL;
534 /* Number of files */
535 INT FileListSize = 0;
536 /* Used for loops */
537 INT i;
538 /* Editable string of what was passed in */
539 TCHAR str[MAX_PATH];
540 /* Keeps track of what element was last selected */
541 static INT Sel;
542 BOOL NeededQuote = FALSE;
543 strOut[0] = _T('\0');
544
545 /* Copy the string, str can be edited and orginal should not be */
546 _tcscpy(str,strIN);
547 _tcscpy(szOrginal,strIN);
548
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');
552
553 /* Look to see if they hit tab again, if so cut off the diff length */
554 if(_tcscmp(str,LastReturned) || !_tcslen(str))
555 {
556 /* We need to know how many chars we added from the start */
557 StartLength = _tcslen(str);
558
559 /* no string, we need all files in that directory */
560 if(!StartLength)
561 {
562 _tcscat(str,_T("*"));
563 }
564
565 /* Zero it out first */
566 szBaseWord[0] = _T('\0');
567 szPrefix[0] = _T('\0');
568
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);
574 /* Strip quotes */
575 while(i < _tcslen(szBaseWord)+1)
576 {
577 if(szBaseWord[i] == _T('\"'))
578 memmove(&szBaseWord[i],&szBaseWord[i + 1], _tcslen(&szBaseWord[i]) * sizeof(TCHAR));
579 else
580 i++;
581 }
582
583 /* clear it out */
584 memset(szSearchPath, 0, sizeof(szSearchPath));
585
586 /* Start the search for all the files */
587 GetFullPathName(szBaseWord, MAX_PATH, szSearchPath, NULL);
588 if(StartLength > 0)
589 _tcscat(szSearchPath,_T("*"));
590 _tcscpy(LastSearch,szSearchPath);
591 _tcscpy(LastPrefix,szPrefix);
592 }
593 else
594 {
595 _tcscpy(szSearchPath, LastSearch);
596 _tcscpy(szPrefix, LastPrefix);
597 StartLength = 0;
598 }
599 /* search for the files it might be */
600 hFile = FindFirstFile (szSearchPath, &file);
601
602 /* aseemble a list of all files names */
603 do
604 {
605 if(hFile == INVALID_HANDLE_VALUE)
606 {
607 /* Assemble the orginal string and return */
608 _tcscpy(strOut,szOrginal);
609 CloseHandle(hFile);
610 if(FileList != NULL)
611 free(FileList);
612 return;
613 }
614
615 if(!_tcscmp (file.cFileName, _T(".")) ||
616 !_tcscmp (file.cFileName, _T("..")))
617 continue;
618 /* Add the file to the list of files */
619 if(FileList == NULL)
620 {
621 FileListSize = 1;
622 FileList = malloc(FileListSize * sizeof(FileName));
623 }
624 else
625 {
626 FileListSize++;
627 FileList = realloc(FileList, FileListSize * sizeof(FileName));
628 }
629
630 if(FileList == NULL)
631 {
632 /* Assemble the orginal string and return */
633 _tcscpy(strOut,szOrginal);
634 CloseHandle(hFile);
635 ConOutFormatMessage (GetLastError());
636 return;
637 }
638 /* Copies the file name into the struct */
639 _tcscpy(FileList[FileListSize-1].Name,file.cFileName);
640
641 }while(FindNextFile(hFile,&file));
642
643
644 /* Sort the files */
645 qsort(FileList,FileListSize,sizeof(FileName), compare);
646
647 /* Find the next/previous */
648 if(!_tcscmp(szOrginal,LastReturned))
649 {
650 if(bNext)
651 {
652 if(FileListSize - 1 == Sel)
653 Sel = 0;
654 else
655 Sel++;
656 }
657 else
658 {
659 if(!Sel)
660 Sel = FileListSize - 1;
661 else
662 Sel--;
663 }
664 }
665 else
666 {
667 Sel = 0;
668 }
669
670 /* nothing found that matched last time
671 so return the first thing in the list */
672 strOut[0] = _T('\0');
673
674 /* space in the name */
675 if(_tcschr(FileList[Sel].Name, _T(' ')))
676 {
677 INT LastSpace;
678 BOOL bInside;
679 /* It needs a " at the end */
680 NeededQuote = TRUE;
681 LastSpace = -1;
682 bInside = FALSE;
683 /* Find the place to put the " at the start */
684 for(i = 0; i < _tcslen(szPrefix); i++)
685 {
686 if(szPrefix[i] == _T('\"'))
687 bInside = !bInside;
688 if(szPrefix[i] == _T(' ') && !bInside)
689 LastSpace = i;
690
691 }
692 /* insert the quoation and move things around */
693 if(szPrefix[LastSpace + 1] == _T('\"') && LastSpace != -1)
694 {
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--)
698 {
699 szPrefix[i] = szPrefix[i - 1];
700 }
701
702 if(LastSpace + 1 == _tcslen(szPrefix))
703 {
704 _tcscat(szPrefix,_T("\""));
705 }
706 szPrefix[LastSpace + 1] = _T('\"');
707 }
708 else if(LastSpace == -1)
709 {
710 _tcscpy(szBaseWord,_T("\""));
711 _tcscat(szBaseWord,szPrefix);
712 _tcscpy(szPrefix,szBaseWord);
713
714 }
715 }
716
717 _tcscpy(strOut,szPrefix);
718 _tcscat(strOut,FileList[Sel].Name);
719
720 /* check for odd number of quotes means we need to close them */
721 if(!NeededQuote)
722 {
723 for(i = 0; i < _tcslen(strOut); i++)
724 if(strOut[i] == _T('\"'))
725 NeededQuote = !NeededQuote;
726 }
727
728 if(szPrefix[_tcslen(szPrefix) - 1] == _T('\"') || NeededQuote)
729 _tcscat(strOut,_T("\""));
730
731 _tcscpy(LastReturned,strOut);
732 EndLength = _tcslen(strOut);
733 DiffLength = EndLength - StartLength;
734 CloseHandle(hFile);
735 if(FileList != NULL)
736 free(FileList);
737
738 }
739 #endif