407 lines
10 KiB
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;
|
|
}
|