NT4/private/windows/cmd/readcon.c
2020-09-30 17:12:29 +02:00

710 lines
18 KiB
C

///////////////////////////////////////////////////////////////
//
// ReadCon.cpp
//
//
// ReadConsole implementation for Win95 that implements the command line editing
// keys, since pathetic Win95 console implementation does not.
//
#include "cmd.h"
#ifdef WIN95_CMD
// adv declarations....
extern BOOLEAN CtrlCSeen;
extern BOOL bInsertDefault;
void ShowBuf( TCHAR* pBuf, int nFromPos );
// some state variables.....
static int nConsoleWidth; // num columns
static int nConsoleHeight; // num rows
static COORD coordStart; // coord of first cmd char....
static COORD coordEnd; // coord of last cmd char....
static int nBufPos = 0; // buffer cursor position
static int nBufLen = 0; // length of current command
static BOOL bInsert; // insert mode
static HANDLE hOut; // output buffer handle
static TCHAR* pPrompt; // allocated buffer to store prompt
static int nPromptSize; // num chars in prompt buffer
static WORD wDefAttr; // default character attribute
static int nState = 0; // input state
static TCHAR history[50][2049]; // history list
static int nFirstCmd = -1; // index of first cmd
static int nLastCmd = -1; // index of last command entered
static int nHistNdx = -1; // index into history list....
static TCHAR* pSearchStr = 0; // search criteria buffer (allocated)
void
IncCoord(
COORD* coord,
int nDelta
)
{
coord->X += nDelta;
if ( coord->X < 0 )
{
coord->Y += coord->X/nConsoleWidth - 1;
coord->X = nConsoleWidth - (-coord->X)%nConsoleWidth;
}
else
if ( coord->X >= nConsoleWidth )
{
coord->Y += coord->X / nConsoleWidth;
coord->X %= nConsoleWidth;
}
}
void
GetOffsetCoord(
TCHAR* pBuf,
int nOffset,
COORD* coord
)
{
int ndx;
// let's walk the buffer to find the correct coord....
*coord = coordStart;
for( ndx=0; ndx<nOffset; ++ndx )
{
if ( pBuf[ndx] == 9 )
{
int nTab;
for ( nTab=1; (nTab+coord->X)%8 ; ++nTab );
coord->X += nTab-1;
}
else
if ( pBuf[ndx] < 32 )
++coord->X;
IncCoord( coord, +1 );
}
}
int
ScrollTo(
TCHAR* pBuf,
COORD* coord
)
{
int nLines = 0;
SMALL_RECT rectFrom;
COORD coordTo;
CHAR_INFO cBlank;
// get some data....
cBlank.Char.AsciiChar = ' ';
cBlank.Attributes = wDefAttr;
coordTo.X = 0;
rectFrom.Left = 0;
rectFrom.Right = nConsoleWidth - 1;
// scroll it....
if ( coord->Y < 0 )
{
// scroll down....
nLines = coord->Y;
rectFrom.Top = 0;
rectFrom.Bottom = nConsoleHeight + nLines - 1;
coordTo.Y = -nLines;
ScrollConsoleScreenBuffer( hOut, &rectFrom, NULL, coordTo, &cBlank );
}
else
{
// scroll up....
nLines = coord->Y - nConsoleHeight + 1;
rectFrom.Top = nLines;
rectFrom.Bottom = nConsoleHeight - 1;
coordTo.Y = 0;
ScrollConsoleScreenBuffer( hOut, &rectFrom, NULL, coordTo, &cBlank );
}
// adjust start/end coords and orig coord to reflect new scroll....
coordStart.Y -= nLines;
coordEnd.Y -= nLines;
coord->Y -= nLines;
// redraw the whole command AND the prompt.....
ShowBuf( pBuf, -1 );
return nLines;
}
void
AdjCursor(
TCHAR* pBuf
)
{
COORD coordCursor;
GetOffsetCoord( pBuf, nBufPos, &coordCursor );
if ( coordCursor.Y < 0 || coordCursor.Y >= nConsoleHeight )
{
// scroll it....
ScrollTo( pBuf, &coordCursor );
}
SetConsoleCursorPosition( hOut, coordCursor );
}
void
AdjCursorSize( void )
{
CONSOLE_CURSOR_INFO cci;
cci.bVisible = TRUE;
if ( bInsert == bInsertDefault )
cci.dwSize = 13;
else
cci.dwSize = 40;
SetConsoleCursorInfo( hOut, &cci );
}
void
AdvancePos(
TCHAR* pBuf,
int nDelta,
DWORD dwKeyState
)
{
// see if control key down....
if ( (dwKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)) != 0 )
{
if ( nDelta > 0 )
{ // skip to NEXT word....
while ( nBufPos < nBufLen && !isspace(pBuf[nBufPos]) )
++nBufPos;
while ( nBufPos < nBufLen && isspace(pBuf[nBufPos]) )
++nBufPos;
}
else
{ // skip to PREVIOUS word....
// if already at beginning of word, back up one....
if ( nBufPos > 0 && isspace(pBuf[nBufPos-1]) )
--nBufPos;
// skip white space....
while ( nBufPos > 0 && isspace(pBuf[nBufPos]) )
--nBufPos;
// skip non-ws....
while ( nBufPos > 0 && !isspace(pBuf[nBufPos-1]) )
--nBufPos;
}
}
else
nBufPos += nDelta;
AdjCursor( pBuf );
}
void
ShowBuf(
TCHAR* pBuf,
int nFromPos
)
{
DWORD cPrint;
COORD coord, cPrompt;
TCHAR temp[8];
int ndx;
// see if we want the prompt, too....
if ( nFromPos < 0 )
{
nFromPos = 0;
GetOffsetCoord( pBuf, 0, &coord );
cPrompt = coord;
cPrompt.X -= nPromptSize;
WriteConsoleOutputCharacter( hOut, pPrompt, nPromptSize, cPrompt, &cPrint );
}
else
GetOffsetCoord( pBuf, nFromPos, &coord );
// walk the rest of the buffer, displaying as we go....
for( ndx=nFromPos;ndx < nBufLen; ++ndx )
{
if ( pBuf[ndx] == 9 )
{
int nTab;
temp[0] = TEXT(' ');
for ( nTab=1; (nTab+coord.X)%8; ++nTab )
{
temp[nTab] = TEXT(' ');
}
WriteConsoleOutputCharacter( hOut, temp, nTab, coord, &cPrint );
coord.X += nTab-1;
}
else if ( pBuf[ndx] < 32 )
{
temp[0] = '^';
temp[1] = pBuf[ndx] + 'A' - 1;
WriteConsoleOutputCharacter( hOut, temp, 2, coord, &cPrint );
++coord.X;
}
else
WriteConsoleOutputCharacter( hOut, pBuf + ndx, 1, coord, &cPrint );
// advance cursor.....
IncCoord( &coord, +1 );
}
// now blank out the rest.....
temp[0] = TEXT(' ');
while ( coordEnd.Y > coord.Y || (coordEnd.Y == coord.Y && coordEnd.X >= coord.X) )
{
WriteConsoleOutputCharacter( hOut, temp, 1, coordEnd, &cPrint );
IncCoord( &coordEnd, -1 );
}
// save the new ending....
coordEnd = coord;
}
void
ShiftBuffer(
TCHAR* pBuf,
int cch,
int nFrom,
int nDelta
)
{
int ndx;
// which direction?
if ( nDelta > 0 )
{
// move all (including this character) out one place....
for( ndx = nBufLen; ndx > nFrom; --ndx )
pBuf[ndx] = pBuf[ndx-1];
++nBufLen;
}
else if ( nDelta < 0 )
{
// move all characters in one place (over this character)...
for( ndx = nFrom; ndx < nBufLen; ++ ndx )
pBuf[ndx] = pBuf[ndx+1];
--nBufLen;
}
}
void
LoadHistory(
TCHAR* pBuf,
int cch,
TCHAR* pHist
)
{
// first go to the front of the line....
nBufPos = 0;
AdjCursor( pBuf );
// then blank-out the current buffer....
if ( nBufLen )
{
nBufLen = 0;
ShowBuf( pBuf, 0 );
}
// now copy in the new one (if there is one)....
if ( pHist )
{
_tcsncpy( pBuf, pHist, cch );
nBufLen = _tcslen( pHist );
// move to the end of the line....
nBufPos = nBufLen;
AdjCursor( pBuf );
// show the whole buffer from the start....
ShowBuf( pBuf, 0 );
}
}
void
SearchHist(
TCHAR* pBuf,
int cch
)
{
int ndx, nSearch, nStop;
// if we're in input mode, set ndx to AFTER last command....
if ( nState == 0 )
ndx = (nLastCmd+1)%50;
else
ndx = nHistNdx;
nStop = ndx; // don't search past here
// if not already in search mode, get a copy of the target....
if ( nState != 4 )
{
// if there already is a search string, get rid of it....
if ( pSearchStr )
free( pSearchStr );
// pSearchStr and copy a new search string....
pSearchStr = calloc( nBufLen+1, sizeof(TCHAR) );
_tcsncpy( pSearchStr, pBuf, nBufLen );
pSearchStr[nBufLen] = 0;
}
nSearch = _tcslen( pSearchStr );
// enter search mode....
nState = 4;
do
{
// back up one cmd....
ndx = (ndx+49)%50;
// check it....
if ( _tcsncmp( history[ndx], pSearchStr, nSearch ) == 0 )
{
// found a match....
nHistNdx = ndx;
LoadHistory( pBuf, cch, history[ndx] );
break;
}
} while ( ndx != nStop );
}
int touched = 1;
BOOL
ProcessKey(
const KEY_EVENT_RECORD* keyEvent,
TCHAR* pBuf,
int cch,
DWORD dwCtrlWakeupMask,
PBOOL bWakeupKeyHit
)
{
BOOL bDone = FALSE;
TCHAR ch;
*bWakeupKeyHit = FALSE;
ch = keyEvent->uChar.AsciiChar;
if ( keyEvent->wVirtualKeyCode == VK_SPACE )
ch = TEXT(' ');
switch ( keyEvent->wVirtualKeyCode )
{
case VK_RETURN:
bDone = TRUE;
// move cursor to end of cmd, if not already there....
if ( nBufPos != nBufLen )
{
nBufPos = nBufLen;
AdjCursor( pBuf );
}
// add the NLN to end of cmd....
pBuf[nBufLen] = _T('\n');
++nBufLen;
touched = 1;
break;
case VK_BACK:
if ( nBufPos > 0 )
{
// return to input state....
nState = 0;
// back up the cursor....
AdvancePos( pBuf, -1, 0 );
// shift over this character and print....
ShiftBuffer( pBuf, cch, nBufPos, -1 );
ShowBuf( pBuf, nBufPos );
touched = 1;
}
break;
case VK_END:
if ( nBufPos != nBufLen )
{
// doesn't affect state....
nBufPos = nBufLen;
AdjCursor( pBuf );
}
break;
case VK_HOME:
if ( nBufPos )
{
// doesn't affect state....
nBufPos = 0;
AdjCursor( pBuf );
}
break;
case VK_LEFT:
// doesn't affect state....
if ( nBufPos > 0 )
AdvancePos( pBuf, -1, keyEvent->dwControlKeyState );
break;
case VK_RIGHT:
// doesn't affect state....
if ( nBufPos < nBufLen )
AdvancePos( pBuf, +1, keyEvent->dwControlKeyState );
break;
case VK_INSERT:
// doesn't affect state....
bInsert = !bInsert;
AdjCursorSize();
break;
case VK_DELETE:
if ( nBufPos < nBufLen )
{
// fall back to input state....
nState = 0;
// shift over this character and print....
ShiftBuffer( pBuf, cch, nBufPos, -1 );
ShowBuf( pBuf, nBufPos );
touched = 1;
}
break;
case VK_F8:
// if there's something to match....
if ( nBufLen != 0 )
{
// if we're not already at the top of the list....
//if ( nHistNdx != nFirstCmd )
//{
// search backwards up the list....
SearchHist( pBuf, cch );
//}
touched = 1;
break;
}
// fall through if there's nothing to match with....
// same as pressing up arrow....
case VK_UP:
// if we're not already at the top of the list....
if ( nState == 0 || nHistNdx != nFirstCmd )
{
if ( nState == 0 )
nHistNdx = nLastCmd;
else
nHistNdx = (nHistNdx+49)%50;
LoadHistory( pBuf, cch, history[nHistNdx] );
// scrolling through history....
nState = 2;
touched = 1;
}
break;
case VK_DOWN:
if ( nState == 0 || nHistNdx == nLastCmd )
{
// blank out command buffer...
LoadHistory( pBuf, cch, NULL );
// return to input state....
nState = 0;
}
else
{
// get the next one....
nHistNdx = (nHistNdx+1)%50;
LoadHistory( pBuf, cch, history[nHistNdx] );
// scrolling through history....
nState = 2;
}
touched = 1;
break;
case VK_ESCAPE:
// blank-out the command buffer....
LoadHistory( pBuf, cch, NULL );
// return to input....
nState = 0;
touched = 1;
break;
default:
// if printable character, let's add it in....
if ( ch >= 1 && ch <= 255 )
{
touched = 1;
// fall back to input state....
nState = 0;
// see if there's room....
if ( nBufPos >= cch || (bInsert && nBufLen >= cch) )
MessageBeep( MB_ICONEXCLAMATION );
else
{
if ( bInsert )
{
// shift the buffer out for the insert....
ShiftBuffer( pBuf, cch, nBufPos, +1 );
}
else
if ( nBufPos >= nBufLen )
++nBufLen;
// place the character in the buffer at the current pos....
pBuf[nBufPos] = ch;
if (ch < TEXT(' ') && (dwCtrlWakeupMask & (1 << ch))) {
*bWakeupKeyHit = TRUE;
AdjCursor(pBuf);
bDone = TRUE;
} else {
// show from this position on....
ShowBuf( pBuf, nBufPos );
// advance position/cursor....
AdvancePos( pBuf, +1, 0 );
}
}
}
}
return bDone;
}
static UINT nOldIndex =0;
static DWORD cRead = 0;
static INPUT_RECORD ir[32];
BOOL bInsertDefault = FALSE;
BOOL
Win95ReadConsoleA(
HANDLE hIn,
LPSTR pBuf,
DWORD cch,
LPDWORD pcch,
LPVOID lpReserved
)
{
PCONSOLE_READCONSOLE_CONTROL pInputControl;
CONSOLE_SCREEN_BUFFER_INFO csbi;
const KEY_EVENT_RECORD* keyEvent;
BOOL bOk = TRUE; // return status value
DWORD dwc, dwOldMode;
DWORD dwCtrlWakeupMask;
BOOL bWakeupKeyHit;
// initialize the state variables....
nState = 0; // input mode
nBufPos = 0; // buffer cursor position
nBufLen = 0; // length of current command
bInsert = bInsertDefault; // insert mode
// set the appropriate console mode....
if (!GetConsoleMode( hIn, &dwOldMode ))
return FALSE;
SetConsoleMode( hIn, ENABLE_PROCESSED_INPUT );
// get the ouput buffer handle....
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
if ( hOut == INVALID_HANDLE_VALUE )
{
DWORD dwErr = GetLastError();
hOut = CRTTONT( STDOUT );
}
// get the output console info....
if ( GetConsoleScreenBufferInfo( hOut, &csbi ) == FALSE )
return FALSE;
// save size and initial cursor pos and console width....
coordStart = coordEnd = csbi.dwCursorPosition;
nConsoleWidth = csbi.dwSize.X;
nConsoleHeight = csbi.dwSize.Y;
wDefAttr = csbi.wAttributes;
// allocate a buffer to hold whatever is before command....
nPromptSize = 0;
if ( coordStart.X > 0 )
{
COORD cPrompt;
DWORD dwRead;
nPromptSize = coordStart.X;
cPrompt = coordStart;
cPrompt.X = 0;
pPrompt = calloc( nPromptSize+1, sizeof(TCHAR) );
// now copy the data....
ReadConsoleOutputCharacter( hOut, pPrompt, nPromptSize,
cPrompt, &dwRead );
// NULL-terminate it....
pPrompt[dwRead] = 0;
}
AdjCursorSize();
pInputControl = (PCONSOLE_READCONSOLE_CONTROL)lpReserved;
if (pInputControl != NULL && pInputControl->nLength == sizeof(*pInputControl)) {
dwCtrlWakeupMask = pInputControl->dwCtrlWakeupMask;
nBufLen = pInputControl->nInitialChars;
nBufPos = pInputControl->nInitialChars;
} else {
pInputControl = NULL;
dwCtrlWakeupMask = 0;
}
while ( !CtrlCSeen )
{
// get the next input record....
UINT ndx;
if( nOldIndex == 0 )
ReadConsoleInput( hIn, ir, sizeof(ir)/sizeof(INPUT_RECORD), &cRead );
// process all the records we just received....
for( ndx=nOldIndex; ndx < cRead; ++ndx )
{
// process only key down events....
keyEvent = &(ir[ndx].Event.KeyEvent);
if( ir[ndx].EventType == KEY_EVENT &&
keyEvent->bKeyDown &&
ProcessKey( keyEvent, pBuf, cch, dwCtrlWakeupMask, &bWakeupKeyHit )
)
{
if (pInputControl != NULL)
pInputControl->dwControlKeyState = keyEvent->dwControlKeyState;
goto donereading;
}
}
if( cRead == ndx ) {
nOldIndex = 0;
} else {
nOldIndex = ndx + 1;
}
}
donereading:
// clean-up....
if ( pPrompt ) {
free( pPrompt );
pPrompt = NULL;
}
if ( pSearchStr ) {
free( pSearchStr );
pSearchStr = NULL;
}
if (!bWakeupKeyHit) {
// not okay if we Ctrl-C'ed out....
if ( CtrlCSeen )
{
bOk = FALSE;
nBufPos = nBufLen;
AdjCursor( pBuf );
*pcch = 0;
}
else
{
// save to history (less the NLN) if something entered....
if ( nBufLen > 1 )
{
nLastCmd = (nLastCmd+1)%50;
// adjust the first for wrap-around....
if ( nLastCmd == nFirstCmd || nFirstCmd == -1 )
nFirstCmd = (nFirstCmd+1)%50;
_tcsncpy( history[nLastCmd], pBuf, nBufLen-1 );
// null-terminate....
history[nLastCmd][nBufLen-1] = 0;
}
*pcch = nBufLen;
}
// go to next line....
WriteConsole( hOut, _T("\n\r"), 2, &dwc, NULL );
FlushConsoleInputBuffer( hIn );
} else {
*pcch = nBufLen;
}
// restore the console mode....
SetConsoleMode( hIn, dwOldMode );
return bOk;
}
#endif // ifdef WIN95_CMD