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