/*++ 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; iprgpff[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; }