9513277b8dd05db0325bc9b0973294f273d3bb3c
[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 pre-empted 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 carrage 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 SHORT maxx;
106 SHORT maxy;
107
108 /*
109 * global command line insert/overwrite flag
110 */
111 static BOOL bInsert = TRUE;
112
113
114 static VOID
115 ClearCommandLine(LPTSTR str, INT maxlen, SHORT orgx, SHORT orgy)
116 {
117 INT count;
118
119 SetCursorXY (orgx, orgy);
120 for (count = 0; count < (INT)_tcslen (str); count++)
121 ConOutChar (_T(' '));
122 _tcsnset (str, _T('\0'), maxlen);
123 SetCursorXY (orgx, orgy);
124 }
125
126
127 /* read in a command line */
128 BOOL ReadCommand(LPTSTR str, INT maxlen)
129 {
130 CONSOLE_SCREEN_BUFFER_INFO csbi;
131 SHORT orgx; /* origin x/y */
132 SHORT orgy;
133 SHORT curx; /*current x/y cursor position*/
134 SHORT cury;
135 SHORT tempscreen;
136 INT count; /*used in some for loops*/
137 INT current = 0; /*the position of the cursor in the string (str)*/
138 INT charcount = 0;/*chars in the string (str)*/
139 INPUT_RECORD ir;
140 DWORD dwControlKeyState;
141 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
142 WORD wLastKey = 0;
143 #endif
144 TCHAR ch;
145 BOOL bReturn = FALSE;
146 BOOL bCharInput;
147 #ifdef FEATURE_4NT_FILENAME_COMPLETION
148 TCHAR szPath[MAX_PATH];
149 #endif
150 #ifdef FEATURE_HISTORY
151 //BOOL bContinue=FALSE;/*is TRUE the second case will not be executed*/
152 TCHAR PreviousChar;
153 #endif
154
155 if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
156 {
157 /* No console */
158 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
159 DWORD dwRead;
160 CHAR chr;
161 do
162 {
163 if (!ReadFile(hStdin, &chr, 1, &dwRead, NULL) || !dwRead)
164 return FALSE;
165 #ifdef _UNICODE
166 MultiByteToWideChar(InputCodePage, 0, &chr, 1, &str[charcount++], 1);
167 #endif
168 } while (chr != '\n' && charcount < maxlen);
169 str[charcount] = _T('\0');
170 return TRUE;
171 }
172
173 /* get screen size */
174 maxx = csbi.dwSize.X;
175 maxy = csbi.dwSize.Y;
176
177 curx = orgx = csbi.dwCursorPosition.X;
178 cury = orgy = csbi.dwCursorPosition.Y;
179
180 memset (str, 0, maxlen * sizeof (TCHAR));
181
182 SetCursorType (bInsert, TRUE);
183
184 do
185 {
186 bReturn = FALSE;
187 ConInKey (&ir);
188
189 dwControlKeyState = ir.Event.KeyEvent.dwControlKeyState;
190
191 if (dwControlKeyState &
192 (RIGHT_ALT_PRESSED |LEFT_ALT_PRESSED|
193 RIGHT_CTRL_PRESSED|LEFT_CTRL_PRESSED) )
194 {
195 switch (ir.Event.KeyEvent.wVirtualKeyCode)
196 {
197 #ifdef FEATURE_HISTORY
198 case 'K':
199 /*add the current command line to the history*/
200 if (dwControlKeyState &
201 (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED))
202 {
203 if (str[0])
204 History(0,str);
205
206 ClearCommandLine (str, maxlen, orgx, orgy);
207 current = charcount = 0;
208 curx = orgx;
209 cury = orgy;
210 //bContinue=TRUE;
211 break;
212 }
213
214 case 'D':
215 /*delete current history entry*/
216 if (dwControlKeyState &
217 (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED))
218 {
219 ClearCommandLine (str, maxlen, orgx, orgy);
220 History_del_current_entry(str);
221 current = charcount = _tcslen (str);
222 ConOutPrintf (_T("%s"), str);
223 GetCursorXY (&curx, &cury);
224 //bContinue=TRUE;
225 break;
226 }
227
228 #endif /*FEATURE_HISTORY*/
229 }
230 }
231
232 bCharInput = FALSE;
233
234 switch (ir.Event.KeyEvent.wVirtualKeyCode)
235 {
236 case VK_BACK:
237 /* <BACKSPACE> - delete character to left of cursor */
238 if (current > 0 && charcount > 0)
239 {
240 if (current == charcount)
241 {
242 /* if at end of line */
243 str[current - 1] = _T('\0');
244 if (GetCursorX () != 0)
245 {
246 ConOutPrintf (_T("\b \b"));
247 curx--;
248 }
249 else
250 {
251 SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
252 ConOutChar (_T(' '));
253 SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
254 cury--;
255 curx = maxx - 1;
256 }
257 }
258 else
259 {
260 for (count = current - 1; count < charcount; count++)
261 str[count] = str[count + 1];
262 if (GetCursorX () != 0)
263 {
264 SetCursorXY ((SHORT)(GetCursorX () - 1), GetCursorY ());
265 curx--;
266 }
267 else
268 {
269 SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
270 cury--;
271 curx = maxx - 1;
272 }
273 GetCursorXY (&curx, &cury);
274 ConOutPrintf (_T("%s "), &str[current - 1]);
275 SetCursorXY (curx, cury);
276 }
277 charcount--;
278 current--;
279 }
280 break;
281
282 case VK_INSERT:
283 /* toggle insert/overstrike mode */
284 bInsert ^= TRUE;
285 SetCursorType (bInsert, TRUE);
286 break;
287
288 case VK_DELETE:
289 /* delete character under cursor */
290 if (current != charcount && charcount > 0)
291 {
292 for (count = current; count < charcount; count++)
293 str[count] = str[count + 1];
294 charcount--;
295 GetCursorXY (&curx, &cury);
296 ConOutPrintf (_T("%s "), &str[current]);
297 SetCursorXY (curx, cury);
298 }
299 break;
300
301 case VK_HOME:
302 /* goto beginning of string */
303 if (current != 0)
304 {
305 SetCursorXY (orgx, orgy);
306 curx = orgx;
307 cury = orgy;
308 current = 0;
309 }
310 break;
311
312 case VK_END:
313 /* goto end of string */
314 if (current != charcount)
315 {
316 SetCursorXY (orgx, orgy);
317 ConOutPrintf (_T("%s"), str);
318 GetCursorXY (&curx, &cury);
319 current = charcount;
320 }
321 break;
322
323 case VK_TAB:
324 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
325 /* expand current file name */
326 if ((current == charcount) ||
327 (current == charcount - 1 &&
328 str[current] == _T('"'))) /* only works at end of line*/
329 {
330 if (wLastKey != VK_TAB)
331 {
332 /* if first TAB, complete filename*/
333 tempscreen = charcount;
334 CompleteFilename (str, charcount);
335 charcount = _tcslen (str);
336 current = charcount;
337
338 SetCursorXY (orgx, orgy);
339 ConOutPrintf (_T("%s"), str);
340
341 if (tempscreen > charcount)
342 {
343 GetCursorXY (&curx, &cury);
344 for (count = tempscreen - charcount; count--; )
345 ConOutChar (_T(' '));
346 SetCursorXY (curx, cury);
347 }
348 else
349 {
350 if (((charcount + orgx) / maxx) + orgy > maxy - 1)
351 orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
352 }
353
354 /* set cursor position */
355 SetCursorXY ((orgx + current) % maxx,
356 orgy + (orgx + current) / maxx);
357 GetCursorXY (&curx, &cury);
358 }
359 else
360 {
361 /*if second TAB, list matches*/
362 if (ShowCompletionMatches (str, charcount))
363 {
364 PrintPrompt();
365 GetCursorXY(&orgx, &orgy);
366 ConOutPrintf(_T("%s"), str);
367
368 /* set cursor position */
369 SetCursorXY((orgx + current) % maxx,
370 orgy + (orgx + current) / maxx);
371 GetCursorXY(&curx, &cury);
372 }
373
374 }
375 }
376 else
377 {
378 MessageBeep(-1);
379 }
380 #endif
381 #ifdef FEATURE_4NT_FILENAME_COMPLETION
382 /* used to later see if we went down to the next line */
383 tempscreen = charcount;
384 szPath[0]=_T('\0');
385
386 /* str is the whole things that is on the current line
387 that is and and out. arg 2 is weather it goes back
388 one file or forward one file */
389 CompleteFilename(str, !(ir.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED), szPath, current);
390 /* Attempt to clear the line */
391 ClearCommandLine (str, maxlen, orgx, orgy);
392 curx = orgx;
393 cury = orgy;
394 current = charcount = 0;
395
396 /* Everything is deleted, lets add it back in */
397 _tcscpy(str,szPath);
398
399 /* Figure out where cusor is going to be after we print it */
400 charcount = _tcslen(str);
401 current = charcount;
402
403 SetCursorXY(orgx, orgy);
404 /* Print out what we have now */
405 ConOutPrintf(_T("%s"), str);
406
407 /* Move cursor accordingly */
408 if (tempscreen > charcount)
409 {
410 GetCursorXY(&curx, &cury);
411 for(count = tempscreen - charcount; count--; )
412 ConOutChar(_T(' '));
413 SetCursorXY(curx, cury);
414 }
415 else
416 {
417 if (((charcount + orgx) / maxx) + orgy > maxy - 1)
418 orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
419 }
420 SetCursorXY((short)(((int)orgx + current) % maxx), (short)((int)orgy + ((int)orgx + current) / maxx));
421 GetCursorXY(&curx, &cury);
422 #endif
423 break;
424
425 case _T('M'):
426 case _T('C'):
427 /* ^M does the same as return */
428 bCharInput = TRUE;
429 if (!(ir.Event.KeyEvent.dwControlKeyState &
430 (RIGHT_CTRL_PRESSED|LEFT_CTRL_PRESSED)))
431 {
432 break;
433 }
434
435 case VK_RETURN:
436 /* end input, return to main */
437 #ifdef FEATURE_HISTORY
438 /* add to the history */
439 if (str[0])
440 History (0, str);
441 #endif
442 str[charcount++] = _T('\n');
443 str[charcount] = _T('\0');
444 ConOutChar(_T('\n'));
445 bReturn = TRUE;
446 break;
447
448 case VK_ESCAPE:
449 /* clear str Make this callable! */
450 ClearCommandLine (str, maxlen, orgx, orgy);
451 curx = orgx;
452 cury = orgy;
453 current = charcount = 0;
454 break;
455
456 #ifdef FEATURE_HISTORY
457 case VK_F3:
458 History_move_to_bottom();
459 #endif
460 case VK_UP:
461 #ifdef FEATURE_HISTORY
462 /* get previous command from buffer */
463 ClearCommandLine (str, maxlen, orgx, orgy);
464 History (-1, str);
465 current = charcount = _tcslen (str);
466 if (((charcount + orgx) / maxx) + orgy > maxy - 1)
467 orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
468 ConOutPrintf (_T("%s"), str);
469 GetCursorXY (&curx, &cury);
470 #endif
471 break;
472
473 case VK_DOWN:
474 #ifdef FEATURE_HISTORY
475 /* get next command from buffer */
476 ClearCommandLine (str, maxlen, orgx, orgy);
477 History (1, str);
478 current = charcount = _tcslen (str);
479 if (((charcount + orgx) / maxx) + orgy > maxy - 1)
480 orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
481 ConOutPrintf (_T("%s"), str);
482 GetCursorXY (&curx, &cury);
483 #endif
484 break;
485
486 case VK_LEFT:
487 if (dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
488 {
489 /* move cursor to the previous word */
490 if (current > 0)
491 {
492 while (current > 0 && str[current - 1] == _T(' '))
493 {
494 current--;
495 if (curx == 0)
496 {
497 cury--;
498 curx = maxx -1;
499 }
500 else
501 {
502 curx--;
503 }
504 }
505
506 while (current > 0 && str[current -1] != _T(' '))
507 {
508 current--;
509 if (curx == 0)
510 {
511 cury--;
512 curx = maxx -1;
513 }
514 else
515 {
516 curx--;
517 }
518 }
519
520 SetCursorXY(curx, cury);
521 }
522 }
523 else
524 {
525 /* move cursor left */
526 if (current > 0)
527 {
528 current--;
529 if (GetCursorX () == 0)
530 {
531 SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
532 curx = maxx - 1;
533 cury--;
534 }
535 else
536 {
537 SetCursorXY ((SHORT)(GetCursorX () - 1), GetCursorY ());
538 curx--;
539 }
540 }
541 else
542 {
543 MessageBeep (-1);
544 }
545 }
546 break;
547
548 case VK_RIGHT:
549 if (dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
550 {
551 /* move cursor to the next word */
552 if (current != charcount)
553 {
554 while (current != charcount && str[current] != _T(' '))
555 {
556 current++;
557 if (curx == maxx - 1)
558 {
559 cury++;
560 curx = 0;
561 }
562 else
563 {
564 curx++;
565 }
566 }
567
568 while (current != charcount && str[current] == _T(' '))
569 {
570 current++;
571 if (curx == maxx - 1)
572 {
573 cury++;
574 curx = 0;
575 }
576 else
577 {
578 curx++;
579 }
580 }
581
582 SetCursorXY(curx, cury);
583 }
584 }
585 else
586 {
587 /* move cursor right */
588 if (current != charcount)
589 {
590 current++;
591 if (GetCursorX () == maxx - 1)
592 {
593 SetCursorXY (0, (SHORT)(GetCursorY () + 1));
594 curx = 0;
595 cury++;
596 }
597 else
598 {
599 SetCursorXY ((SHORT)(GetCursorX () + 1), GetCursorY ());
600 curx++;
601 }
602 }
603 #ifdef FEATURE_HISTORY
604 else
605 {
606 LPCTSTR last = PeekHistory(-1);
607 if (last && charcount < (INT)_tcslen (last))
608 {
609 PreviousChar = last[current];
610 ConOutChar(PreviousChar);
611 GetCursorXY(&curx, &cury);
612 str[current++] = PreviousChar;
613 charcount++;
614 }
615 }
616 #endif
617 }
618 break;
619
620 default:
621 /* This input is just a normal char */
622 bCharInput = TRUE;
623
624 }
625 #ifdef _UNICODE
626 ch = ir.Event.KeyEvent.uChar.UnicodeChar;
627 if (ch >= 32 && (charcount != (maxlen - 2)) && bCharInput)
628 #else
629 ch = ir.Event.KeyEvent.uChar.AsciiChar;
630 if ((UCHAR)ch >= 32 && (charcount != (maxlen - 2)) && bCharInput)
631 #endif /* _UNICODE */
632 {
633 /* insert character into string... */
634 if (bInsert && current != charcount)
635 {
636 /* If this character insertion will cause screen scrolling,
637 * adjust the saved origin of the command prompt. */
638 tempscreen = _tcslen(str + current) + curx;
639 if ((tempscreen % maxx) == (maxx - 1) &&
640 (tempscreen / maxx) + cury == (maxy - 1))
641 {
642 orgy--;
643 cury--;
644 }
645
646 for (count = charcount; count > current; count--)
647 str[count] = str[count - 1];
648 str[current++] = ch;
649 if (curx == maxx - 1)
650 curx = 0, cury++;
651 else
652 curx++;
653 ConOutPrintf (_T("%s"), &str[current - 1]);
654 SetCursorXY (curx, cury);
655 charcount++;
656 }
657 else
658 {
659 if (current == charcount)
660 charcount++;
661 str[current++] = ch;
662 if (GetCursorX () == maxx - 1 && GetCursorY () == maxy - 1)
663 orgy--, cury--;
664 if (GetCursorX () == maxx - 1)
665 curx = 0, cury++;
666 else
667 curx++;
668 ConOutChar (ch);
669 }
670 }
671
672 //wLastKey = ir.Event.KeyEvent.wVirtualKeyCode;
673 }
674 while (!bReturn);
675
676 SetCursorType (bInsert, TRUE);
677
678 #ifdef FEATURE_ALIASES
679 /* expand all aliases */
680 ExpandAlias (str, maxlen);
681 #endif /* FEATURE_ALIAS */
682 return TRUE;
683 }