12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477 |
- /*++
- Copyright (c) 2013 Minoca Corp. All Rights Reserved
- Module Name:
- linein.c
- Abstract:
- This module implements user input functionality.
- Author:
- Evan Green 16-Mar-2013
- Environment:
- User
- --*/
- //
- // ------------------------------------------------------------------- Includes
- //
- #include <assert.h>
- #include <ctype.h>
- #include <errno.h>
- #include <libgen.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include "sh.h"
- #include "../swlib.h"
- #include <minoca/lib/termlib.h>
- //
- // ---------------------------------------------------------------- Definitions
- //
- //
- // Define the size of the string initially allocated for the line. This should
- // be big enough that "most" commands succeed without expansion.
- //
- #define INITIAL_COMMAND_LENGTH 10
- //
- // Define the maximum number of bytes that could constitute one (control)
- // character.
- //
- #define MAX_CHARACTER_LENGTH 10
- //
- // Define the number of lines page up and page down scroll by.
- //
- #define SCROLL_LINE_COUNT 10
- //
- // Define the Control+C character.
- //
- #define CONTROL_C_CHARACTER 3
- //
- // Define the default size of the command history.
- //
- #define DEFAULT_COMMAND_HISTORY_SIZE 50
- #define INITIAL_STRING_ARRAY_SIZE 16
- #define COMPLETION_COLUMN_PADDING 2
- #define COMPLETION_DEFAULT_TERMINAL_WIDTH 80
- //
- // ------------------------------------------------------ Data Type Definitions
- //
- //
- // ----------------------------------------------- Internal Function Prototypes
- //
- VOID
- ShCompleteFilePath (
- PSHELL Shell,
- PSTR *Command,
- PULONG CommandLength,
- PULONG CommandSize,
- PULONG CompletionPosition,
- PULONG Position
- );
- VOID
- ShGetFileMatches (
- PSTR File,
- PSTR **Matches,
- PULONG MatchCount
- );
- VOID
- ShGetFileMatchesInDirectory (
- PSTR FileName,
- PSTR DirectoryName,
- PSTR **Matches,
- PULONG MatchCount
- );
- BOOL
- ShGetFileCompletionPortion (
- PSHELL Shell,
- PSTR Command,
- PULONG CompletionPosition,
- ULONG CommandLength,
- PULONG FileStartPosition,
- PSTR *FileString,
- PSTR *PreviousGuess
- );
- PSTR
- ShGetFileReplacementString (
- PSTR UserString,
- PSTR PreviousGuess,
- PSTR *Matches,
- ULONG MatchCount
- );
- BOOL
- ShQuoteString (
- PSTR String,
- PSTR *QuotedString
- );
- VOID
- ShAddCommandHistoryEntry (
- PSTR Command
- );
- PSTR
- ShGetCommandHistoryEntry (
- LONG Offset
- );
- VOID
- ShCleanLine (
- FILE *Output,
- ULONG Position,
- ULONG CommandLength
- );
- VOID
- ShPrintSpaces (
- FILE *Output,
- INT Count
- );
- BOOL
- ShAddStringToArray (
- PSTR **Array,
- PSTR Entry,
- PULONG ArraySize,
- PULONG ArrayCapacity
- );
- BOOL
- ShMergeStringArrays (
- PSTR **Array1,
- PULONG Array1Size,
- PULONG Array1Capacity,
- PSTR *Array2,
- ULONG Array2Size
- );
- VOID
- ShRemoveDuplicateFileMatches (
- PSTR *Array,
- PULONG ArraySize
- );
- VOID
- ShDestroyStringArray (
- PSTR *Array,
- ULONG ArraySize
- );
- int
- ShCompareStringArrayElements (
- const void *LeftElement,
- const void *RightElement
- );
- //
- // -------------------------------------------------------------------- Globals
- //
- CHAR ShBackspaceCharacter = 0x7F;
- CHAR ShKillLineCharacter = 0x0B;
- //
- // Define the size of the command history.
- //
- LONG ShCommandHistorySize = DEFAULT_COMMAND_HISTORY_SIZE;
- PSTR *ShCommandHistory;
- LONG ShCommandHistoryIndex;
- //
- // Set this flag to enable printing of file completion suggestions in color.
- //
- BOOL ShColorFileSuggestions = TRUE;
- //
- // Set this flag to "guess" when there are multiple file matches, and then
- // cycle through the guesses.
- //
- BOOL ShGuessFileMatch = TRUE;
- //
- // ------------------------------------------------------------------ Functions
- //
- BOOL
- ShReadLine (
- PSHELL Shell,
- PSTR *ReturnedCommand,
- PULONG ReturnedCommandLength
- )
- /*++
- Routine Description:
- This routine reads a command in from the user.
- Arguments:
- Shell - Supplies a pointer to the shell.
- ReturnedCommand - Supplies a pointer where a pointer to a null terminated
- string (allocated via malloc) containing the command will be returned.
- This may be set to NULL if the end of file indicator was returned from
- stdin.
- ReturnedCommandLength - Supplies a pointer where the size of the command
- being returned will be returned, including the null terminator, in
- bytes.
- Return Value:
- TRUE on success.
- FALSE on failure.
- --*/
- {
- ULONG BaseOffset;
- int Byte;
- ULONG ByteIndex;
- PSTR Command;
- ULONG CommandLength;
- ULONG CommandSize;
- ULONG CompletionPosition;
- PSTR HistoryEntry;
- LONG HistoryOffset;
- TERMINAL_KEY_DATA KeyData;
- PSTR NewBuffer;
- ULONG NewCommandSize;
- FILE *Output;
- TERMINAL_PARSE_RESULT ParseResult;
- ULONG Position;
- BOOL Result;
- CommandLength = 0;
- CompletionPosition = (ULONG)-1;
- HistoryEntry = NULL;
- HistoryOffset = 0;
- Output = Shell->NonStandardError;
- Result = TRUE;
- Position = 0;
- memset(&KeyData, 0, sizeof(TERMINAL_KEY_DATA));
- ShSetTerminalMode(Shell, TRUE);
- //
- // Allocate the initial command buffer.
- //
- CommandSize = INITIAL_COMMAND_LENGTH;
- Command = malloc(CommandSize);
- if (Command == NULL) {
- Result = FALSE;
- goto ReadLineEnd;
- }
- Command[0] = '\0';
- //
- // Loop reading in characters.
- //
- while (TRUE) {
- fflush(Output);
- Byte = SwReadInputCharacter();
- if (Byte == -1) {
- printf("sh: Input error: %s\n", strerror(errno));
- break;
- }
- //
- // A newline signals the end of the command.
- //
- if ((Byte == '\n') || (Byte == '\r')) {
- break;
- }
- //
- // A Control-C aborts the current command.
- //
- if (Byte == CONTROL_C_CHARACTER) {
- fputs("^C\n", Output);
- Result = FALSE;
- goto ReadLineEnd;
- }
- ParseResult = TermProcessInput(&KeyData, Byte);
- switch (ParseResult) {
- case TerminalParseResultNormalCharacter:
- //
- // Handle a backspace.
- //
- if (Byte == ShBackspaceCharacter) {
- Byte = -1;
- //
- // If there is already nothing, throw this out.
- //
- if (Position == 0) {
- continue;
- }
- //
- // Adjust the buffer for the backspace. The standard memory
- // copy function cannot be used since the buffers are
- // overlapping and so copy order matters.
- //
- Position -= 1;
- CommandLength -= 1;
- for (ByteIndex = Position;
- ByteIndex < CommandLength;
- ByteIndex += 1) {
- Command[ByteIndex] = Command[ByteIndex + 1];
- }
- //
- // Go back one and print the shifted command, plus a space to
- // cover up the old ending character.
- //
- assert(Command[CommandLength + 1] == '\0');
- Command[CommandLength] = ' ';
- SwMoveCursorRelative(Output, -1, NULL);
- fwrite(Command + Position,
- CommandLength - Position + 1,
- 1,
- Output);
- Command[CommandLength] = '\0';
- //
- // But oh dear, now the cursor is at the end of the line. Print
- // backspaces to get it back.
- //
- SwMoveCursorRelative(Output,
- -(CommandLength - Position + 1),
- NULL);
- //
- // Tabs do file completion.
- //
- } else if (Byte == '\t') {
- ShCompleteFilePath(Shell,
- &Command,
- &CommandLength,
- &CommandSize,
- &CompletionPosition,
- &Position);
- Byte = -1;
- //
- // In the case where the returned completion position is not the
- // same as the current position, continue directly to avoid
- // updating it, so that pressing tab again guesses from the
- // previous completion position.
- //
- if (CompletionPosition != (ULONG)-1) {
- continue;
- }
- //
- // Handle the kill line character.
- //
- } else if (Byte == ShKillLineCharacter) {
- ShCleanLine(Output, Position, CommandLength);
- Position = 0;
- CommandLength = 0;
- Command[0] = '\0';
- Byte = -1;
- } else if (iscntrl(Byte)) {
- Byte = -1;
- }
- break;
- case TerminalParseResultPartialCommand:
- Byte = -1;
- break;
- case TerminalParseResultCompleteCommand:
- Byte = -1;
- //
- // Handle command history entries.
- //
- switch (KeyData.Key) {
- case TerminalKeyUp:
- HistoryEntry = ShGetCommandHistoryEntry(HistoryOffset + 1);
- if (HistoryEntry != NULL) {
- HistoryOffset += 1;
- }
- break;
- case TerminalKeyDown:
- HistoryEntry = ShGetCommandHistoryEntry(HistoryOffset - 1);
- if (HistoryEntry != NULL) {
- HistoryOffset -= 1;
- }
- break;
- case TerminalKeyRight:
- if (Position < CommandLength) {
- SwMoveCursorRelative(Output, 1, &(Command[Position]));
- Position += 1;
- }
- break;
- case TerminalKeyLeft:
- if (Position != 0) {
- Position -= 1;
- SwMoveCursorRelative(Output, -1, NULL);
- }
- break;
- case TerminalKeyHome:
- SwMoveCursorRelative(Output, -Position, NULL);
- Position = 0;
- break;
- case TerminalKeyEnd:
- SwMoveCursorRelative(Output,
- CommandLength - Position,
- &(Command[Position]));
- Position = CommandLength;
- break;
- case TerminalKeyDelete:
- if ((CommandLength == 0) || (Position == CommandLength)) {
- break;
- }
- CommandLength -= 1;
- for (ByteIndex = Position;
- ByteIndex < CommandLength;
- ByteIndex += 1) {
- Command[ByteIndex] = Command[ByteIndex + 1];
- }
- //
- // Go back one and print the shifted command, plus a space
- // to cover up the old ending character.
- //
- assert(Command[CommandLength + 1] == '\0');
- Command[CommandLength] = ' ';
- fwrite(Command + Position,
- CommandLength - Position + 1,
- 1,
- Output);
- Command[CommandLength] = '\0';
- //
- // But oh dear, now the cursor is at the end of the line.
- // Print backspaces to get it back.
- //
- SwMoveCursorRelative(Output,
- -(CommandLength - Position + 1),
- NULL);
- break;
- case TerminalKeyPageUp:
- SwScrollTerminal(-SCROLL_LINE_COUNT);
- break;
- case TerminalKeyPageDown:
- SwScrollTerminal(SCROLL_LINE_COUNT);
- break;
- default:
- break;
- }
- break;
- default:
- assert(FALSE);
- break;
- }
- CompletionPosition = (ULONG)-1;
- //
- // If a history entry was found, then kill the current line and use it.
- //
- if (HistoryEntry != NULL) {
- Byte = -1;
- ShCleanLine(Output, Position, CommandLength);
- if (Command != NULL) {
- free(Command);
- }
- Command = HistoryEntry;
- CommandLength = strlen(Command);
- CommandSize = CommandLength + 1;
- Position = CommandLength;
- fwrite(Command, 1, CommandLength, Output);
- HistoryEntry = NULL;
- }
- //
- // If after all the processing there are no new characters, loop on to
- // get more.
- //
- if (Byte == -1) {
- continue;
- }
- //
- // There are characters in the character buffer, move them to the
- // command. Start by seeing if adding these characters would overrun
- // the buffer, and expand if so.
- //
- if (CommandLength + 2 > CommandSize) {
- NewCommandSize = CommandSize * 2;
- if (NewCommandSize < INITIAL_COMMAND_LENGTH) {
- NewCommandSize = INITIAL_COMMAND_LENGTH;
- }
- assert(CommandLength + 2 < NewCommandSize);
- NewBuffer = realloc(Command, NewCommandSize);
- if (NewBuffer == NULL) {
- Result = FALSE;
- goto ReadLineEnd;
- }
- CommandSize = NewCommandSize;
- Command = NewBuffer;
- }
- //
- // If the current position is not at the end of the string, then
- // room will need to be made. The standard memory copy routines cannot
- // be used since the copy buffers are overlapping, and therefore copy
- // order matters.
- //
- if (Position != CommandLength) {
- BaseOffset = CommandLength - 1;
- for (ByteIndex = 0;
- ByteIndex < CommandLength - Position;
- ByteIndex += 1) {
- Command[BaseOffset + 1 - ByteIndex] =
- Command[BaseOffset - ByteIndex];
- }
- }
- Command[Position] = Byte;
- //
- // Write out the remainder of the line.
- //
- CommandLength += 1;
- fwrite(Command + Position, CommandLength - Position, 1, Output);
- Position += 1;
- if (Position != CommandLength) {
- SwMoveCursorRelative(Output, -(CommandLength - Position), 0);
- }
- Byte = -1;
- Command[CommandLength] = '\0';
- }
- assert(Command[CommandLength] == '\0');
- //
- // If the current position is not the end, move to the end.
- //
- if (Position != CommandLength) {
- SwMoveCursorRelative(Output,
- CommandLength - Position,
- &(Command[Position]));
- }
- fputc('\n', Output);
- CommandLength += 1;
- ShAddCommandHistoryEntry(Command);
- ReadLineEnd:
- fflush(Output);
- ShSetTerminalMode(Shell, FALSE);
- if (Result == FALSE) {
- CommandLength = 0;
- if (Command != NULL) {
- free(Command);
- Command = NULL;
- }
- }
- *ReturnedCommand = Command;
- *ReturnedCommandLength = CommandLength;
- return Result;
- }
- //
- // --------------------------------------------------------- Internal Functions
- //
- VOID
- ShCompleteFilePath (
- PSHELL Shell,
- PSTR *Command,
- PULONG CommandLength,
- PULONG CommandSize,
- PULONG CompletionPosition,
- PULONG Position
- )
- /*++
- Routine Description:
- This routine performs file path completion in response to a tab character.
- Arguments:
- Shell - Supplies a pointer to the shell context.
- Command - Supplies a pointer that on input contains a pointer to the
- command so far. On output this will return the potentially reallocated
- completed command.
- CommandLength - Supplies a pointer that on input contains the command
- length. This will be updated to reflect any changes.
- CommandSize - Supplies a pointer that on input contains the size of the
- command buffer's allocation. On output this will contain the updated
- value if the command was reallocated.
- CompletionPosition - Supplies a pointer that on input contains the index to
- stem completions off of. This may be different from the current
- position if cycling through guesses. This may be updated if a
- definitive selection is chosen.
- Position - Supplies a pointer that on input contains the position within
- the command. On output this may be updated.
- Return Value:
- None.
- --*/
- {
- UINTN BigCommandCapacity;
- UINTN BigCommandSize;
- ULONG ColumnCount;
- ULONG ColumnIndex;
- size_t ColumnSize;
- int ConsoleWidth;
- ULONG FileStartIndex;
- PSTR Match;
- ULONG MatchCount;
- PSTR *Matches;
- ULONG MatchIndex;
- size_t MatchLength;
- LONG Offset;
- FILE *Output;
- PSTR PreviousGuess;
- ULONG ReplacedSize;
- PSTR Replacement;
- size_t ReplacementSize;
- BOOL Result;
- ULONG RoundedCount;
- PSTR *RoundedMatches;
- ULONG RowCount;
- PSTR UserString;
- Matches = NULL;
- MatchCount = 0;
- Output = Shell->NonStandardError;
- PreviousGuess = NULL;
- Replacement = NULL;
- RoundedMatches = NULL;
- UserString = NULL;
- //
- // Restore the terminal mode since the shell may execute expansions.
- //
- ShSetTerminalMode(Shell, FALSE);
- Result = ShGetFileCompletionPortion(Shell,
- *Command,
- CompletionPosition,
- *Position,
- &FileStartIndex,
- &UserString,
- &PreviousGuess);
- if (Result == FALSE) {
- goto CompleteFilePathEnd;
- }
- ShGetFileMatches(UserString, &Matches, &MatchCount);
- if ((Matches == NULL) || (MatchCount == 0)) {
- goto CompleteFilePathEnd;
- }
- //
- // Sort the match array alphabetically.
- //
- if (MatchCount > 1) {
- qsort(Matches, MatchCount, sizeof(PSTR), ShCompareStringArrayElements);
- ShRemoveDuplicateFileMatches(Matches, &MatchCount);
- }
- //
- // Using the matches, attempt to come up with a replacement string.
- //
- Replacement = ShGetFileReplacementString(UserString,
- PreviousGuess,
- Matches,
- MatchCount);
- //
- // Perform the replacement if there was one.
- //
- if (Replacement != NULL) {
- BigCommandSize = *CommandLength;
- BigCommandCapacity = *CommandSize;
- if (BigCommandSize < BigCommandCapacity) {
- BigCommandSize += 1;
- }
- ReplacedSize = *Position - FileStartIndex;
- ReplacementSize = strlen(Replacement);
- assert((*Command)[*CommandLength] == '\0');
- Result = SwStringReplaceRegion(Command,
- &BigCommandSize,
- &BigCommandCapacity,
- FileStartIndex,
- FileStartIndex + ReplacedSize,
- Replacement,
- ReplacementSize + 1);
- if (Result == FALSE) {
- goto CompleteFilePathEnd;
- }
- *CommandLength = BigCommandSize - 1;
- *CommandSize = BigCommandCapacity;
- assert((*Command)[*CommandLength] == '\0');
- //
- // Redraw the command. Go back to the beginning of the replacement
- // region, print the command, print spaces for any leftover parts, then
- // back up over the spaces.
- //
- SwMoveCursorRelative(Output, -(*Position - FileStartIndex), NULL);
- fprintf(Output, "%s", *Command + FileStartIndex);
- Offset = 0;
- if (ReplacedSize > ReplacementSize) {
- Offset = ReplacedSize - ReplacementSize;
- ShPrintSpaces(Output, Offset);
- }
- //
- // The negative of the offset gets back to the end of the new command.
- // Back up to the beginning, then forward to the end of the replacement.
- //
- Offset += *CommandLength - (FileStartIndex + ReplacementSize);
- if (Offset != 0) {
- SwMoveCursorRelative(Output, -Offset, NULL);
- }
- *Position += ReplacementSize - ReplacedSize;
- }
- //
- // If the match was ambiguous and this is the first time taking a stab at
- // it, print the options.
- //
- if ((MatchCount > 1) && (PreviousGuess == NULL)) {
- //
- // Figure out the column size to display these matches.
- //
- ColumnSize = COMPLETION_COLUMN_PADDING;
- for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex += 1) {
- MatchLength = strlen(Matches[MatchIndex]);
- if (ColumnSize < MatchLength + COMPLETION_COLUMN_PADDING) {
- ColumnSize = MatchLength + COMPLETION_COLUMN_PADDING;
- }
- }
- Result = SwGetTerminalDimensions(&ConsoleWidth, NULL);
- if (Result != 0) {
- ConsoleWidth = COMPLETION_DEFAULT_TERMINAL_WIDTH;
- }
- ColumnCount = (ConsoleWidth - 1) / ColumnSize;
- if (ColumnCount == 0) {
- ColumnCount = 1;
- }
- RowCount = MatchCount / ColumnCount;
- if ((MatchCount % ColumnCount) != 0) {
- RowCount += 1;
- }
- //
- // Potentially reallocate a rounded up rectangular array.
- //
- RoundedMatches = Matches;
- RoundedCount = MatchCount;
- if (MatchCount > ColumnCount) {
- RoundedCount = RowCount * ColumnCount;
- if (RoundedCount > MatchCount) {
- RoundedMatches = malloc(RoundedCount * sizeof(PSTR));
- if (RoundedMatches == NULL) {
- goto CompleteFilePathEnd;
- }
- memcpy(RoundedMatches, Matches, MatchCount * sizeof(PSTR));
- memset(RoundedMatches + MatchCount,
- 0,
- (RoundedCount - MatchCount) * sizeof(PSTR));
- }
- Result = SwRotatePointerArray((PVOID *)RoundedMatches,
- ColumnCount,
- RowCount);
- if (Result == FALSE) {
- goto CompleteFilePathEnd;
- }
- }
- if (*Position != *CommandLength) {
- SwMoveCursorRelative(Output,
- *CommandLength - *Position,
- *Command + *Position);
- }
- fputc('\n', Output);
- ColumnIndex = 0;
- for (MatchIndex = 0; MatchIndex < RoundedCount; MatchIndex += 1) {
- Match = RoundedMatches[MatchIndex];
- if (Match == NULL) {
- if ((MatchIndex == 0) ||
- (RoundedMatches[MatchIndex - 1] != NULL)) {
- fputc('\n', Output);
- }
- ColumnIndex = 0;
- continue;
- }
- MatchLength = strlen(Match);
- if ((MatchLength != 0) && (Match[MatchLength - 1] == '/') &&
- (ShColorFileSuggestions != FALSE)) {
- SwPrintInColor(ConsoleColorDefault,
- ConsoleColorBlue,
- "%-*s",
- ColumnSize,
- Match);
- } else {
- fprintf(Output, "%-*s", ColumnSize, Match);
- }
- ColumnIndex += 1;
- if (ColumnIndex == ColumnCount) {
- fputc('\n', Output);
- ColumnIndex = 0;
- }
- }
- if (ColumnIndex != 0) {
- fputc('\n', Output);
- }
- //
- // Reprint the command.
- //
- fprintf(Output, "%s", Shell->Prompt);
- fprintf(Output, "%s", *Command);
- if (*Position != *CommandLength) {
- SwMoveCursorRelative(Output, -(*CommandLength - *Position), NULL);
- }
- }
- CompleteFilePathEnd:
- assert((*Command)[*CommandLength] == '\0');
- ShSetTerminalMode(Shell, TRUE);
- if (UserString != NULL) {
- free(UserString);
- }
- if (PreviousGuess != NULL) {
- free(PreviousGuess);
- }
- if ((RoundedMatches != NULL) && (RoundedMatches != Matches)) {
- free(RoundedMatches);
- }
- if (Matches != NULL) {
- ShDestroyStringArray(Matches, MatchCount);
- }
- if (Replacement != NULL) {
- free(Replacement);
- }
- return;
- }
- VOID
- ShGetFileMatches (
- PSTR File,
- PSTR **Matches,
- PULONG MatchCount
- )
- /*++
- Routine Description:
- This routine returns any files that match the given file name.
- Arguments:
- File - Supplies a pointer to the string containing the file prefix.
- Matches - Supplies a pointer where an array of matches will be returned on
- success, or NULL if nothing matches.
- MatchCount - Supplies a pointer where the number of elements in the
- match array will be returned.
- Return Value:
- None.
- --*/
- {
- PSTR BaseName;
- PSTR CurrentPath;
- PSTR Directory;
- PSTR *DirectoryMatchArray;
- ULONG DirectoryMatchArraySize;
- PSTR FileCopy;
- size_t FileLength;
- PSTR *MatchArray;
- ULONG MatchArrayCapacity;
- ULONG MatchArraySize;
- PSTR NextSeparator;
- PSTR Path;
- PSTR PathCopy;
- CHAR Separator;
- BaseName = NULL;
- Directory = NULL;
- MatchArray = NULL;
- MatchArrayCapacity = 0;
- MatchArraySize = 0;
- PathCopy = NULL;
- FileCopy = strdup(File);
- if (FileCopy == NULL) {
- goto GetFileMatchesEnd;
- }
- FileLength = strlen(FileCopy);
- //
- // Get the directory portion and the file name portion of the string.
- // If the last character is a slash, then treat the whole thing like the
- // directory name.
- //
- if ((FileLength != 0) && (FileCopy[FileLength - 1] == '/')) {
- BaseName = strdup("");
- Directory = FileCopy;
- FileCopy = NULL;
- } else {
- BaseName = basename(FileCopy);
- if (BaseName == NULL) {
- goto GetFileMatchesEnd;
- }
- BaseName = strdup(BaseName);
- Directory = dirname(FileCopy);
- if (Directory == NULL) {
- goto GetFileMatchesEnd;
- }
- Directory = strdup(Directory);
- }
- if ((BaseName == NULL) || (Directory == NULL)) {
- goto GetFileMatchesEnd;
- }
- //
- // Search the directory as specified directly.
- //
- ShGetFileMatchesInDirectory(BaseName,
- Directory,
- &MatchArray,
- &MatchArraySize);
- MatchArrayCapacity = MatchArraySize;
- //
- // If the path has a slash in it, then it's fully specified, so this set is
- // it.
- //
- if (strchr(File, '/') != NULL) {
- goto GetFileMatchesEnd;
- }
- //
- // Get the path and create a copy to tokenize over.
- //
- Path = getenv("PATH");
- if (Path == NULL) {
- goto GetFileMatchesEnd;
- }
- PathCopy = strdup(Path);
- if (PathCopy == NULL) {
- goto GetFileMatchesEnd;
- }
- CurrentPath = PathCopy;
- Separator = PATH_LIST_SEPARATOR;
- while (TRUE) {
- NextSeparator = strchr(CurrentPath, Separator);
- if (NextSeparator != NULL) {
- *NextSeparator = '\0';
- }
- DirectoryMatchArray = NULL;
- DirectoryMatchArraySize = 0;
- ShGetFileMatchesInDirectory(BaseName,
- CurrentPath,
- &DirectoryMatchArray,
- &DirectoryMatchArraySize);
- ShMergeStringArrays(&MatchArray,
- &MatchArraySize,
- &MatchArrayCapacity,
- DirectoryMatchArray,
- DirectoryMatchArraySize);
- if (NextSeparator == NULL) {
- break;
- }
- CurrentPath = NextSeparator + 1;
- }
- GetFileMatchesEnd:
- if (FileCopy != NULL) {
- free(FileCopy);
- }
- if (BaseName != NULL) {
- free(BaseName);
- }
- if (Directory != NULL) {
- free(Directory);
- }
- if (PathCopy != NULL) {
- free(PathCopy);
- }
- *Matches = MatchArray;
- *MatchCount = MatchArraySize;
- return;
- }
- VOID
- ShGetFileMatchesInDirectory (
- PSTR FileName,
- PSTR DirectoryName,
- PSTR **Matches,
- PULONG MatchCount
- )
- /*++
- Routine Description:
- This routine returns any files that match the given file name in the given
- directory.
- Arguments:
- FileName - Supplies a pointer to the string containing the file prefix.
- DirectoryName - Supplies a pointer to a string containing the directory to
- search in.
- Matches - Supplies a pointer where an array of matches will be returned on
- success, or NULL if nothing matches.
- MatchCount - Supplies a pointer where the number of elements in the
- match array will be returned.
- Return Value:
- None.
- --*/
- {
- DIR *Directory;
- size_t DirectoryLength;
- struct dirent Entry;
- size_t EntryLength;
- struct dirent *EntryPointer;
- size_t FileNameLength;
- PSTR FullPath;
- PSTR *MatchArray;
- ULONG MatchArrayCapacity;
- ULONG MatchArraySize;
- int Result;
- int SlashSize;
- struct stat Stat;
- MatchArray = NULL;
- MatchArraySize = 0;
- MatchArrayCapacity = 0;
- Directory = opendir(DirectoryName);
- if (Directory == NULL) {
- goto GetFileMatchesInDirectoryEnd;
- }
- DirectoryLength = strlen(DirectoryName);
- FileNameLength = strlen(FileName);
- //
- // Loop reading directory entries.
- //
- while (TRUE) {
- Result = SwReadDirectory(Directory, &Entry, &EntryPointer);
- if (Result != 0) {
- goto GetFileMatchesInDirectoryEnd;
- }
- if (EntryPointer == NULL) {
- break;
- }
- if ((strcmp(Entry.d_name, ".") == 0) ||
- (strcmp(Entry.d_name, "..") == 0)) {
- continue;
- }
- if (strncasecmp(FileName, Entry.d_name, FileNameLength) == 0) {
- EntryLength = strlen(Entry.d_name);
- FullPath = malloc(DirectoryLength + EntryLength + 3);
- if (FullPath == NULL) {
- continue;
- }
- memcpy(FullPath, DirectoryName, DirectoryLength);
- SlashSize = 0;
- if ((DirectoryLength != 0) &&
- (DirectoryName[DirectoryLength - 1] != '/')) {
- FullPath[DirectoryLength] = '/';
- SlashSize = 1;
- }
- strcpy(FullPath + DirectoryLength + SlashSize, Entry.d_name);
- Result = SwStat(FullPath, TRUE, &Stat);
- if ((Result == 0) && (S_ISDIR(Stat.st_mode))) {
- strcpy(FullPath + DirectoryLength + SlashSize + EntryLength,
- "/");
- }
- ShAddStringToArray(&MatchArray,
- FullPath + DirectoryLength + SlashSize,
- &MatchArraySize,
- &MatchArrayCapacity);
- free(FullPath);
- }
- }
- GetFileMatchesInDirectoryEnd:
- if (Directory != NULL) {
- closedir(Directory);
- }
- *Matches = MatchArray;
- *MatchCount = MatchArraySize;
- return;
- }
- BOOL
- ShGetFileCompletionPortion (
- PSHELL Shell,
- PSTR Command,
- PULONG CompletionPosition,
- ULONG CommandLength,
- PULONG FileStartPosition,
- PSTR *FileString,
- PSTR *PreviousGuess
- )
- /*++
- Routine Description:
- This routine gets the portion of the end of the command that is eligible
- for file path expansion.
- Arguments:
- Shell - Supplies a pointer to the shell context.
- Command - Supplies a pointer to the command.
- CompletionPosition - Supplies a pointer to the position to base completions
- off of. This may be different than the current position
- (supplied command length) if cycling through guesses. If -1 is supplied,
- then the completion position will be filled in for the expanded current
- path.
- CommandLength - Supplies the length of the command, not including the null
- terminating byte. Usually the current position is passed in here.
- FileStartPosition - Supplies a pointer where the index into the command
- where the beginning of the file path replacement will be returned.
- FileString - Supplies a pointer where a newly allocated string
- will be returned containing the portion of the command that should be
- expanded.
- PreviousGuess - Supplies a pointer where the previous guess will be
- returned if the completion portion and command length are not the same.
- Return Value:
- TRUE on success.
- FALSE on failure.
- --*/
- {
- PSTR BaseName;
- size_t BaseNameLength;
- CHAR Character;
- ULONG CommandEnd;
- PSTR ExpandedField;
- UINTN ExpandedFieldSize;
- ULONG ExpandOptions;
- PSTR Guess;
- PSTR GuessBaseName;
- UINTN GuessSize;
- ULONG Index;
- PSTR LastField;
- ULONG LastFieldIndex;
- UINTN LastFieldLength;
- CHAR Quote;
- BOOL Result;
- BOOL TrailingSlash;
- BOOL WasBackslash;
- BOOL WasBlank;
- ExpandedField = NULL;
- Guess = NULL;
- GuessBaseName = NULL;
- LastField = NULL;
- Quote = 0;
- WasBackslash = FALSE;
- WasBlank = FALSE;
- LastFieldIndex = 0;
- Result = FALSE;
- if (*CompletionPosition == (ULONG)-1) {
- CommandEnd = CommandLength;
- } else {
- CommandEnd = *CompletionPosition;
- }
- //
- // Loop to find the start of the last field, honoring quotes.
- //
- Index = 0;
- while (Index < CommandEnd) {
- Character = Command[Index];
- if (Quote != 0) {
- if (Quote == '\'') {
- if (Character == '\'') {
- Quote = 0;
- }
- } else if (Quote == '"') {
- if ((Character == '"') && (WasBackslash == FALSE)) {
- Quote = 0;
- }
- }
- } else {
- if (WasBackslash == FALSE) {
- //
- // An unquoted unescaped blank starts a new field.
- //
- if (isblank(Character)) {
- WasBlank = TRUE;
- //
- // This is not a blank. If the last character was, then this is
- // the start of the new field.
- //
- } else {
- if (WasBlank != FALSE) {
- LastFieldIndex = Index;
- }
- WasBlank = FALSE;
- //
- // Look to see if quotes are beginning.
- //
- if ((Character == '\'') || (Character == '"')) {
- Quote = Character;
- }
- }
- }
- }
- if (Character == '\\') {
- WasBackslash = !WasBackslash;
- } else {
- WasBackslash = FALSE;
- }
- Index += 1;
- }
- if (WasBlank != FALSE) {
- LastFieldIndex = CommandEnd;
- }
- //
- // Create a copy of the last field. If the last field is zero in size, use
- // the current directory.
- //
- if ((strlen(Command + LastFieldIndex) == 0) ||
- (LastFieldIndex == CommandEnd)) {
- LastField = strdup("./");
- if (LastField == NULL) {
- goto GetFileCompletionPositionEnd;
- }
- LastFieldLength = 2;
- } else {
- LastField = strdup(Command + LastFieldIndex);
- if (LastField == NULL) {
- goto GetFileCompletionPositionEnd;
- }
- LastFieldLength = CommandEnd - LastFieldIndex;
- LastField[LastFieldLength] = '\0';
- }
- //
- // Escape and control-quote the string. Since it's only tab completion,
- // there's no need for this to be perfect.
- //
- Index = 0;
- WasBackslash = FALSE;
- while (LastField[Index] != '\0') {
- //
- // Handle a double quote region.
- //
- if (LastField[Index] == '\'') {
- LastField[Index] = SHELL_CONTROL_QUOTE;
- do {
- Index += 1;
- } while ((LastField[Index] != '\'') && (LastField[Index] != '\0'));
- if (LastField[Index] == '\'') {
- LastField[Index] = SHELL_CONTROL_QUOTE;
- Index += 1;
- }
- //
- // Handle a single quote region.
- //
- } else if (LastField[Index] == '"') {
- LastField[Index] = SHELL_CONTROL_QUOTE;
- while ((LastField[Index] != '\0') && (LastField[Index] != '"')) {
- if (LastField[Index] == '\\') {
- Index += 1;
- if ((LastField[Index] == '$') ||
- (LastField[Index] == '`') ||
- (LastField[Index] == '"') ||
- (LastField[Index] == '\\') ||
- (LastField[Index] == '\r') ||
- (LastField[Index] == '\n')) {
- LastField[Index - 1] = SHELL_CONTROL_ESCAPE;
- }
- }
- Index += 1;
- }
- if (LastField[Index] == '"') {
- LastField[Index] = SHELL_CONTROL_QUOTE;
- Index += 1;
- }
- } else {
- if (LastField[Index] == '\\') {
- LastField[Index] = SHELL_CONTROL_ESCAPE;
- Index += 1;
- }
- Index += 1;
- }
- }
- //
- // Expand the string.
- //
- ExpandOptions = SHELL_EXPANSION_OPTION_NO_FIELD_SPLIT |
- SHELL_EXPANSION_OPTION_NO_PATH_EXPANSION;
- Result = ShPerformExpansions(Shell,
- LastField,
- LastFieldLength + 1,
- ExpandOptions,
- &ExpandedField,
- &ExpandedFieldSize,
- NULL,
- NULL);
- if (Result == FALSE) {
- goto GetFileCompletionPositionEnd;
- }
- //
- // If the completion position has not been set, this is the first time
- // trying.
- //
- if (*CompletionPosition == (ULONG)-1) {
- *CompletionPosition = LastFieldIndex + ExpandedFieldSize - 1;
- Result = TRUE;
- goto GetFileCompletionPositionEnd;
- }
- GuessSize = CommandLength - LastFieldIndex + 1;
- Guess = malloc(GuessSize);
- if (Guess == NULL) {
- goto GetFileCompletionPositionEnd;
- }
- memcpy(Guess, Command + LastFieldIndex, GuessSize - 1);
- Guess[GuessSize - 1] = '\0';
- //
- // Temporarily remove a trailing slash as it gets basename all flustered.
- //
- TrailingSlash = FALSE;
- if ((GuessSize > 1) && (Guess[GuessSize - 2] == '/')) {
- GuessSize -= 1;
- Guess[GuessSize - 1] = '\0';
- TrailingSlash = TRUE;
- }
- BaseName = basename(Guess);
- if (BaseName == NULL) {
- goto GetFileCompletionPositionEnd;
- }
- BaseNameLength = strlen(BaseName);
- GuessBaseName = malloc(BaseNameLength + 2);
- if (GuessBaseName == NULL) {
- goto GetFileCompletionPositionEnd;
- }
- strcpy(GuessBaseName, BaseName);
- if (TrailingSlash != FALSE) {
- GuessBaseName[BaseNameLength] = '/';
- GuessBaseName[BaseNameLength + 1] = '\0';
- }
- Result = TRUE;
- GetFileCompletionPositionEnd:
- if (LastField != NULL) {
- free(LastField);
- }
- if (Guess != NULL) {
- free(Guess);
- }
- *FileStartPosition = LastFieldIndex;
- *FileString = ExpandedField;
- *PreviousGuess = GuessBaseName;
- return Result;
- }
- PSTR
- ShGetFileReplacementString (
- PSTR UserString,
- PSTR PreviousGuess,
- PSTR *Matches,
- ULONG MatchCount
- )
- /*++
- Routine Description:
- This routine attempts to get the replacement string to the file path
- completion if there is one.
- Arguments:
- UserString - Supplies the query string.
- PreviousGuess - Supplies a pointer to a string containing the previous
- guess for this query.
- Matches - Supplies the array of strings that match.
- MatchCount - Supplies the number of elements in the match array.
- Return Value:
- Returns a pointer to a string representing the complete replacement, which
- will be the first match if there is only one, or the common prefix of all
- matches if there is one and it's longer than the input.
- --*/
- {
- PSTR BaseName;
- size_t BaseNameLength;
- ULONG CharacterIndex;
- ULONG CombinedLength;
- PSTR CombinedString;
- PSTR CurrentMatch;
- PSTR Directory;
- size_t DirectoryLength;
- ULONG LongestPrefix;
- PSTR Match;
- ULONG MatchIndex;
- PSTR QuotedString;
- PSTR UserCopy;
- size_t UserStringLength;
- BaseName = NULL;
- CombinedString = NULL;
- Directory = NULL;
- Match = NULL;
- UserCopy = NULL;
- QuotedString = NULL;
- if ((Matches == NULL) || (MatchCount == 0)) {
- goto GetFileReplacementStringEnd;
- }
- //
- // Get the directory portion and the file name portion of the string.
- //
- UserCopy = strdup(UserString);
- if (UserCopy == NULL) {
- goto GetFileReplacementStringEnd;
- }
- UserStringLength = strlen(UserCopy);
- if ((UserStringLength != 0) && (UserString[UserStringLength - 1] == '/')) {
- BaseName = strdup("");
- Directory = UserCopy;
- UserCopy = NULL;
- } else {
- BaseName = basename(UserCopy);
- if (BaseName == NULL) {
- goto GetFileReplacementStringEnd;
- }
- BaseName = strdup(BaseName);
- //
- // If the given path doesn't have a slash in it, then use an empty
- // directory name as the found results may come from the path.
- //
- if (strchr(UserString, '/') == NULL) {
- Directory = "";
- } else {
- Directory = dirname(UserCopy);
- if (Directory == NULL) {
- goto GetFileReplacementStringEnd;
- }
- }
- Directory = strdup(Directory);
- }
- if ((BaseName == NULL) || (Directory == NULL)) {
- goto GetFileReplacementStringEnd;
- }
- //
- // If there's only one match, use that.
- //
- if (MatchCount == 1) {
- Match = strdup(Matches[0]);
- //
- // If guessing, come up with a guess. If there is no previous guess, then
- // just pick the first one. Otherwise, find the previous guess in the list
- // and then guess the next thing.
- //
- } else if (ShGuessFileMatch != FALSE) {
- if (PreviousGuess == NULL) {
- Match = strdup(Matches[0]);
- } else {
- for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex += 1) {
- CurrentMatch = Matches[MatchIndex];
- if (strcmp(CurrentMatch, PreviousGuess) == 0) {
- break;
- }
- }
- //
- // Advance to the next index. If it goes beyond the end (or was
- // already beyond the end because no match was found, start at the
- // beginning.
- //
- MatchIndex += 1;
- if (MatchIndex >= MatchCount) {
- MatchIndex = 0;
- }
- Match = strdup(Matches[MatchIndex]);
- }
- //
- // There are a bunch of matches. Find the longest common prefix of all
- // the matches.
- //
- } else {
- BaseNameLength = strlen(BaseName);
- LongestPrefix = strlen(Matches[0]);
- for (MatchIndex = 1; MatchIndex < MatchCount; MatchIndex += 1) {
- CurrentMatch = Matches[MatchIndex];
- CharacterIndex = 0;
- while ((CharacterIndex < LongestPrefix) &&
- (CurrentMatch[CharacterIndex] ==
- Matches[0][CharacterIndex])) {
- CharacterIndex += 1;
- }
- //
- // If this match can't keep up for as long, then the new common
- // prefix is his length.
- //
- if (CharacterIndex < LongestPrefix) {
- LongestPrefix = CharacterIndex;
- }
- //
- // Stop bothering to look if it's no longer than the basename.
- //
- if (LongestPrefix <= BaseNameLength) {
- break;
- }
- }
- //
- // If the longest prefix is no longer than the base name, don't return
- // anything.
- //
- if (LongestPrefix <= BaseNameLength) {
- goto GetFileReplacementStringEnd;
- }
- //
- // Create a copy of the longest prefix.
- //
- Match = strdup(Matches[0]);
- if (Match == NULL) {
- goto GetFileReplacementStringEnd;
- }
- Match[LongestPrefix] = '\0';
- }
- //
- // If there's no match, then just cut out now.
- //
- if (Match == NULL) {
- goto GetFileReplacementStringEnd;
- }
- //
- // Create the combined string, which is the user supplied directory name
- // and this match.
- //
- DirectoryLength = strlen(Directory);
- CombinedLength = DirectoryLength + 1 + strlen(Match) + 2;
- CombinedString = malloc(CombinedLength);
- if (CombinedString == NULL) {
- goto GetFileReplacementStringEnd;
- }
- memcpy(CombinedString, Directory, DirectoryLength);
- if ((DirectoryLength != 0) && (Directory[DirectoryLength - 1] != '/')) {
- CombinedString[DirectoryLength] = '/';
- DirectoryLength += 1;
- }
- strcpy(CombinedString + DirectoryLength, Match);
- //
- // Quote the string.
- //
- ShQuoteString(CombinedString, &QuotedString);
- GetFileReplacementStringEnd:
- if (UserCopy != NULL) {
- free(UserCopy);
- }
- if (BaseName != NULL) {
- free(BaseName);
- }
- if (Directory != NULL) {
- free(Directory);
- }
- if (Match != NULL) {
- free(Match);
- }
- if (CombinedString != NULL) {
- free(CombinedString);
- }
- return QuotedString;
- }
- BOOL
- ShQuoteString (
- PSTR String,
- PSTR *QuotedString
- )
- /*++
- Routine Description:
- This routine quotes a given string.
- Arguments:
- String - Supplies a pointer to the string to quote.
- QuotedString - Supplies a pointer where an allocated string will be
- returned. The caller is responsible for freeing this memory.
- Return Value:
- TRUE on success.
- FALSE on allocation failure.
- --*/
- {
- CHAR Character;
- PSTR Final;
- ULONG FinalIndex;
- ULONG InputIndex;
- size_t InputLength;
- BOOL NeedsBackslash;
- *QuotedString = NULL;
- InputLength = strlen(String);
- //
- // Allocate a new buffer for the final string. In the worst case every
- // character needs a backslash.
- //
- Final = malloc(InputLength * 2);
- if (Final == NULL) {
- return FALSE;
- }
- FinalIndex = 0;
- InputIndex = 0;
- while (InputIndex < InputLength) {
- Character = String[InputIndex];
- InputIndex += 1;
- NeedsBackslash = TRUE;
- if ((isalnum(Character)) || (Character == '.') || (Character == '_') ||
- (Character == '/') || (Character == ',') || (Character == '+') ||
- (Character == '-')) {
- NeedsBackslash = FALSE;
- }
- if (NeedsBackslash != FALSE) {
- Final[FinalIndex] = '\\';
- FinalIndex += 1;
- }
- Final[FinalIndex] = Character;
- FinalIndex += 1;
- }
- Final[FinalIndex] = '\0';
- assert(InputIndex < InputLength * 2);
- *QuotedString = Final;
- return TRUE;
- }
- VOID
- ShAddCommandHistoryEntry (
- PSTR Command
- )
- /*++
- Routine Description:
- This routine adds a command to the command history.
- Arguments:
- Command - Supplies a pointer to the command string to add.
- Return Value:
- None.
- --*/
- {
- PSTR Copy;
- INT PreviousIndex;
- INT Result;
- Copy = NULL;
- Result = ENOMEM;
- if (ShCommandHistory == NULL) {
- if (ShCommandHistorySize == 0) {
- Result = 0;
- goto ShAddCommandHistoryEntryEnd;
- }
- ShCommandHistory = malloc(ShCommandHistorySize * sizeof(PSTR));
- if (ShCommandHistory == NULL) {
- goto ShAddCommandHistoryEntryEnd;
- }
- memset(ShCommandHistory, 0, ShCommandHistorySize * sizeof(PSTR));
- }
- assert(ShCommandHistoryIndex < ShCommandHistorySize);
- //
- // Don't add the command if it's exactly the same as the last one.
- //
- PreviousIndex = ShCommandHistoryIndex - 1;
- if (PreviousIndex < 0) {
- PreviousIndex += ShCommandHistorySize;
- }
- if ((ShCommandHistory[PreviousIndex] != NULL) &&
- (strcmp(Command, ShCommandHistory[PreviousIndex]) == 0)) {
- Result = 0;
- goto ShAddCommandHistoryEntryEnd;
- }
- Copy = strdup(Command);
- if (Copy == NULL) {
- goto ShAddCommandHistoryEntryEnd;
- }
- if (ShCommandHistory[ShCommandHistoryIndex] != NULL) {
- free(ShCommandHistory[ShCommandHistoryIndex]);
- }
- ShCommandHistory[ShCommandHistoryIndex] = Copy;
- ShCommandHistoryIndex += 1;
- if (ShCommandHistoryIndex >= ShCommandHistorySize) {
- ShCommandHistoryIndex = 0;
- }
- Result = 0;
- ShAddCommandHistoryEntryEnd:
- if (Result != 0) {
- if (Copy != NULL) {
- free(Copy);
- }
- }
- return;
- }
- PSTR
- ShGetCommandHistoryEntry (
- LONG Offset
- )
- /*++
- Routine Description:
- This routine cleans the current line and resets the position to zero.
- Arguments:
- Offset - Supplies the offset from the most recent command to reach for.
- Return Value:
- Returns a pointer to a newly allocated string containing the historical
- command on success.
- NULL on failure or if there is no command that far back.
- --*/
- {
- PSTR Copy;
- LONG Index;
- if ((Offset < 0) || (Offset > ShCommandHistorySize)) {
- return NULL;
- }
- if (ShCommandHistory == NULL) {
- return NULL;
- }
- Index = ShCommandHistoryIndex - Offset;
- while (Index < 0) {
- Index += ShCommandHistorySize;
- }
- if (ShCommandHistory[Index] == NULL) {
- return NULL;
- }
- Copy = strdup(ShCommandHistory[Index]);
- return Copy;
- }
- VOID
- ShCleanLine (
- FILE *Output,
- ULONG Position,
- ULONG CommandLength
- )
- /*++
- Routine Description:
- This routine cleans the current line and resets the position to zero.
- Arguments:
- Output - Supplies a pointer to the output file stream.
- Position - Supplies the current position index.
- CommandLength - Supplies the length of the command.
- Return Value:
- None.
- --*/
- {
- if (CommandLength != 0) {
- if (Position != 0) {
- SwMoveCursorRelative(Output, -Position, NULL);
- Position = 0;
- }
- ShPrintSpaces(Output, CommandLength);
- SwMoveCursorRelative(Output, -CommandLength, NULL);
- CommandLength = 0;
- }
- return;
- }
- VOID
- ShPrintSpaces (
- FILE *Output,
- INT Count
- )
- /*++
- Routine Description:
- This routine simply prints spaces.
- Arguments:
- Output - Supplies a pointer to the output stream to write to.
- Count - Supplies the number of spaces to print.
- Return Value:
- None.
- --*/
- {
- INT FillCount;
- CHAR Spaces[50];
- FillCount = sizeof(Spaces);
- if (FillCount > Count) {
- FillCount = Count;
- }
- memset(Spaces, ' ', FillCount);
- while (Count > FillCount) {
- fwrite(Spaces, 1, FillCount, Output);
- Count -= FillCount;
- }
- fwrite(Spaces, 1, Count, Output);
- return;
- }
- BOOL
- ShAddStringToArray (
- PSTR **Array,
- PSTR Entry,
- PULONG ArraySize,
- PULONG ArrayCapacity
- )
- /*++
- Routine Description:
- This routine adds a string to the given string array.
- Arguments:
- Array - Supplies a pointer that on input contains a pointer to the string
- array. On output, this may be updated if the string array is
- reallocated.
- Entry - Supplies the string entry to add. A copy of this string will be
- made.
- ArraySize - Supplies a pointer that on input contains the size of the array,
- in elements. This value will be incremented on success.
- ArrayCapacity - Supplies a pointer that on input contains the array
- capacity. This value will be updated if the array is reallocated.
- Return Value:
- TRUE on success.
- FALSE on failure.
- --*/
- {
- PSTR EntryCopy;
- PSTR *NewBuffer;
- ULONG NewCapacity;
- if (*ArraySize >= *ArrayCapacity) {
- if (*ArrayCapacity == 0) {
- NewCapacity = INITIAL_STRING_ARRAY_SIZE;
- } else {
- NewCapacity = *ArrayCapacity * 2;
- }
- NewBuffer = realloc(*Array, NewCapacity * sizeof(PSTR));
- if (NewBuffer == NULL) {
- return FALSE;
- }
- *Array = NewBuffer;
- *ArrayCapacity = NewCapacity;
- }
- EntryCopy = strdup(Entry);
- if (EntryCopy == NULL) {
- return FALSE;
- }
- (*Array)[*ArraySize] = EntryCopy;
- *ArraySize += 1;
- return TRUE;
- }
- BOOL
- ShMergeStringArrays (
- PSTR **Array1,
- PULONG Array1Size,
- PULONG Array1Capacity,
- PSTR *Array2,
- ULONG Array2Size
- )
- /*++
- Routine Description:
- This routine merges two string arrays.
- Arguments:
- Array1 - Supplies a pointer that on input contains a pointer to the
- destination string array. On output, this may be updated if the string
- array is reallocated.
- Array1Size - Supplies a pointer that on input contains the size of the
- primary array, in elements. This value will be incremented on success.
- Array1Capacity - Supplies a pointer that on input contains the primary array
- capacity. This value will be updated if the array is reallocated.
- Array2 - Supplies the array of strings to add to the primary array. After
- this function this array will be destroyed.
- Array2Size - Supplies the number of elements in the second array.
- Return Value:
- TRUE on success.
- FALSE on failure.
- --*/
- {
- ULONG Array2Index;
- PSTR *NewBuffer;
- ULONG NewCapacity;
- if (Array2 == NULL) {
- return TRUE;
- }
- if (*Array1Size + Array2Size >= *Array1Capacity) {
- NewCapacity = INITIAL_STRING_ARRAY_SIZE;
- while (NewCapacity < *Array1Size + Array2Size) {
- NewCapacity *= 2;
- }
- NewBuffer = realloc(*Array1, NewCapacity * sizeof(PSTR));
- if (NewBuffer == NULL) {
- return FALSE;
- }
- *Array1 = NewBuffer;
- *Array1Capacity = NewCapacity;
- }
- for (Array2Index = 0; Array2Index < Array2Size; Array2Index += 1) {
- (*Array1)[*Array1Size + Array2Index] = Array2[Array2Index];
- Array2[Array2Index] = NULL;
- }
- free(Array2);
- *Array1Size += Array2Size;
- return TRUE;
- }
- VOID
- ShRemoveDuplicateFileMatches (
- PSTR *Array,
- PULONG ArraySize
- )
- /*++
- Routine Description:
- This routine removes duplicates from the given array. This routine assumes
- the array has already been sorted.
- Arguments:
- Array - Supplies the array of strings.
- ArraySize - Supplies a pointer that on input contains the number of
- elements in the string array. On output, returns the potentially
- reduced number after duplicates have been removed.
- Return Value:
- None.
- --*/
- {
- ULONG Index;
- ULONG MoveIndex;
- for (Index = 1; Index < *ArraySize; Index += 1) {
- while ((Index < *ArraySize) &&
- (strcmp(Array[Index], Array[Index - 1]) == 0)) {
- free(Array[Index]);
- //
- // Move the other entries down.
- //
- for (MoveIndex = Index;
- MoveIndex < *ArraySize - 1;
- MoveIndex += 1) {
- Array[MoveIndex] = Array[MoveIndex + 1];
- }
- *ArraySize -= 1;
- }
- }
- return;
- }
- VOID
- ShDestroyStringArray (
- PSTR *Array,
- ULONG ArraySize
- )
- /*++
- Routine Description:
- This routine destroys a string array. This routine will free both the
- array and each element.
- Arguments:
- Array - Supplies a pointer to the array of strings.
- ArraySize - Supplies the number of elements in the string array.
- Return Value:
- None.
- --*/
- {
- ULONG Index;
- if (Array == NULL) {
- return;
- }
- for (Index = 0; Index < ArraySize; Index += 1) {
- if (Array[Index] != NULL) {
- free(Array[Index]);
- }
- }
- free(Array);
- return;
- }
- int
- ShCompareStringArrayElements (
- const void *LeftElement,
- const void *RightElement
- )
- /*++
- Routine Description:
- This routine compares two pointers to strings. This prototype is
- compatible with the qsort function compare routine.
- Arguments:
- LeftElement - Supplies a pointer where a pointer to the left string resides.
- RightElement - Supplies a pointer where a pointer to the right string
- resides.
- Return Value:
- <0 if the left is less than the right.
- 0 if the left is equal to the right.
- >0 if th left is greater than the right.
- --*/
- {
- PSTR LeftString;
- int Result;
- PSTR RightString;
- LeftString = *((PSTR *)LeftElement);
- RightString = *((PSTR *)RightElement);
- Result = strcasecmp(LeftString, RightString);
- return Result;
- }
|