reactos/base/shell/cmd/cmdinput.c
2021-07-23 22:03:48 +02:00

732 lines
26 KiB
C

/*
* CMDINPUT.C - handles command input (tab completion, history, etc.).
*
*
* History:
*
* 01/14/95 (Tim Norman)
* started.
*
* 08/08/95 (Matt Rains)
* i have cleaned up the source code. changes now bring this source
* into guidelines for recommended programming practice.
* i have added some constants to help making changes easier.
*
* 12/12/95 (Tim Norman)
* added findxy() function to get max x/y coordinates to display
* correctly on larger screens
*
* 12/14/95 (Tim Norman)
* fixed the Tab completion code that Matt Rains broke by moving local
* variables to a more global scope and forgetting to initialize them
* when needed
*
* 8/1/96 (Tim Norman)
* fixed a bug in tab completion that caused filenames at the beginning
* of the command-line to have their first letter truncated
*
* 9/1/96 (Tim Norman)
* fixed a silly bug using printf instead of fputs, where typing "%i"
* confused printf :)
*
* 6/14/97 (Steffan Kaiser)
* ctrl-break checking
*
* 6/7/97 (Marc Desrochers)
* recoded everything! now properly adjusts when text font is changed.
* removed findxy(), reposition(), and reprint(), as these functions
* were inefficient. added goxy() function as gotoxy() was buggy when
* the screen font was changed. the printf() problem with %i on the
* command line was fixed by doing printf("%s",str) instead of
* printf(str). Don't ask how I find em just be glad I do :)
*
* 7/12/97 (Tim Norman)
* Note: above changes preempted Steffan's ctrl-break checking.
*
* 7/7/97 (Marc Desrochers)
* rewrote a new findxy() because the new dir() used it. This
* findxy() simply returns the values of *maxx *maxy. In the
* future, please use the pointers, they will always be correct
* since they point to BIOS values.
*
* 7/8/97 (Marc Desrochers)
* once again removed findxy(), moved the *maxx, *maxy pointers
* global and included them as externs in command.h. Also added
* insert/overstrike capability
*
* 7/13/97 (Tim Norman)
* added different cursor appearance for insert/overstrike mode
*
* 7/13/97 (Tim Norman)
* changed my code to use _setcursortype until I can figure out why
* my code is crashing on some machines. It doesn't crash on mine :)
*
* 27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
* added config.h include
*
* 28-Jul-1998 (John P Price <linux-guru@gcfl.net>)
* put ifdef's around filename completion code.
*
* 30-Jul-1998 (John P Price <linux-guru@gcfl.net>)
* moved filename completion code to filecomp.c
* made second TAB display list of filename matches
*
* 31-Jul-1998 (John P Price <linux-guru@gcfl.net>)
* Fixed bug where if you typed something, then hit HOME, then tried
* to type something else in insert mode, it crashed.
*
* 07-Aug-1998 (John P Price <linux-guru@gcfl.net>)
* Fixed carriage return output to better match MSDOS with echo
* on or off.(marked with "JPP 19980708")
*
* 13-Dec-1998 (Eric Kohl)
* Added insert/overwrite cursor.
*
* 25-Jan-1998 (Eric Kohl)
* Replaced CRT io functions by Win32 console io functions.
* This can handle <Shift>-<Tab> for 4NT filename completion.
* Unicode and redirection safe!
*
* 04-Feb-1999 (Eric Kohl)
* Fixed input bug. A "line feed" character remained in the keyboard
* input queue when you pressed <RETURN>. This sometimes caused
* some very strange effects.
* Fixed some command line editing annoyances.
*
* 30-Apr-2004 (Filip Navara <xnavara@volny.cz>)
* Fixed problems when the screen was scrolled away.
*
* 28-September-2007 (Hervé Poussineau)
* Added history possibilities to right key.
*/
#include "precomp.h"
/*
* See https://technet.microsoft.com/en-us/library/cc978715.aspx
* and https://technet.microsoft.com/en-us/library/cc940805.aspx
* to know the differences between those two settings.
* Values 0x00, 0x0D (carriage return) and 0x20 (space) disable completion.
*/
TCHAR AutoCompletionChar = _T('\t'); // Default is 0x20
TCHAR PathCompletionChar = _T('\t'); // Default is 0x20
SHORT maxx;
SHORT maxy;
/*
* global command line insert/overwrite flag
*/
static BOOL bInsert = TRUE;
static VOID
ClearCommandLine(LPTSTR str, INT maxlen, SHORT orgx, SHORT orgy)
{
INT count;
SetCursorXY (orgx, orgy);
for (count = 0; count < (INT)_tcslen (str); count++)
ConOutChar (_T(' '));
_tcsnset (str, _T('\0'), maxlen);
SetCursorXY (orgx, orgy);
}
/* read in a command line */
BOOL ReadCommand(LPTSTR str, INT maxlen)
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
SHORT orgx; /* origin x/y */
SHORT orgy;
SHORT curx; /*current x/y cursor position*/
SHORT cury;
SIZE_T tempscreen;
INT count; /*used in some for loops*/
INT current = 0; /*the position of the cursor in the string (str)*/
INT charcount = 0;/*chars in the string (str)*/
INPUT_RECORD ir;
DWORD dwControlKeyState;
#ifdef FEATURE_UNIX_FILENAME_COMPLETION
WORD wLastKey = 0;
#endif
TCHAR ch;
BOOL bReturn = FALSE;
BOOL bCharInput;
#ifdef FEATURE_4NT_FILENAME_COMPLETION
TCHAR szPath[MAX_PATH];
#endif
#ifdef FEATURE_HISTORY
//BOOL bContinue=FALSE;/*is TRUE the second case will not be executed*/
TCHAR PreviousChar;
#endif
if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
{
/* No console */
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
DWORD dwRead;
CHAR chr;
do
{
if (!ReadFile(hStdin, &chr, 1, &dwRead, NULL) || !dwRead)
return FALSE;
#ifdef _UNICODE
MultiByteToWideChar(InputCodePage, 0, &chr, 1, &str[charcount++], 1);
#endif
} while (chr != '\n' && charcount < maxlen);
str[charcount] = _T('\0');
return TRUE;
}
/* get screen size */
maxx = csbi.dwSize.X;
maxy = csbi.dwSize.Y;
curx = orgx = csbi.dwCursorPosition.X;
cury = orgy = csbi.dwCursorPosition.Y;
memset (str, 0, maxlen * sizeof (TCHAR));
SetCursorType (bInsert, TRUE);
do
{
bReturn = FALSE;
ConInKey (&ir);
dwControlKeyState = ir.Event.KeyEvent.dwControlKeyState;
if (dwControlKeyState &
(RIGHT_ALT_PRESSED |LEFT_ALT_PRESSED|
RIGHT_CTRL_PRESSED|LEFT_CTRL_PRESSED) )
{
switch (ir.Event.KeyEvent.wVirtualKeyCode)
{
#ifdef FEATURE_HISTORY
case _T('K'):
/* add the current command line to the history */
if (dwControlKeyState &
(LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED))
{
if (str[0])
History(0,str);
ClearCommandLine (str, maxlen, orgx, orgy);
current = charcount = 0;
curx = orgx;
cury = orgy;
//bContinue=TRUE;
break;
}
case _T('D'):
/* delete current history entry */
if (dwControlKeyState &
(LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED))
{
ClearCommandLine (str, maxlen, orgx, orgy);
History_del_current_entry(str);
current = charcount = _tcslen (str);
ConOutPrintf (_T("%s"), str);
GetCursorXY (&curx, &cury);
//bContinue=TRUE;
break;
}
#endif /*FEATURE_HISTORY*/
case _T('M'):
/* ^M does the same as return */
if (dwControlKeyState &
(LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED))
{
/* end input, return to main */
#ifdef FEATURE_HISTORY
/* add to the history */
if (str[0])
History(0, str);
#endif /*FEATURE_HISTORY*/
str[charcount++] = _T('\n');
str[charcount] = _T('\0');
ConOutChar (_T('\n'));
bReturn = TRUE;
break;
}
}
}
bCharInput = FALSE;
switch (ir.Event.KeyEvent.wVirtualKeyCode)
{
case VK_BACK:
/* <BACKSPACE> - delete character to left of cursor */
if (current > 0 && charcount > 0)
{
if (current == charcount)
{
/* if at end of line */
str[current - 1] = _T('\0');
if (GetCursorX () != 0)
{
ConOutPrintf (_T("\b \b"));
curx--;
}
else
{
SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
ConOutChar (_T(' '));
SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
cury--;
curx = maxx - 1;
}
}
else
{
for (count = current - 1; count < charcount; count++)
str[count] = str[count + 1];
if (GetCursorX () != 0)
{
SetCursorXY ((SHORT)(GetCursorX () - 1), GetCursorY ());
curx--;
}
else
{
SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
cury--;
curx = maxx - 1;
}
GetCursorXY (&curx, &cury);
ConOutPrintf (_T("%s "), &str[current - 1]);
SetCursorXY (curx, cury);
}
charcount--;
current--;
}
break;
case VK_INSERT:
/* toggle insert/overstrike mode */
bInsert ^= TRUE;
SetCursorType (bInsert, TRUE);
break;
case VK_DELETE:
/* delete character under cursor */
if (current != charcount && charcount > 0)
{
for (count = current; count < charcount; count++)
str[count] = str[count + 1];
charcount--;
GetCursorXY (&curx, &cury);
ConOutPrintf (_T("%s "), &str[current]);
SetCursorXY (curx, cury);
}
break;
case VK_HOME:
/* goto beginning of string */
if (current != 0)
{
SetCursorXY (orgx, orgy);
curx = orgx;
cury = orgy;
current = 0;
}
break;
case VK_END:
/* goto end of string */
if (current != charcount)
{
SetCursorXY (orgx, orgy);
ConOutPrintf (_T("%s"), str);
GetCursorXY (&curx, &cury);
current = charcount;
}
break;
case VK_TAB:
#ifdef FEATURE_UNIX_FILENAME_COMPLETION
/* expand current file name */
if ((current == charcount) ||
(current == charcount - 1 &&
str[current] == _T('"'))) /* only works at end of line*/
{
if (wLastKey != VK_TAB)
{
/* if first TAB, complete filename*/
tempscreen = charcount;
CompleteFilename (str, charcount);
charcount = _tcslen (str);
current = charcount;
SetCursorXY (orgx, orgy);
ConOutPrintf (_T("%s"), str);
if (tempscreen > charcount)
{
GetCursorXY (&curx, &cury);
for (count = tempscreen - charcount; count--; )
ConOutChar (_T(' '));
SetCursorXY (curx, cury);
}
else
{
if (((charcount + orgx) / maxx) + orgy > maxy - 1)
orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
}
/* set cursor position */
SetCursorXY ((orgx + current) % maxx,
orgy + (orgx + current) / maxx);
GetCursorXY (&curx, &cury);
}
else
{
/*if second TAB, list matches*/
if (ShowCompletionMatches (str, charcount))
{
PrintPrompt();
GetCursorXY(&orgx, &orgy);
ConOutPrintf(_T("%s"), str);
/* set cursor position */
SetCursorXY((orgx + current) % maxx,
orgy + (orgx + current) / maxx);
GetCursorXY(&curx, &cury);
}
}
}
else
{
MessageBeep(-1);
}
#endif
#ifdef FEATURE_4NT_FILENAME_COMPLETION
/* used to later see if we went down to the next line */
tempscreen = charcount;
szPath[0]=_T('\0');
/* str is the whole things that is on the current line
that is and and out. arg 2 is weather it goes back
one file or forward one file */
CompleteFilename(str, !(ir.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED), szPath, current);
/* Attempt to clear the line */
ClearCommandLine (str, maxlen, orgx, orgy);
curx = orgx;
cury = orgy;
current = charcount = 0;
/* Everything is deleted, lets add it back in */
_tcscpy(str,szPath);
/* Figure out where cusor is going to be after we print it */
charcount = _tcslen(str);
current = charcount;
SetCursorXY(orgx, orgy);
/* Print out what we have now */
ConOutPrintf(_T("%s"), str);
/* Move cursor accordingly */
if (tempscreen > charcount)
{
GetCursorXY(&curx, &cury);
for(count = tempscreen - charcount; count--; )
ConOutChar(_T(' '));
SetCursorXY(curx, cury);
}
else
{
if (((charcount + orgx) / maxx) + orgy > maxy - 1)
orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
}
SetCursorXY((short)(((int)orgx + current) % maxx), (short)((int)orgy + ((int)orgx + current) / maxx));
GetCursorXY(&curx, &cury);
#endif
break;
case _T('C'):
if ((ir.Event.KeyEvent.dwControlKeyState &
(RIGHT_CTRL_PRESSED|LEFT_CTRL_PRESSED)))
{
/* Ignore the Ctrl-C key event if it has already been handled */
if (!bCtrlBreak)
break;
/*
* Fully print the entered string
* so the command prompt would not overwrite it.
*/
SetCursorXY(orgx, orgy);
ConOutPrintf(_T("%s"), str);
/*
* A Ctrl-C. Do not clear the command line,
* but return an empty string in str.
*/
str[0] = _T('\0');
curx = orgx;
cury = orgy;
current = charcount = 0;
bReturn = TRUE;
}
else
{
/* Just a normal 'C' character */
bCharInput = TRUE;
}
break;
case VK_RETURN:
/* end input, return to main */
#ifdef FEATURE_HISTORY
/* add to the history */
if (str[0])
History(0, str);
#endif
str[charcount++] = _T('\n');
str[charcount] = _T('\0');
ConOutChar(_T('\n'));
bReturn = TRUE;
break;
case VK_ESCAPE:
/* clear str Make this callable! */
ClearCommandLine (str, maxlen, orgx, orgy);
curx = orgx;
cury = orgy;
current = charcount = 0;
break;
#ifdef FEATURE_HISTORY
case VK_F3:
History_move_to_bottom();
#endif
case VK_UP:
#ifdef FEATURE_HISTORY
/* get previous command from buffer */
ClearCommandLine (str, maxlen, orgx, orgy);
History(-1, str);
current = charcount = _tcslen (str);
if (((charcount + orgx) / maxx) + orgy > maxy - 1)
orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
ConOutPrintf (_T("%s"), str);
GetCursorXY (&curx, &cury);
#endif
break;
case VK_DOWN:
#ifdef FEATURE_HISTORY
/* get next command from buffer */
ClearCommandLine (str, maxlen, orgx, orgy);
History(1, str);
current = charcount = _tcslen (str);
if (((charcount + orgx) / maxx) + orgy > maxy - 1)
orgy += maxy - ((charcount + orgx) / maxx + orgy + 1);
ConOutPrintf (_T("%s"), str);
GetCursorXY (&curx, &cury);
#endif
break;
case VK_LEFT:
if (dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
{
/* move cursor to the previous word */
if (current > 0)
{
while (current > 0 && str[current - 1] == _T(' '))
{
current--;
if (curx == 0)
{
cury--;
curx = maxx -1;
}
else
{
curx--;
}
}
while (current > 0 && str[current -1] != _T(' '))
{
current--;
if (curx == 0)
{
cury--;
curx = maxx -1;
}
else
{
curx--;
}
}
SetCursorXY(curx, cury);
}
}
else
{
/* move cursor left */
if (current > 0)
{
current--;
if (GetCursorX () == 0)
{
SetCursorXY ((SHORT)(maxx - 1), (SHORT)(GetCursorY () - 1));
curx = maxx - 1;
cury--;
}
else
{
SetCursorXY ((SHORT)(GetCursorX () - 1), GetCursorY ());
curx--;
}
}
else
{
MessageBeep (-1);
}
}
break;
case VK_RIGHT:
if (dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
{
/* move cursor to the next word */
if (current != charcount)
{
while (current != charcount && str[current] != _T(' '))
{
current++;
if (curx == maxx - 1)
{
cury++;
curx = 0;
}
else
{
curx++;
}
}
while (current != charcount && str[current] == _T(' '))
{
current++;
if (curx == maxx - 1)
{
cury++;
curx = 0;
}
else
{
curx++;
}
}
SetCursorXY(curx, cury);
}
}
else
{
/* move cursor right */
if (current != charcount)
{
current++;
if (GetCursorX () == maxx - 1)
{
SetCursorXY (0, (SHORT)(GetCursorY () + 1));
curx = 0;
cury++;
}
else
{
SetCursorXY ((SHORT)(GetCursorX () + 1), GetCursorY ());
curx++;
}
}
#ifdef FEATURE_HISTORY
else
{
LPCTSTR last = PeekHistory(-1);
if (last && charcount < (INT)_tcslen (last))
{
PreviousChar = last[current];
ConOutChar(PreviousChar);
GetCursorXY(&curx, &cury);
str[current++] = PreviousChar;
charcount++;
}
}
#endif
}
break;
default:
/* This input is just a normal char */
bCharInput = TRUE;
}
#ifdef _UNICODE
ch = ir.Event.KeyEvent.uChar.UnicodeChar;
if (ch >= 32 && (charcount != (maxlen - 2)) && bCharInput)
#else
ch = ir.Event.KeyEvent.uChar.AsciiChar;
if ((UCHAR)ch >= 32 && (charcount != (maxlen - 2)) && bCharInput)
#endif /* _UNICODE */
{
/* insert character into string... */
if (bInsert && current != charcount)
{
/* If this character insertion will cause screen scrolling,
* adjust the saved origin of the command prompt. */
tempscreen = _tcslen(str + current) + curx;
if ((tempscreen % maxx) == (maxx - 1) &&
(tempscreen / maxx) + cury == (maxy - 1))
{
orgy--;
cury--;
}
for (count = charcount; count > current; count--)
str[count] = str[count - 1];
str[current++] = ch;
if (curx == maxx - 1)
curx = 0, cury++;
else
curx++;
ConOutPrintf (_T("%s"), &str[current - 1]);
SetCursorXY (curx, cury);
charcount++;
}
else
{
if (current == charcount)
charcount++;
str[current++] = ch;
if (GetCursorX () == maxx - 1 && GetCursorY () == maxy - 1)
orgy--, cury--;
if (GetCursorX () == maxx - 1)
curx = 0, cury++;
else
curx++;
ConOutChar (ch);
}
}
//wLastKey = ir.Event.KeyEvent.wVirtualKeyCode;
}
while (!bReturn);
SetCursorType (bInsert, TRUE);
#ifdef FEATURE_ALIASES
/* expand all aliases */
ExpandAlias (str, maxlen);
#endif /* FEATURE_ALIAS */
return TRUE;
}