Windows-Server-2003/base/cmd/complete.c

407 lines
10 KiB
C

/*++
Copyright (c) 1988-1999 Microsoft Corporation
Module Name:
complete.c
Abstract:
File/path name completion support
--*/
#include "cmd.h"
//
// Upon the first completion, pCompleteBuffer is a pointer to an array
// of matching full path names
//
TCHAR **pCompleteBuffer = NULL;
//
// The count of strings stored in pCompleteBuffer
//
int nBufSize;
//
// The index in pCompleteBuffer of the current match displayed
//
int nCurrentSpec;
//
// There are two types of completion matching, path and directory. This is the current
// matching being done.
//
int bCurrentSpecType;
//
// When called for completion, the location of the beginning of the path is found
// and stored in nCurrentSpecPathStart. This is relative to the buffer passed
// in to DoComplete
//
int nCurrentSpecPathStart;
int CompleteDir( TCHAR *pFileSpecBuffer, int, int );
//
// Characters that CMD uses for its own grammar. To use these in filenames
// requires quoting
//
TCHAR szSpecialFileCharsToQuote[] = TEXT(" &()[]{}^=;!%'+,`~");
void
FreeCompleteBuffers( VOID )
{
int i;
if (pCompleteBuffer != NULL) {
for (i = 0; i < nBufSize; i++ ){
free( pCompleteBuffer[i] );
}
free( pCompleteBuffer );
pCompleteBuffer = NULL;
}
}
void
DoCompleteInitialize( VOID )
{
FreeCompleteBuffers( );
nBufSize = 0;
bCurrentSpecType = 0;
nCurrentSpecPathStart = 0;
nCurrentSpec = 0;
return;
}
int
DoComplete(
TCHAR *buffer,
int len,
int maxlen,
int bForward,
int bPathCompletion,
int bTouched)
/*++
Routine Description:
This is used whenever a path completion character is seen on input. It updates the
input buffer with the next matching text and returns the updated size.
Arguments:
buffer - input string that contains the prefix of the path to match at the end.
len - length of the input string.
maxlen - maximum string length that can be stored in buffer.
bForward - TRUE => matching goes forward through the storted match list. Otherwise
move backwards through the list.
bPathCompletion - TRUE => we match ONLY directories and not files+directories.
bTouched - TRUE => the user has edited the path. This usually means that we need to
begin the matching process anew.
Return Value:
Zero if no matching entries were found, otherwise the length of the updated buffer.
--*/
{
PTCHAR pFileSpecBuffer;
int nBufPos;
int nPathStart;
int nFileStart;
int k;
BOOL bWildSeen;
//
// Allocate the temp buffer. Unfortunately, some of the internal
// routines will issue a longjmp, so we need to free the buffer in a try/finally
//
pFileSpecBuffer = mkstr( LBUFLEN * sizeof( TCHAR ));
if (pFileSpecBuffer == NULL) {
return 0;
}
try {
//
// If the user edited the previous match or if the form of completion (dir vs
// dir/file) changed, then we must rebuild the matching information
//
if ( bTouched || (bCurrentSpecType != bPathCompletion)) {
BOOL InQuotes = FALSE;
//
// The following code was shipped in NT 4 and Windows 2000. It presented
// a usability problem when changing matching forms in mid-stream. We will
// now treat a simple change of completion type the same as being touched
// by the user: rebuild the matching database from where we are presently.
//
// //
// // if the buffer was left alone but the matching style
// // was changed, then start the matching process at the
// // beginning of the path
// //
//
// if (!bTouched && (bCurrentSpecType != bPathCompletion)) {
// buffer[nCurrentSpecPathStart] = NULLC;
// len = nCurrentSpecPathStart;
// }
//
// Determine the beginning of the path and file name. We
// need to take into account the presence of quotes and the
// need for CMD to introduce quoting as well
//
nPathStart = 0;
nFileStart = -1;
bWildSeen = FALSE;
for ( k = 0; k < len; k++ ) {
if (buffer[k] == SWITCHAR) {
nPathStart = k + 1;
bWildSeen = FALSE;
} else if ( buffer[k] == QUOTE ) {
if ( !InQuotes )
nPathStart = k;
InQuotes = !InQuotes;
} else if ( !InQuotes &&
_tcschr(szSpecialFileCharsToQuote, buffer[k]) != NULL
) {
nPathStart = k+1;
bWildSeen = FALSE;
} else if (buffer[k] == COLON ||
buffer[k] == BSLASH
) {
nFileStart = k+1;
bWildSeen = FALSE;
} else if (buffer[k] == STAR || buffer[k] == QMARK) {
bWildSeen = TRUE;
}
}
if (nFileStart == -1 || nFileStart < nPathStart)
nFileStart = nPathStart;
_tcsncpy( pFileSpecBuffer, &(buffer[nPathStart]), len-nPathStart );
if (!bWildSeen) {
pFileSpecBuffer[len-nPathStart+0] = TEXT('*');
pFileSpecBuffer[len-nPathStart+1] = TEXT('\0');
} else {
pFileSpecBuffer[len-nPathStart+0] = TEXT('\0');
}
// do the DIR into a buffer
nBufSize = CompleteDir( pFileSpecBuffer, bPathCompletion, nFileStart - nPathStart );
// reset the current completion string
nCurrentSpec = nBufSize;
nCurrentSpecPathStart = nPathStart;
bCurrentSpecType = bPathCompletion;
}
// if no matches, do nothing.
if ( nBufSize == 0 ) {
leave;;
}
// find our postion in the completion buffer.
if ( bForward ) {
nCurrentSpec++;
if ( nCurrentSpec >= nBufSize )
nCurrentSpec = 0;
} else {
nCurrentSpec--;
if ( nCurrentSpec < 0 )
nCurrentSpec = nBufSize - 1;
}
// Return nothing if buffer not big enough
if ((int)(nCurrentSpecPathStart+_tcslen(pCompleteBuffer[nCurrentSpec])) >= maxlen) {
nBufSize = 0;
leave;
}
// copy the completion path onto the end of the command line
_tcscpy(&buffer[nCurrentSpecPathStart], pCompleteBuffer[nCurrentSpec] );
} finally {
FreeStr( pFileSpecBuffer );
}
return nBufSize;
}
int
CompleteDir(
TCHAR *pFileSpecBuffer,
int bPathCompletion,
int nFileStart
)
{
PFS pfsCur;
PSCREEN pscr;
DRP drpCur = {0, 0, 0, 0,
{{0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}},
0, 0, NULL, 0, 0, 0, 0} ;
int hits = 0;
int i, j, nFileLen;
unsigned Err;
TCHAR *s, *d, *pszFileStart;
BOOLEAN bNeedQuotes;
ULONG rgfAttribs, rgfAttribsOnOff;
FreeCompleteBuffers( );
// fake up a screen to print into
pscr = (PSCREEN)gmkstr(sizeof(SCREEN));
pscr->ccol = 2048;
rgfAttribs = 0;
rgfAttribsOnOff = 0;
if (bPathCompletion) {
rgfAttribs = FILE_ATTRIBUTE_DIRECTORY;
rgfAttribsOnOff = FILE_ATTRIBUTE_DIRECTORY;
}
ParseDirParms(pFileSpecBuffer, &drpCur);
if ( (drpCur.patdscFirst.pszPattern == NULL) ||
(SetFsSetSaveDir(drpCur.patdscFirst.pszPattern) == (PCPYINFO) FAILURE) ||
(BuildFSFromPatterns(&drpCur, FALSE, TRUE, &pfsCur) == FAILURE) ) {
RestoreSavedDirectory( );
return( 0 );
}
Err =
ExpandAndApplyToFS( pfsCur,
pscr,
rgfAttribs,
rgfAttribsOnOff,
NULL,
NULL,
NULL,
NULL,
NULL );
if (Err) {
RestoreSavedDirectory( );
return 0;
}
//
// Make sure there is something to sort, then sort
//
if (pfsCur->cff) {
qsort( pfsCur->prgpff,
pfsCur->cff,
sizeof(PTCHAR),
CmpName
);
}
s = pFileSpecBuffer;
d = s;
bNeedQuotes = FALSE;
while (*s) {
if (*s == QUOTE) {
bNeedQuotes = TRUE;
s += 1;
if (nFileStart >= (s-pFileSpecBuffer))
nFileStart -= 1;
if (*s == QUOTE)
*d++ = *s++;
}
else {
if (_tcschr(szSpecialFileCharsToQuote, *s) != NULL)
bNeedQuotes = TRUE;
*d++ = *s++;
}
}
*d = NULLC;
hits = pfsCur->cff;
pCompleteBuffer = calloc( sizeof(TCHAR *), hits );
if (pCompleteBuffer == NULL) {
RestoreSavedDirectory( );
return 0;
}
for(i=0, j=0; i<hits; i++) {
if (!_tcscmp((TCHAR *)(pfsCur->prgpff[i]->data.cFileName), TEXT(".") )
|| !_tcscmp((TCHAR *)(pfsCur->prgpff[i]->data.cFileName), TEXT("..") )) {
continue;
}
nFileLen = _tcslen( (TCHAR *)(pfsCur->prgpff[i]->data.cFileName) );
pCompleteBuffer[j] = (TCHAR *)calloc( (nFileStart + nFileLen + 4) , sizeof( TCHAR ));
if (pCompleteBuffer[j] == NULL) {
continue;
}
if (!bNeedQuotes) {
s = (TCHAR *)(pfsCur->prgpff[i]->data.cFileName);
while (*s) {
if (_tcschr(szSpecialFileCharsToQuote, *s) != NULL)
bNeedQuotes = TRUE;
s += 1;
}
}
else
s = NULL;
d = pCompleteBuffer[j];
if (bNeedQuotes)
*d++ = QUOTE;
_tcsncpy( d, pFileSpecBuffer, nFileStart );
d += nFileStart;
_tcsncpy( d, (TCHAR *)(pfsCur->prgpff[i]->data.cFileName), nFileLen );
d += nFileLen;
if (bNeedQuotes) {
*d++ = QUOTE;
if (s)
bNeedQuotes = FALSE;
}
*d++ = NULLC;
j++;
}
hits = j;
FreeStr((PTCHAR)(pfsCur->pff));
FreeStr(pfsCur->pszDir);
FreeStr((PTCHAR)pfsCur);
RestoreSavedDirectory( );
return hits;
}