380 lines
9.7 KiB
C
380 lines
9.7 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;
|
||
|
|
||
|
//
|
||
|
// 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
|
||
|
DoCompleteInitialize( VOID )
|
||
|
{
|
||
|
pCompleteBuffer = NULL;
|
||
|
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.
|
||
|
|
||
|
--*/
|
||
|
{
|
||
|
TCHAR pFileSpecBuffer[512];
|
||
|
int nBufPos;
|
||
|
int nPathStart;
|
||
|
int nFileStart;
|
||
|
int k;
|
||
|
BOOL bWildSeen;
|
||
|
|
||
|
//
|
||
|
// 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 ) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// 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)
|
||
|
return 0;
|
||
|
|
||
|
// copy the completion path onto the end of the command line
|
||
|
_tcscpy(&buffer[nCurrentSpecPathStart], pCompleteBuffer[nCurrentSpec] );
|
||
|
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;
|
||
|
|
||
|
if (pCompleteBuffer != NULL) {
|
||
|
// free the old buffer
|
||
|
for( i=0; i<nBufSize; i++ ){
|
||
|
free( pCompleteBuffer[i] );
|
||
|
}
|
||
|
free( pCompleteBuffer );
|
||
|
pCompleteBuffer = NULL;
|
||
|
}
|
||
|
|
||
|
// 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;
|
||
|
}
|