xref: /reactos/base/shell/cmd/cmdinput.c (revision 1734f297)
1 /*
2  *  CMDINPUT.C - handles command input (tab completion, history, etc.).
3  *
4  *
5  *  History:
6  *
7  *    01/14/95 (Tim Norman)
8  *        started.
9  *
10  *    08/08/95 (Matt Rains)
11  *        i have cleaned up the source code. changes now bring this source
12  *        into guidelines for recommended programming practice.
13  *        i have added some constants to help making changes easier.
14  *
15  *    12/12/95 (Tim Norman)
16  *        added findxy() function to get max x/y coordinates to display
17  *        correctly on larger screens
18  *
19  *    12/14/95 (Tim Norman)
20  *        fixed the Tab completion code that Matt Rains broke by moving local
21  *        variables to a more global scope and forgetting to initialize them
22  *        when needed
23  *
24  *    8/1/96 (Tim Norman)
25  *        fixed a bug in tab completion that caused filenames at the beginning
26  *        of the command-line to have their first letter truncated
27  *
28  *    9/1/96 (Tim Norman)
29  *        fixed a silly bug using printf instead of fputs, where typing "%i"
30  *        confused printf :)
31  *
32  *    6/14/97 (Steffan Kaiser)
33  *        ctrl-break checking
34  *
35  *    6/7/97 (Marc Desrochers)
36  *        recoded everything! now properly adjusts when text font is changed.
37  *        removed findxy(), reposition(), and reprint(), as these functions
38  *        were inefficient. added goxy() function as gotoxy() was buggy when
39  *        the screen font was changed. the printf() problem with %i on the
40  *        command line was fixed by doing printf("%s",str) instead of
41  *        printf(str). Don't ask how I find em just be glad I do :)
42  *
43  *    7/12/97 (Tim Norman)
44  *        Note: above changes preempted Steffan's ctrl-break checking.
45  *
46  *    7/7/97 (Marc Desrochers)
47  *        rewrote a new findxy() because the new dir() used it.  This
48  *        findxy() simply returns the values of *maxx *maxy.  In the
49  *        future, please use the pointers, they will always be correct
50  *        since they point to BIOS values.
51  *
52  *    7/8/97 (Marc Desrochers)
53  *        once again removed findxy(), moved the *maxx, *maxy pointers
54  *        global and included them as externs in command.h.  Also added
55  *        insert/overstrike capability
56  *
57  *    7/13/97 (Tim Norman)
58  *        added different cursor appearance for insert/overstrike mode
59  *
60  *    7/13/97 (Tim Norman)
61  *        changed my code to use _setcursortype until I can figure out why
62  *        my code is crashing on some machines.  It doesn't crash on mine :)
63  *
64  *    27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
65  *        added config.h include
66  *
67  *    28-Jul-1998 (John P Price <linux-guru@gcfl.net>)
68  *        put ifdef's around filename completion code.
69  *
70  *    30-Jul-1998 (John P Price <linux-guru@gcfl.net>)
71  *        moved filename completion code to filecomp.c
72  *        made second TAB display list of filename matches
73  *
74  *    31-Jul-1998 (John P Price <linux-guru@gcfl.net>)
75  *        Fixed bug where if you typed something, then hit HOME, then tried
76  *        to type something else in insert mode, it crashed.
77  *
78  *    07-Aug-1998 (John P Price <linux-guru@gcfl.net>)
79  *        Fixed carriage return output to better match MSDOS with echo
80  *        on or off.(marked with "JPP 19980708")
81  *
82  *    13-Dec-1998 (Eric Kohl)
83  *        Added insert/overwrite cursor.
84  *
85  *    25-Jan-1998 (Eric Kohl)
86  *        Replaced CRT io functions by Win32 console io functions.
87  *        This can handle <Shift>-<Tab> for 4NT filename completion.
88  *        Unicode and redirection safe!
89  *
90  *    04-Feb-1999 (Eric Kohl)
91  *        Fixed input bug. A "line feed" character remained in the keyboard
92  *        input queue when you pressed <RETURN>. This sometimes caused
93  *        some very strange effects.
94  *        Fixed some command line editing annoyances.
95  *
96  *    30-Apr-2004 (Filip Navara <xnavara@volny.cz>)
97  *        Fixed problems when the screen was scrolled away.
98  *
99  *    28-September-2007 (Hervé Poussineau)
100  *        Added history possibilities to right key.
101  */
102 
103 #include "precomp.h"
104 
105 /*
106  * See https://technet.microsoft.com/en-us/library/cc978715.aspx
107  * and https://technet.microsoft.com/en-us/library/cc940805.aspx
108  * to know the differences between those two settings.
109  * Values 0x00, 0x0D (carriage return) and 0x20 (space) disable completion.
110  */
111 TCHAR AutoCompletionChar = _T('\t'); // Default is 0x20
112 TCHAR PathCompletionChar = _T('\t'); // Default is 0x20
113 
114 
115 SHORT maxx;
116 SHORT maxy;
117 
118 /*
119  * global command line insert/overwrite flag
120  */
121 static BOOL bInsert = TRUE;
122 
123 
124 static VOID
125 ClearCommandLine(LPTSTR str, INT maxlen, SHORT orgx, SHORT orgy)
126 {
127     INT count;
128 
129     SetCursorXY (orgx, orgy);
130     for (count = 0; count < (INT)_tcslen (str); count++)
131         ConOutChar (_T(' '));
132     _tcsnset (str, _T('\0'), maxlen);
133     SetCursorXY (orgx, orgy);
134 }
135 
136 
137 /* read in a command line */
138 BOOL ReadCommand(LPTSTR str, INT maxlen)
139 {
140     CONSOLE_SCREEN_BUFFER_INFO csbi;
141     SHORT orgx;     /* origin x/y */
142     SHORT orgy;
143     SHORT curx;     /*current x/y cursor position*/
144     SHORT cury;
145     SIZE_T tempscreen;
146     INT   count;    /*used in some for loops*/
147     INT   current = 0;  /*the position of the cursor in the string (str)*/
148     INT   charcount = 0;/*chars in the string (str)*/
149     INPUT_RECORD ir;
150     DWORD dwControlKeyState;
151 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
152     WORD   wLastKey = 0;
153 #endif
154     TCHAR  ch;
155     BOOL bReturn = FALSE;
156     BOOL bCharInput;
157 #ifdef FEATURE_4NT_FILENAME_COMPLETION
158     TCHAR szPath[MAX_PATH];
159 #endif
160 #ifdef FEATURE_HISTORY
161     //BOOL bContinue=FALSE;/*is TRUE the second case will not be executed*/
162     TCHAR PreviousChar;
163 #endif
164 
165     if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
166     {
167         /* No console */
168         HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
169         DWORD dwRead;
170         CHAR chr;
171         do
172         {
173             if (!ReadFile(hStdin, &chr, 1, &dwRead, NULL) || !dwRead)
174                 return FALSE;
175 #ifdef _UNICODE
176             MultiByteToWideChar(InputCodePage, 0, &chr, 1, &str[charcount++], 1);
177 #endif
178         } while (chr != '\n' && charcount < maxlen);
179         str[charcount] = _T('\0');
180         return TRUE;
181     }
182 
183     /* get screen size */
184     maxx = csbi.dwSize.X;
185     maxy = csbi.dwSize.Y;
186 
187     curx = orgx = csbi.dwCursorPosition.X;
188     cury = orgy = csbi.dwCursorPosition.Y;
189 
190     memset (str, 0, maxlen * sizeof (TCHAR));
191 
192     SetCursorType (bInsert, TRUE);
193 
194     do
195     {
196         bReturn = FALSE;
197         ConInKey (&ir);
198 
199         dwControlKeyState = ir.Event.KeyEvent.dwControlKeyState;
200 
201         if (dwControlKeyState &
202             (RIGHT_ALT_PRESSED |LEFT_ALT_PRESSED|
203              RIGHT_CTRL_PRESSED|LEFT_CTRL_PRESSED) )
204         {
205             switch (ir.Event.KeyEvent.wVirtualKeyCode)
206             {
207 #ifdef FEATURE_HISTORY
208                 case _T('K'):
209                     /* add the current command line to the history */
210                     if (dwControlKeyState &
211                         (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED))
212                     {
213                         if (str[0])
214                             History(0,str);
215 
216                         ClearCommandLine (str, maxlen, orgx, orgy);
217                         current = charcount = 0;
218                         curx = orgx;
219                         cury = orgy;
220                         //bContinue=TRUE;
221                         break;
222                     }
223 
224                 case _T('D'):
225                     /* delete current history entry */
226                     if (dwControlKeyState &
227                         (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED))
228                     {
229                         ClearCommandLine (str, maxlen, orgx, orgy);
230                         History_del_current_entry(str);
231                         current = charcount = _tcslen (str);
232                         ConOutPrintf (_T("%s"), str);
233                         GetCursorXY (&curx, &cury);
234                         //bContinue=TRUE;
235                         break;
236                     }
237 #endif /*FEATURE_HISTORY*/
238 
239                 case _T('M'):
240                     /* ^M does the same as return */
241                     if (dwControlKeyState &
242                         (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED))
243                     {
244                         /* end input, return to main */
245 #ifdef FEATURE_HISTORY
246                         /* add to the history */
247                         if (str[0])
248                             History(0, str);
249 #endif /*FEATURE_HISTORY*/
250                         str[charcount++] = _T('\n');
251                         str[charcount] = _T('\0');
252                         ConOutChar (_T('\n'));
253                         bReturn = TRUE;
254                         break;
255                     }
256             }
257         }
258 
259         bCharInput = FALSE;
260 
261         switch (ir.Event.KeyEvent.wVirtualKeyCode)
262         {
263             case VK_BACK:
264                 /* <BACKSPACE> - delete character to left of cursor */
265                 if (current > 0 && charcount > 0)
266                 {
267                     if (current == charcount)
268                     {
269                         /* if at end of line */
270                         str[current - 1] = _T('\0');
271                         if (GetCursorX () != 0)
272                         {
273                             ConOutPrintf (_T("\b \b"));
274                             curx--;
275                         }
276                         else
277                         {
278                             SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
279                             ConOutChar (_T(' '));
280                             SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
281                             cury--;
282                             curx = maxx - 1;
283                         }
284                     }
285                     else
286                     {
287                         for (count = current - 1; count < charcount; count++)
288                             str[count] = str[count + 1];
289                         if (GetCursorX () != 0)
290                         {
291                             SetCursorXY ((SHORT)(GetCursorX () - 1), GetCursorY ());
292                             curx--;
293                         }
294                         else
295                         {
296                             SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
297                             cury--;
298                             curx = maxx - 1;
299                         }
300                         GetCursorXY (&curx, &cury);
301                         ConOutPrintf (_T("%s "), &str[current - 1]);
302                         SetCursorXY (curx, cury);
303                     }
304                     charcount--;
305                     current--;
306                 }
307                 break;
308 
309             case VK_INSERT:
310                 /* toggle insert/overstrike mode */
311                 bInsert ^= TRUE;
312                 SetCursorType (bInsert, TRUE);
313                 break;
314 
315             case VK_DELETE:
316                 /* delete character under cursor */
317                 if (current != charcount && charcount > 0)
318                 {
319                     for (count = current; count < charcount; count++)
320                         str[count] = str[count + 1];
321                     charcount--;
322                     GetCursorXY (&curx, &cury);
323                     ConOutPrintf (_T("%s "), &str[current]);
324                     SetCursorXY (curx, cury);
325                 }
326                 break;
327 
328             case VK_HOME:
329                 /* goto beginning of string */
330                 if (current != 0)
331                 {
332                     SetCursorXY (orgx, orgy);
333                     curx = orgx;
334                     cury = orgy;
335                     current = 0;
336                 }
337                 break;
338 
339             case VK_END:
340                 /* goto end of string */
341                 if (current != charcount)
342                 {
343                     SetCursorXY (orgx, orgy);
344                     ConOutPrintf (_T("%s"), str);
345                     GetCursorXY (&curx, &cury);
346                     current = charcount;
347                 }
348                 break;
349 
350             case VK_TAB:
351 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
352                 /* expand current file name */
353                 if ((current == charcount) ||
354                     (current == charcount - 1 &&
355                      str[current] == _T('"'))) /* only works at end of line*/
356                 {
357                     if (wLastKey != VK_TAB)
358                     {
359                         /* if first TAB, complete filename*/
360                         tempscreen = charcount;
361                         CompleteFilename (str, charcount);
362                         charcount = _tcslen (str);
363                         current = charcount;
364 
365                         SetCursorXY (orgx, orgy);
366                         ConOutPrintf (_T("%s"), str);
367 
368                         if (tempscreen > charcount)
369                         {
370                             GetCursorXY (&curx, &cury);
371                             for (count = tempscreen - charcount; count--; )
372                                 ConOutChar (_T(' '));
373                             SetCursorXY (curx, cury);
374                         }
375                         else
376                         {
377                             if (((charcount + orgx) / maxx) + orgy > maxy - 1)
378                                 orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
379                         }
380 
381                         /* set cursor position */
382                         SetCursorXY ((orgx + current) % maxx,
383                                  orgy + (orgx + current) / maxx);
384                         GetCursorXY (&curx, &cury);
385                     }
386                     else
387                     {
388                         /*if second TAB, list matches*/
389                         if (ShowCompletionMatches (str, charcount))
390                         {
391                             PrintPrompt();
392                             GetCursorXY(&orgx, &orgy);
393                             ConOutPrintf(_T("%s"), str);
394 
395                             /* set cursor position */
396                             SetCursorXY((orgx + current) % maxx,
397                                          orgy + (orgx + current) / maxx);
398                             GetCursorXY(&curx, &cury);
399                         }
400 
401                     }
402                 }
403                 else
404                 {
405                     MessageBeep(-1);
406                 }
407 #endif
408 #ifdef FEATURE_4NT_FILENAME_COMPLETION
409                 /* used to later see if we went down to the next line */
410                 tempscreen = charcount;
411                 szPath[0]=_T('\0');
412 
413                 /* str is the whole things that is on the current line
414                    that is and and out.  arg 2 is weather it goes back
415                     one file or forward one file */
416                 CompleteFilename(str, !(ir.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED), szPath, current);
417                 /* Attempt to clear the line */
418                 ClearCommandLine (str, maxlen, orgx, orgy);
419                 curx = orgx;
420                 cury = orgy;
421                 current = charcount = 0;
422 
423                 /* Everything is deleted, lets add it back in */
424                 _tcscpy(str,szPath);
425 
426                 /* Figure out where cusor is going to be after we print it */
427                 charcount = _tcslen(str);
428                 current = charcount;
429 
430                 SetCursorXY(orgx, orgy);
431                 /* Print out what we have now */
432                 ConOutPrintf(_T("%s"), str);
433 
434                 /* Move cursor accordingly */
435                 if (tempscreen > charcount)
436                 {
437                     GetCursorXY(&curx, &cury);
438                     for(count = tempscreen - charcount; count--; )
439                         ConOutChar(_T(' '));
440                     SetCursorXY(curx, cury);
441                 }
442                 else
443                 {
444                     if (((charcount + orgx) / maxx) + orgy > maxy - 1)
445                         orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
446                 }
447                 SetCursorXY((short)(((int)orgx + current) % maxx), (short)((int)orgy + ((int)orgx + current) / maxx));
448                 GetCursorXY(&curx, &cury);
449 #endif
450                 break;
451 
452             case _T('C'):
453                 if ((ir.Event.KeyEvent.dwControlKeyState &
454                     (RIGHT_CTRL_PRESSED|LEFT_CTRL_PRESSED)))
455                 {
456                     /* Ignore the Ctrl-C key event if it has already been handled */
457                     if (!bCtrlBreak)
458                         break;
459 
460                     /*
461                      * Fully print the entered string
462                      * so the command prompt would not overwrite it.
463                      */
464                     SetCursorXY(orgx, orgy);
465                     ConOutPrintf(_T("%s"), str);
466 
467                     /*
468                      * A Ctrl-C. Do not clear the command line,
469                      * but return an empty string in str.
470                      */
471                     str[0] = _T('\0');
472                     curx = orgx;
473                     cury = orgy;
474                     current = charcount = 0;
475                     bReturn = TRUE;
476                 }
477                 else
478                 {
479                     /* Just a normal 'C' character */
480                     bCharInput = TRUE;
481                 }
482                 break;
483 
484             case VK_RETURN:
485                 /* end input, return to main */
486 #ifdef FEATURE_HISTORY
487                 /* add to the history */
488                 if (str[0])
489                     History(0, str);
490 #endif
491                 str[charcount++] = _T('\n');
492                 str[charcount] = _T('\0');
493                 ConOutChar(_T('\n'));
494                 bReturn = TRUE;
495                 break;
496 
497             case VK_ESCAPE:
498                 /* clear str  Make this callable! */
499                 ClearCommandLine (str, maxlen, orgx, orgy);
500                 curx = orgx;
501                 cury = orgy;
502                 current = charcount = 0;
503                 break;
504 
505 #ifdef FEATURE_HISTORY
506             case VK_F3:
507                 History_move_to_bottom();
508 #endif
509             case VK_UP:
510 #ifdef FEATURE_HISTORY
511                 /* get previous command from buffer */
512                 ClearCommandLine (str, maxlen, orgx, orgy);
513                 History(-1, str);
514                 current = charcount = _tcslen (str);
515                 if (((charcount + orgx) / maxx) + orgy > maxy - 1)
516                     orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
517                 ConOutPrintf (_T("%s"), str);
518                 GetCursorXY (&curx, &cury);
519 #endif
520                 break;
521 
522             case VK_DOWN:
523 #ifdef FEATURE_HISTORY
524                 /* get next command from buffer */
525                 ClearCommandLine (str, maxlen, orgx, orgy);
526                 History(1, str);
527                 current = charcount = _tcslen (str);
528                 if (((charcount + orgx) / maxx) + orgy > maxy - 1)
529                     orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
530                 ConOutPrintf (_T("%s"), str);
531                 GetCursorXY (&curx, &cury);
532 #endif
533                 break;
534 
535             case VK_LEFT:
536                 if (dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
537                 {
538                     /* move cursor to the previous word */
539                     if (current > 0)
540                     {
541                         while (current > 0 && str[current - 1] == _T(' '))
542                         {
543                             current--;
544                             if (curx == 0)
545                             {
546                                 cury--;
547                                 curx = maxx -1;
548                             }
549                             else
550                             {
551                                 curx--;
552                             }
553                         }
554 
555                         while (current > 0 && str[current -1] != _T(' '))
556                         {
557                             current--;
558                             if (curx == 0)
559                             {
560                                 cury--;
561                                 curx = maxx -1;
562                             }
563                             else
564                             {
565                                 curx--;
566                             }
567                         }
568 
569                         SetCursorXY(curx, cury);
570                     }
571                 }
572                 else
573                 {
574                     /* move cursor left */
575                     if (current > 0)
576                     {
577                         current--;
578                         if (GetCursorX () == 0)
579                         {
580                             SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
581                             curx = maxx - 1;
582                             cury--;
583                         }
584                         else
585                         {
586                             SetCursorXY ((SHORT)(GetCursorX () - 1), GetCursorY ());
587                             curx--;
588                         }
589                     }
590                     else
591                     {
592                         MessageBeep (-1);
593                     }
594                 }
595                 break;
596 
597             case VK_RIGHT:
598                 if (dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
599                 {
600                     /* move cursor to the next word */
601                     if (current != charcount)
602                     {
603                         while (current != charcount && str[current] != _T(' '))
604                         {
605                             current++;
606                             if (curx == maxx - 1)
607                             {
608                                 cury++;
609                                 curx = 0;
610                             }
611                             else
612                             {
613                                 curx++;
614                             }
615                         }
616 
617                         while (current != charcount && str[current] == _T(' '))
618                         {
619                             current++;
620                             if (curx == maxx - 1)
621                             {
622                                 cury++;
623                                 curx = 0;
624                             }
625                             else
626                             {
627                                 curx++;
628                             }
629                         }
630 
631                         SetCursorXY(curx, cury);
632                     }
633                 }
634                 else
635                 {
636                     /* move cursor right */
637                     if (current != charcount)
638                     {
639                         current++;
640                         if (GetCursorX () == maxx - 1)
641                         {
642                             SetCursorXY (0, (SHORT)(GetCursorY () + 1));
643                             curx = 0;
644                             cury++;
645                         }
646                         else
647                         {
648                             SetCursorXY ((SHORT)(GetCursorX () + 1), GetCursorY ());
649                             curx++;
650                         }
651                     }
652 #ifdef FEATURE_HISTORY
653                     else
654                     {
655                         LPCTSTR last = PeekHistory(-1);
656                         if (last && charcount < (INT)_tcslen (last))
657                         {
658                             PreviousChar = last[current];
659                             ConOutChar(PreviousChar);
660                             GetCursorXY(&curx, &cury);
661                             str[current++] = PreviousChar;
662                             charcount++;
663                         }
664                     }
665 #endif
666                 }
667                 break;
668 
669             default:
670                 /* This input is just a normal char */
671                 bCharInput = TRUE;
672 
673             }
674 #ifdef _UNICODE
675             ch = ir.Event.KeyEvent.uChar.UnicodeChar;
676             if (ch >= 32 && (charcount != (maxlen - 2)) && bCharInput)
677 #else
678             ch = ir.Event.KeyEvent.uChar.AsciiChar;
679             if ((UCHAR)ch >= 32 && (charcount != (maxlen - 2)) && bCharInput)
680 #endif /* _UNICODE */
681             {
682                 /* insert character into string... */
683                 if (bInsert && current != charcount)
684                 {
685                     /* If this character insertion will cause screen scrolling,
686                      * adjust the saved origin of the command prompt. */
687                     tempscreen = _tcslen(str + current) + curx;
688                     if ((tempscreen % maxx) == (maxx - 1) &&
689                         (tempscreen / maxx) + cury == (maxy - 1))
690                     {
691                         orgy--;
692                         cury--;
693                     }
694 
695                     for (count = charcount; count > current; count--)
696                         str[count] = str[count - 1];
697                     str[current++] = ch;
698                     if (curx == maxx - 1)
699                         curx = 0, cury++;
700                     else
701                         curx++;
702                     ConOutPrintf (_T("%s"), &str[current - 1]);
703                     SetCursorXY (curx, cury);
704                     charcount++;
705                 }
706                 else
707                 {
708                     if (current == charcount)
709                         charcount++;
710                     str[current++] = ch;
711                     if (GetCursorX () == maxx - 1 && GetCursorY () == maxy - 1)
712                         orgy--, cury--;
713                     if (GetCursorX () == maxx - 1)
714                         curx = 0, cury++;
715                     else
716                         curx++;
717                     ConOutChar (ch);
718                 }
719             }
720 
721         //wLastKey = ir.Event.KeyEvent.wVirtualKeyCode;
722     }
723     while (!bReturn);
724 
725     SetCursorType (bInsert, TRUE);
726 
727 #ifdef FEATURE_ALIASES
728     /* expand all aliases */
729     ExpandAlias (str, maxlen);
730 #endif /* FEATURE_ALIAS */
731     return TRUE;
732 }
733