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