CMD Enhancements:
[reactos.git] / reactos / subsys / system / 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 <ekohl@abo.rhein-zeitung.de>)
83 * Added insert/overwrite cursor.
84 *
85 * 25-Jan-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
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 <ekohl@abo.rhein-zeitung.de>)
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
100 #include "config.h"
101
102 #include <windows.h>
103 #include <tchar.h>
104 #include <string.h>
105
106 #include "cmd.h"
107 #include "batch.h"
108
109
110 SHORT maxx;
111 SHORT maxy;
112
113 /*
114 * global command line insert/overwrite flag
115 */
116 static BOOL bInsert = TRUE;
117
118
119 static VOID
120 ClearCommandLine (LPTSTR str, INT maxlen, SHORT orgx, SHORT orgy)
121 {
122 INT count;
123
124 SetCursorXY (orgx, orgy);
125 for (count = 0; count < (INT)_tcslen (str); count++)
126 ConOutChar (_T(' '));
127 _tcsnset (str, _T('\0'), maxlen);
128 SetCursorXY (orgx, orgy);
129 }
130
131
132 /* read in a command line */
133 VOID ReadCommand (LPTSTR str, INT maxlen)
134 {
135 SHORT orgx; /* origin x/y */
136 SHORT orgy;
137 SHORT curx; /*current x/y cursor position*/
138 SHORT cury;
139 SHORT tempscreen;
140 INT count; /*used in some for loops*/
141 INT current = 0; /*the position of the cursor in the string (str)*/
142 INT charcount = 0;/*chars in the string (str)*/
143 INPUT_RECORD ir;
144 WORD wLastKey = 0;
145 TCHAR ch;
146 BOOL bContinue=FALSE;/*is TRUE the second case will not be executed*/
147
148 /* get screen size */
149 GetScreenSize (&maxx, &maxy);
150
151 /* JPP 19980807 - if echo off, don't print prompt */
152 if (bEcho)
153 PrintPrompt();
154
155 GetCursorXY (&orgx, &orgy);
156 GetCursorXY (&curx, &cury);
157
158 memset (str, 0, maxlen * sizeof (TCHAR));
159
160 SetCursorType (bInsert, TRUE);
161
162 do
163 {
164 ConInKey (&ir);
165
166 if (ir.Event.KeyEvent.dwControlKeyState &
167 (RIGHT_ALT_PRESSED|RIGHT_ALT_PRESSED|
168 RIGHT_CTRL_PRESSED|LEFT_CTRL_PRESSED) )
169 {
170
171 switch (ir.Event.KeyEvent.wVirtualKeyCode)
172 {
173
174 #ifdef FEATURE_HISTORY
175
176 case 'K':
177 /*add the current command line to the history*/
178 if (ir.Event.KeyEvent.dwControlKeyState &
179 (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED))
180 {
181
182 if (str[0])
183 History(0,str);
184
185 ClearCommandLine (str, maxlen, orgx, orgy);
186 current = charcount = 0;
187 curx = orgx;
188 cury = orgy;
189 bContinue=TRUE;
190 break;
191 }
192
193 case 'D':
194 /*delete current history entry*/
195 if (ir.Event.KeyEvent.dwControlKeyState &
196 (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED))
197 {
198 ClearCommandLine (str, maxlen, orgx, orgy);
199 History_del_current_entry(str);
200 current = charcount = _tcslen (str);
201 ConOutPrintf (_T("%s"), str);
202 GetCursorXY (&curx, &cury);
203 bContinue=TRUE;
204 break;
205 }
206
207 #endif/*FEATURE_HISTORY*/
208 }
209
210
211
212
213 }
214
215 //if (bContinue)
216 // continue;
217
218
219
220 switch (ir.Event.KeyEvent.wVirtualKeyCode)
221 {
222 case VK_BACK:
223 /* <BACKSPACE> - delete character to left of cursor */
224 if (current > 0 && charcount > 0)
225 {
226 if (current == charcount)
227 {
228 /* if at end of line */
229 str[current - 1] = _T('\0');
230 if (GetCursorX () != 0)
231 {
232 ConOutPrintf (_T("\b \b"));
233 curx--;
234 }
235 else
236 {
237 SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
238 ConOutChar (_T(' '));
239 SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
240 cury--;
241 curx = maxx - 1;
242 }
243 }
244 else
245 {
246 for (count = current - 1; count < charcount; count++)
247 str[count] = str[count + 1];
248 if (GetCursorX () != 0)
249 {
250 SetCursorXY ((SHORT)(GetCursorX () - 1), GetCursorY ());
251 curx--;
252 }
253 else
254 {
255 SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
256 cury--;
257 curx = maxx - 1;
258 }
259 GetCursorXY (&curx, &cury);
260 ConOutPrintf (_T("%s "), &str[current - 1]);
261 SetCursorXY (curx, cury);
262 }
263 charcount--;
264 current--;
265 }
266 break;
267
268 case VK_INSERT:
269 /* toggle insert/overstrike mode */
270 bInsert ^= TRUE;
271 SetCursorType (bInsert, TRUE);
272 break;
273
274 case VK_DELETE:
275 /* delete character under cursor */
276 if (current != charcount && charcount > 0)
277 {
278 for (count = current; count < charcount; count++)
279 str[count] = str[count + 1];
280 charcount--;
281 GetCursorXY (&curx, &cury);
282 ConOutPrintf (_T("%s "), &str[current]);
283 SetCursorXY (curx, cury);
284 }
285 break;
286
287 case VK_HOME:
288 /* goto beginning of string */
289 if (current != 0)
290 {
291 SetCursorXY (orgx, orgy);
292 curx = orgx;
293 cury = orgy;
294 current = 0;
295 }
296 break;
297
298 case VK_END:
299 /* goto end of string */
300 if (current != charcount)
301 {
302 SetCursorXY (orgx, orgy);
303 ConOutPrintf (_T("%s"), str);
304 GetCursorXY (&curx, &cury);
305 current = charcount;
306 }
307 break;
308
309 case VK_TAB:
310 #ifdef FEATURE_UNIX_FILENAME_COMPLETION
311 /* expand current file name */
312 if ((current == charcount) ||
313 (current == charcount - 1 &&
314 str[current] == _T('"'))) /* only works at end of line*/
315 {
316 if (wLastKey != VK_TAB)
317 {
318 /* if first TAB, complete filename*/
319 tempscreen = charcount;
320 CompleteFilename (str, charcount);
321 charcount = _tcslen (str);
322 current = charcount;
323
324 if (current > 0 &&
325 str[current-1] == _T('"'))
326 current--;
327
328 SetCursorXY (orgx, orgy);
329 ConOutPrintf (_T("%s"), str);
330
331 if (tempscreen > charcount)
332 {
333 GetCursorXY (&curx, &cury);
334 for (count = tempscreen - charcount; count--; )
335 ConOutChar (_T(' '));
336 SetCursorXY (curx, cury);
337 }
338 else
339 {
340 if (((charcount + orgx) / maxx) + orgy > maxy - 1)
341 orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
342 }
343
344 /* set cursor position */
345 SetCursorXY ((orgx + current) % maxx,
346 orgy + (orgx + current) / maxx);
347 GetCursorXY (&curx, &cury);
348 }
349 else
350 {
351 /*if second TAB, list matches*/
352 if (ShowCompletionMatches (str, charcount))
353 {
354 PrintPrompt ();
355 GetCursorXY (&orgx, &orgy);
356 ConOutPrintf (_T("%s"), str);
357
358 /* set cursor position */
359 SetCursorXY ((orgx + current) % maxx,
360 orgy + (orgx + current) / maxx);
361 GetCursorXY (&curx, &cury);
362 }
363
364 }
365 }
366 else
367 {
368 #ifdef __REACTOS__
369 Beep (440, 50);
370 #else
371 MessageBeep (-1);
372 #endif
373 }
374 #endif
375 #ifdef FEATURE_4NT_FILENAME_COMPLETION
376 /* this is not implemented yet */
377 if (ir.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED)
378 {
379 /* get previous match */
380
381 }
382 else
383 {
384 /* get next match */
385
386 }
387 #endif
388 break;
389
390 case VK_RETURN:
391 /* end input, return to main */
392 #ifdef FEATURE_HISTORY
393 /* add to the history */
394 if (str[0])
395 History (0, str);
396 #endif
397 ConInDummy ();
398 ConOutChar (_T('\n'));
399 break;
400
401 case VK_ESCAPE:
402 /* clear str Make this callable! */
403 ClearCommandLine (str, maxlen, orgx, orgy);
404 curx = orgx;
405 cury = orgy;
406 current = charcount = 0;
407 break;
408
409 #ifdef FEATURE_HISTORY
410 case VK_F3:
411 History_move_to_bottom();
412 #endif
413 case VK_UP:
414 #ifdef FEATURE_HISTORY
415 /* get previous command from buffer */
416 ClearCommandLine (str, maxlen, orgx, orgy);
417 History (-1, str);
418 current = charcount = _tcslen (str);
419 if (((charcount + orgx) / maxx) + orgy > maxy - 1)
420 orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
421 ConOutPrintf (_T("%s"), str);
422 GetCursorXY (&curx, &cury);
423 #endif
424 break;
425
426 case VK_DOWN:
427 #ifdef FEATURE_HISTORY
428 /* get next command from buffer */
429 ClearCommandLine (str, maxlen, orgx, orgy);
430 History (1, str);
431 current = charcount = _tcslen (str);
432 if (((charcount + orgx) / maxx) + orgy > maxy - 1)
433 orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
434 ConOutPrintf (_T("%s"), str);
435 GetCursorXY (&curx, &cury);
436 #endif
437 break;
438
439 case VK_LEFT:
440 /* move cursor left */
441 if (current > 0)
442 {
443 current--;
444 if (GetCursorX () == 0)
445 {
446 SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
447 curx = maxx - 1;
448 cury--;
449 }
450 else
451 {
452 SetCursorXY ((SHORT)(GetCursorX () - 1), GetCursorY ());
453 curx--;
454 }
455 }
456 else
457 {
458 #ifdef __REACTOS__
459 Beep (440, 50);
460 #else
461 MessageBeep (-1);
462 #endif
463 }
464 break;
465
466 case VK_RIGHT:
467 /* move cursor right */
468 if (current != charcount)
469 {
470 current++;
471 if (GetCursorX () == maxx - 1)
472 {
473 SetCursorXY (0, (SHORT)(GetCursorY () + 1));
474 curx = 0;
475 cury++;
476 }
477 else
478 {
479 SetCursorXY ((SHORT)(GetCursorX () + 1), GetCursorY ());
480 curx++;
481 }
482 }
483 break;
484
485 default:
486 #ifdef _UNICODE
487 ch = ir.Event.KeyEvent.uChar.UnicodeChar;
488 if ((ch >= 32 && ch <= 255) && (charcount != (maxlen - 2)))
489 #else
490 ch = ir.Event.KeyEvent.uChar.AsciiChar;
491 if ((UCHAR)ch >= 32 && (charcount != (maxlen - 2)))
492 #endif /* _UNICODE */
493 {
494 /* insert character into string... */
495 if (bInsert && current != charcount)
496 {
497 /* If this character insertion will cause screen scrolling,
498 * adjust the saved origin of the command prompt. */
499 tempscreen = _tcslen(str + current) + curx;
500 if ((tempscreen % maxx) == (maxx - 1) &&
501 (tempscreen / maxx) + cury == (maxy - 1))
502 {
503 orgy--;
504 cury--;
505 }
506
507 for (count = charcount; count > current; count--)
508 str[count] = str[count - 1];
509 str[current++] = ch;
510 if (curx == maxx - 1)
511 curx = 0, cury++;
512 else
513 curx++;
514 ConOutPrintf (_T("%s"), &str[current - 1]);
515 SetCursorXY (curx, cury);
516 charcount++;
517 }
518 else
519 {
520 if (current == charcount)
521 charcount++;
522 str[current++] = ch;
523 if (GetCursorX () == maxx - 1 && GetCursorY () == maxy - 1)
524 orgy--, cury--;
525 if (GetCursorX () == maxx - 1)
526 curx = 0, cury++;
527 else
528 curx++;
529 ConOutChar (ch);
530 }
531 }
532 #if 0
533 else
534 {
535 #ifdef __REACTOS__
536 Beep (440, 100);
537 #else
538 MessageBeep (-1);
539 #endif
540 }
541 #endif
542 break;
543
544 }
545 wLastKey = ir.Event.KeyEvent.wVirtualKeyCode;
546 }
547 while (ir.Event.KeyEvent.wVirtualKeyCode != VK_RETURN);
548
549 SetCursorType (bInsert, TRUE);
550 }