minor build fixes for msvc
[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)
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((!_tcsncmp(&str[i], _T(" "),1) || !_tcsncmp(&str[i], _T("\\"),1)) && !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(!_tcsncmp(&szPrefix[_tcslen(szPrefix) - 2],_T("\""),1))
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 INT ii;
539 /* Editable string of what was passed in */
540 TCHAR str[MAX_PATH];
541 /* Keeps track of what element was last selected */
542 static INT Sel;
543 BOOL NeededQuote = FALSE;
544 strOut[0] = _T('\0');
545
546 /* Copy the string, str can be edited and orginal should not be */
547 _tcscpy(str,strIN);
548 _tcscpy(szOrginal,strIN);
549
550 /* Look to see if the cusor is not at the end of the string */
551 if((cusor + 1) < _tcslen(str))
552 str[cusor] = _T('\0');
553
554 /* Look to see if they hit tab again, if so cut off the diff length */
555 if(_tcscmp(str,LastReturned) || !_tcslen(str))
556 {
557 /* We need to know how many chars we added from the start */
558 StartLength = _tcslen(str);
559
560 /* no string, we need all files in that directory */
561 if(!StartLength)
562 {
563 _tcscat(str,_T("*"));
564 }
565
566 /* Zero it out first */
567 szBaseWord[0] = _T('\0');
568 szPrefix[0] = _T('\0');
569
570 /*What comes out of this needs to be:
571 szBaseWord = path no quotes to the object
572 szPrefix = what leads up to the filename
573 no quote at the END of the full name */
574 FindPrefixAndSuffix(str,szPrefix,szBaseWord);
575 /* Strip quotes */
576 for(i = 0; i < _tcslen(szBaseWord); i++)
577 {
578 if(!_tcsncmp(&szBaseWord[i], _T("\""),1))
579 {
580 for(ii = i; ii < (_tcslen(szBaseWord)); ii++)
581 szBaseWord[ii] = szBaseWord[ii + 1];
582 }
583 }
584
585 /* clear it out */
586 memset(szSearchPath, 0, sizeof(szSearchPath));
587
588 /* Start the search for all the files */
589 GetFullPathName(szBaseWord, MAX_PATH, szSearchPath, NULL);
590 if(StartLength > 0)
591 _tcscat(szSearchPath,_T("*"));
592 _tcscpy(LastSearch,szSearchPath);
593 _tcscpy(LastPrefix,szPrefix);
594 }
595 else
596 {
597 _tcscpy(szSearchPath, LastSearch);
598 _tcscpy(szPrefix, LastPrefix);
599 StartLength = 0;
600 }
601 /* search for the files it might be */
602 hFile = FindFirstFile (szSearchPath, &file);
603
604 /* aseemble a list of all files names */
605 do
606 {
607 if(hFile == INVALID_HANDLE_VALUE)
608 {
609 /* Assemble the orginal string and return */
610 _tcscpy(strOut,szOrginal);
611 CloseHandle(hFile);
612 if(FileList != NULL)
613 free(FileList);
614 return;
615 }
616
617 if(!_tcscmp (file.cFileName, _T(".")) ||
618 !_tcscmp (file.cFileName, _T("..")))
619 continue;
620 /* Add the file to the list of files */
621 if(FileList == NULL)
622 {
623 FileListSize = 1;
624 FileList = malloc(FileListSize * sizeof(FileName));
625 }
626 else
627 {
628 FileListSize++;
629 FileList = realloc(FileList, FileListSize * sizeof(FileName));
630 }
631
632 if(FileList == NULL)
633 {
634 /* Assemble the orginal string and return */
635 _tcscpy(strOut,szOrginal);
636 CloseHandle(hFile);
637 ConOutFormatMessage (GetLastError());
638 return;
639 }
640 /* Copies the file name into the struct */
641 _tcscpy(FileList[FileListSize-1].Name,file.cFileName);
642
643 }while(FindNextFile(hFile,&file));
644
645
646 /* Sort the files */
647 qsort(FileList,FileListSize,sizeof(FileName), compare);
648
649 /* Find the next/previous */
650 if(!_tcscmp(szOrginal,LastReturned))
651 {
652 if(bNext)
653 {
654 if(FileListSize - 1 == Sel)
655 Sel = 0;
656 else
657 Sel++;
658 }
659 else
660 {
661 if(!Sel)
662 Sel = FileListSize - 1;
663 else
664 Sel--;
665 }
666 }
667 else
668 {
669 Sel = 0;
670 }
671
672 /* nothing found that matched last time
673 so return the first thing in the list */
674 strOut[0] = _T('\0');
675
676 /* space in the name */
677 if(_tcschr(FileList[Sel].Name, _T(' ')))
678 {
679 INT LastSpace;
680 BOOL bInside;
681 /* It needs a " at the end */
682 NeededQuote = TRUE;
683 LastSpace = -1;
684 bInside = FALSE;
685 /* Find the place to put the " at the start */
686 for(i = 0; i < _tcslen(szPrefix); i++)
687 {
688 if(szPrefix[i] == _T('\"'))
689 bInside = !bInside;
690 if(szPrefix[i] == _T(' ') && !bInside)
691 LastSpace = i;
692
693 }
694 /* insert the space and move things around */
695 if(_tcsncmp(&szPrefix[LastSpace + 1],_T("\""),1) && LastSpace != -1)
696 {
697 /* add another char or you will lose a null char ending */
698 _tcsncat(szPrefix,&szPrefix[_tcslen(szPrefix) - 1],1);
699 for(i = _tcslen(szPrefix) - 1; i > LastSpace; i--)
700 {
701 szPrefix[i] = szPrefix[i - 1];
702 }
703
704 if(LastSpace + 1 == _tcslen(szPrefix))
705 {
706 _tcscat(szPrefix,_T("\""));
707 }
708 szPrefix[LastSpace + 1] = _T('\"');
709 }
710 else if(LastSpace == -1)
711 {
712 _tcscpy(szBaseWord,_T("\""));
713 _tcscat(szBaseWord,szPrefix);
714 _tcscpy(szPrefix,szBaseWord);
715
716 }
717 }
718
719 _tcscpy(strOut,szPrefix);
720 _tcscat(strOut,FileList[Sel].Name);
721
722 /* check for odd number of quotes means we need to close them */
723 if(!NeededQuote)
724 {
725 for(i = 0; i < _tcslen(strOut); i++)
726 if(strOut[i] == _T('\"'))
727 NeededQuote = !NeededQuote;
728 }
729
730 if(szPrefix[_tcslen(szPrefix) - 1] == _T('\"') || NeededQuote)
731 _tcscat(strOut,_T("\""));
732
733 _tcscpy(LastReturned,strOut);
734 EndLength = _tcslen(strOut);
735 DiffLength = EndLength - StartLength;
736 CloseHandle(hFile);
737 if(FileList != NULL)
738 free(FileList);
739
740 }
741 #endif