[CMD]: Diverse improvements:
[reactos.git] / reactos / base / shell / cmd / cmdinput.c
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 SHORT 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 '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 '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
238 #endif /*FEATURE_HISTORY*/
239 }
240 }
241
242 bCharInput = FALSE;
243
244 switch (ir.Event.KeyEvent.wVirtualKeyCode)
245 {
246 case VK_BACK:
247 /* <BACKSPACE> - delete character to left of cursor */
248 if (current > 0 && charcount > 0)
249 {
250 if (current == charcount)
251 {
252 /* if at end of line */
253 str[current - 1] = _T('\0');
254 if (GetCursorX () != 0)
255 {
256 ConOutPrintf (_T("\b \b"));
257 curx--;
258 }
259 else
260 {
261 SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
262 ConOutChar (_T(' '));
263 SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
264 cury--;
265 curx = maxx - 1;
266 }
267 }
268 else
269 {
270 for (count = current - 1; count < charcount; count++)
271 str[count] = str[count + 1];
272 if (GetCursorX () != 0)
273 {
274 SetCursorXY ((SHORT)(GetCursorX () - 1), GetCursorY ());
275 curx--;
276 }
277 else
278 {
279 SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
280 cury--;
281 curx = maxx - 1;
282 }
283 GetCursorXY (&curx, &cury);
284 ConOutPrintf (_T("%s "), &str[current - 1]);
285 SetCursorXY (curx, cury);
286 }
287 charcount--;
288 current--;
289 }
290 break;
291
292 case VK_INSERT:
293 /* toggle insert/overstrike mode */
294 bInsert ^= TRUE;
295 SetCursorType (bInsert, TRUE);
296 break;
297
298 case VK_DELETE:
299 /* delete character under cursor */
300 if (current != charcount && charcount > 0)
301 {
302 for (count = current; count < charcount; count++)
303 str[count] = str[count + 1];
304 charcount--;
305 GetCursorXY (&curx, &cury);
306 ConOutPrintf (_T("%s "), &str[current]);
307 SetCursorXY (curx, cury);
308 }
309 break;
310
311 case VK_HOME:
312 /* goto beginning of string */
313 if (current != 0)
314 {
315 SetCursorXY (orgx, orgy);
316 curx = orgx;
317 cury = orgy;
318 current = 0;
319 }
320 break;
321
322 case VK_END:
323 /* goto end of string */
324 if (current != charcount)
325 {
326 SetCursorXY (orgx, orgy);
327 ConOutPrintf (_T("%s"), str);
328 GetCursorXY (&curx, &cury);
329 current = charcount;
330 }
331 break;
332
333 case VK_TAB:
334 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
335 /* expand current file name */
336 if ((current == charcount) ||
337 (current == charcount - 1 &&
338 str[current] == _T('"'))) /* only works at end of line*/
339 {
340 if (wLastKey != VK_TAB)
341 {
342 /* if first TAB, complete filename*/
343 tempscreen = charcount;
344 CompleteFilename (str, charcount);
345 charcount = _tcslen (str);
346 current = charcount;
347
348 SetCursorXY (orgx, orgy);
349 ConOutPrintf (_T("%s"), str);
350
351 if (tempscreen > charcount)
352 {
353 GetCursorXY (&curx, &cury);
354 for (count = tempscreen - charcount; count--; )
355 ConOutChar (_T(' '));
356 SetCursorXY (curx, cury);
357 }
358 else
359 {
360 if (((charcount + orgx) / maxx) + orgy > maxy - 1)
361 orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
362 }
363
364 /* set cursor position */
365 SetCursorXY ((orgx + current) % maxx,
366 orgy + (orgx + current) / maxx);
367 GetCursorXY (&curx, &cury);
368 }
369 else
370 {
371 /*if second TAB, list matches*/
372 if (ShowCompletionMatches (str, charcount))
373 {
374 PrintPrompt();
375 GetCursorXY(&orgx, &orgy);
376 ConOutPrintf(_T("%s"), str);
377
378 /* set cursor position */
379 SetCursorXY((orgx + current) % maxx,
380 orgy + (orgx + current) / maxx);
381 GetCursorXY(&curx, &cury);
382 }
383
384 }
385 }
386 else
387 {
388 MessageBeep(-1);
389 }
390 #endif
391 #ifdef FEATURE_4NT_FILENAME_COMPLETION
392 /* used to later see if we went down to the next line */
393 tempscreen = charcount;
394 szPath[0]=_T('\0');
395
396 /* str is the whole things that is on the current line
397 that is and and out. arg 2 is weather it goes back
398 one file or forward one file */
399 CompleteFilename(str, !(ir.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED), szPath, current);
400 /* Attempt to clear the line */
401 ClearCommandLine (str, maxlen, orgx, orgy);
402 curx = orgx;
403 cury = orgy;
404 current = charcount = 0;
405
406 /* Everything is deleted, lets add it back in */
407 _tcscpy(str,szPath);
408
409 /* Figure out where cusor is going to be after we print it */
410 charcount = _tcslen(str);
411 current = charcount;
412
413 SetCursorXY(orgx, orgy);
414 /* Print out what we have now */
415 ConOutPrintf(_T("%s"), str);
416
417 /* Move cursor accordingly */
418 if (tempscreen > charcount)
419 {
420 GetCursorXY(&curx, &cury);
421 for(count = tempscreen - charcount; count--; )
422 ConOutChar(_T(' '));
423 SetCursorXY(curx, cury);
424 }
425 else
426 {
427 if (((charcount + orgx) / maxx) + orgy > maxy - 1)
428 orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
429 }
430 SetCursorXY((short)(((int)orgx + current) % maxx), (short)((int)orgy + ((int)orgx + current) / maxx));
431 GetCursorXY(&curx, &cury);
432 #endif
433 break;
434
435 case _T('M'):
436 case _T('C'):
437 /* ^M does the same as return */
438 bCharInput = TRUE;
439 if (!(ir.Event.KeyEvent.dwControlKeyState &
440 (RIGHT_CTRL_PRESSED|LEFT_CTRL_PRESSED)))
441 {
442 break;
443 }
444
445 case VK_RETURN:
446 /* end input, return to main */
447 #ifdef FEATURE_HISTORY
448 /* add to the history */
449 if (str[0])
450 History (0, str);
451 #endif
452 str[charcount++] = _T('\n');
453 str[charcount] = _T('\0');
454 ConOutChar(_T('\n'));
455 bReturn = TRUE;
456 break;
457
458 case VK_ESCAPE:
459 /* clear str Make this callable! */
460 ClearCommandLine (str, maxlen, orgx, orgy);
461 curx = orgx;
462 cury = orgy;
463 current = charcount = 0;
464 break;
465
466 #ifdef FEATURE_HISTORY
467 case VK_F3:
468 History_move_to_bottom();
469 #endif
470 case VK_UP:
471 #ifdef FEATURE_HISTORY
472 /* get previous command from buffer */
473 ClearCommandLine (str, maxlen, orgx, orgy);
474 History (-1, str);
475 current = charcount = _tcslen (str);
476 if (((charcount + orgx) / maxx) + orgy > maxy - 1)
477 orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
478 ConOutPrintf (_T("%s"), str);
479 GetCursorXY (&curx, &cury);
480 #endif
481 break;
482
483 case VK_DOWN:
484 #ifdef FEATURE_HISTORY
485 /* get next command from buffer */
486 ClearCommandLine (str, maxlen, orgx, orgy);
487 History (1, str);
488 current = charcount = _tcslen (str);
489 if (((charcount + orgx) / maxx) + orgy > maxy - 1)
490 orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
491 ConOutPrintf (_T("%s"), str);
492 GetCursorXY (&curx, &cury);
493 #endif
494 break;
495
496 case VK_LEFT:
497 if (dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
498 {
499 /* move cursor to the previous word */
500 if (current > 0)
501 {
502 while (current > 0 && str[current - 1] == _T(' '))
503 {
504 current--;
505 if (curx == 0)
506 {
507 cury--;
508 curx = maxx -1;
509 }
510 else
511 {
512 curx--;
513 }
514 }
515
516 while (current > 0 && str[current -1] != _T(' '))
517 {
518 current--;
519 if (curx == 0)
520 {
521 cury--;
522 curx = maxx -1;
523 }
524 else
525 {
526 curx--;
527 }
528 }
529
530 SetCursorXY(curx, cury);
531 }
532 }
533 else
534 {
535 /* move cursor left */
536 if (current > 0)
537 {
538 current--;
539 if (GetCursorX () == 0)
540 {
541 SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
542 curx = maxx - 1;
543 cury--;
544 }
545 else
546 {
547 SetCursorXY ((SHORT)(GetCursorX () - 1), GetCursorY ());
548 curx--;
549 }
550 }
551 else
552 {
553 MessageBeep (-1);
554 }
555 }
556 break;
557
558 case VK_RIGHT:
559 if (dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
560 {
561 /* move cursor to the next word */
562 if (current != charcount)
563 {
564 while (current != charcount && str[current] != _T(' '))
565 {
566 current++;
567 if (curx == maxx - 1)
568 {
569 cury++;
570 curx = 0;
571 }
572 else
573 {
574 curx++;
575 }
576 }
577
578 while (current != charcount && str[current] == _T(' '))
579 {
580 current++;
581 if (curx == maxx - 1)
582 {
583 cury++;
584 curx = 0;
585 }
586 else
587 {
588 curx++;
589 }
590 }
591
592 SetCursorXY(curx, cury);
593 }
594 }
595 else
596 {
597 /* move cursor right */
598 if (current != charcount)
599 {
600 current++;
601 if (GetCursorX () == maxx - 1)
602 {
603 SetCursorXY (0, (SHORT)(GetCursorY () + 1));
604 curx = 0;
605 cury++;
606 }
607 else
608 {
609 SetCursorXY ((SHORT)(GetCursorX () + 1), GetCursorY ());
610 curx++;
611 }
612 }
613 #ifdef FEATURE_HISTORY
614 else
615 {
616 LPCTSTR last = PeekHistory(-1);
617 if (last && charcount < (INT)_tcslen (last))
618 {
619 PreviousChar = last[current];
620 ConOutChar(PreviousChar);
621 GetCursorXY(&curx, &cury);
622 str[current++] = PreviousChar;
623 charcount++;
624 }
625 }
626 #endif
627 }
628 break;
629
630 default:
631 /* This input is just a normal char */
632 bCharInput = TRUE;
633
634 }
635 #ifdef _UNICODE
636 ch = ir.Event.KeyEvent.uChar.UnicodeChar;
637 if (ch >= 32 && (charcount != (maxlen - 2)) && bCharInput)
638 #else
639 ch = ir.Event.KeyEvent.uChar.AsciiChar;
640 if ((UCHAR)ch >= 32 && (charcount != (maxlen - 2)) && bCharInput)
641 #endif /* _UNICODE */
642 {
643 /* insert character into string... */
644 if (bInsert && current != charcount)
645 {
646 /* If this character insertion will cause screen scrolling,
647 * adjust the saved origin of the command prompt. */
648 tempscreen = _tcslen(str + current) + curx;
649 if ((tempscreen % maxx) == (maxx - 1) &&
650 (tempscreen / maxx) + cury == (maxy - 1))
651 {
652 orgy--;
653 cury--;
654 }
655
656 for (count = charcount; count > current; count--)
657 str[count] = str[count - 1];
658 str[current++] = ch;
659 if (curx == maxx - 1)
660 curx = 0, cury++;
661 else
662 curx++;
663 ConOutPrintf (_T("%s"), &str[current - 1]);
664 SetCursorXY (curx, cury);
665 charcount++;
666 }
667 else
668 {
669 if (current == charcount)
670 charcount++;
671 str[current++] = ch;
672 if (GetCursorX () == maxx - 1 && GetCursorY () == maxy - 1)
673 orgy--, cury--;
674 if (GetCursorX () == maxx - 1)
675 curx = 0, cury++;
676 else
677 curx++;
678 ConOutChar (ch);
679 }
680 }
681
682 //wLastKey = ir.Event.KeyEvent.wVirtualKeyCode;
683 }
684 while (!bReturn);
685
686 SetCursorType (bInsert, TRUE);
687
688 #ifdef FEATURE_ALIASES
689 /* expand all aliases */
690 ExpandAlias (str, maxlen);
691 #endif /* FEATURE_ALIAS */
692 return TRUE;
693 }