1
0

linein.c 57 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477
  1. /*++
  2. Copyright (c) 2013 Minoca Corp. All Rights Reserved
  3. Module Name:
  4. linein.c
  5. Abstract:
  6. This module implements user input functionality.
  7. Author:
  8. Evan Green 16-Mar-2013
  9. Environment:
  10. User
  11. --*/
  12. //
  13. // ------------------------------------------------------------------- Includes
  14. //
  15. #include <assert.h>
  16. #include <ctype.h>
  17. #include <errno.h>
  18. #include <libgen.h>
  19. #include <stdlib.h>
  20. #include <string.h>
  21. #include <unistd.h>
  22. #include "sh.h"
  23. #include "../swlib.h"
  24. #include <minoca/lib/termlib.h>
  25. //
  26. // ---------------------------------------------------------------- Definitions
  27. //
  28. //
  29. // Define the size of the string initially allocated for the line. This should
  30. // be big enough that "most" commands succeed without expansion.
  31. //
  32. #define INITIAL_COMMAND_LENGTH 10
  33. //
  34. // Define the maximum number of bytes that could constitute one (control)
  35. // character.
  36. //
  37. #define MAX_CHARACTER_LENGTH 10
  38. //
  39. // Define the number of lines page up and page down scroll by.
  40. //
  41. #define SCROLL_LINE_COUNT 10
  42. //
  43. // Define the Control+C character.
  44. //
  45. #define CONTROL_C_CHARACTER 3
  46. //
  47. // Define the default size of the command history.
  48. //
  49. #define DEFAULT_COMMAND_HISTORY_SIZE 50
  50. #define INITIAL_STRING_ARRAY_SIZE 16
  51. #define COMPLETION_COLUMN_PADDING 2
  52. #define COMPLETION_DEFAULT_TERMINAL_WIDTH 80
  53. //
  54. // ------------------------------------------------------ Data Type Definitions
  55. //
  56. //
  57. // ----------------------------------------------- Internal Function Prototypes
  58. //
  59. VOID
  60. ShCompleteFilePath (
  61. PSHELL Shell,
  62. PSTR *Command,
  63. PULONG CommandLength,
  64. PULONG CommandSize,
  65. PULONG CompletionPosition,
  66. PULONG Position
  67. );
  68. VOID
  69. ShGetFileMatches (
  70. PSTR File,
  71. PSTR **Matches,
  72. PULONG MatchCount
  73. );
  74. VOID
  75. ShGetFileMatchesInDirectory (
  76. PSTR FileName,
  77. PSTR DirectoryName,
  78. PSTR **Matches,
  79. PULONG MatchCount
  80. );
  81. BOOL
  82. ShGetFileCompletionPortion (
  83. PSHELL Shell,
  84. PSTR Command,
  85. PULONG CompletionPosition,
  86. ULONG CommandLength,
  87. PULONG FileStartPosition,
  88. PSTR *FileString,
  89. PSTR *PreviousGuess
  90. );
  91. PSTR
  92. ShGetFileReplacementString (
  93. PSTR UserString,
  94. PSTR PreviousGuess,
  95. PSTR *Matches,
  96. ULONG MatchCount
  97. );
  98. BOOL
  99. ShQuoteString (
  100. PSTR String,
  101. PSTR *QuotedString
  102. );
  103. VOID
  104. ShAddCommandHistoryEntry (
  105. PSTR Command
  106. );
  107. PSTR
  108. ShGetCommandHistoryEntry (
  109. LONG Offset
  110. );
  111. VOID
  112. ShCleanLine (
  113. FILE *Output,
  114. ULONG Position,
  115. ULONG CommandLength
  116. );
  117. VOID
  118. ShPrintSpaces (
  119. FILE *Output,
  120. INT Count
  121. );
  122. BOOL
  123. ShAddStringToArray (
  124. PSTR **Array,
  125. PSTR Entry,
  126. PULONG ArraySize,
  127. PULONG ArrayCapacity
  128. );
  129. BOOL
  130. ShMergeStringArrays (
  131. PSTR **Array1,
  132. PULONG Array1Size,
  133. PULONG Array1Capacity,
  134. PSTR *Array2,
  135. ULONG Array2Size
  136. );
  137. VOID
  138. ShRemoveDuplicateFileMatches (
  139. PSTR *Array,
  140. PULONG ArraySize
  141. );
  142. VOID
  143. ShDestroyStringArray (
  144. PSTR *Array,
  145. ULONG ArraySize
  146. );
  147. int
  148. ShCompareStringArrayElements (
  149. const void *LeftElement,
  150. const void *RightElement
  151. );
  152. //
  153. // -------------------------------------------------------------------- Globals
  154. //
  155. CHAR ShBackspaceCharacter = 0x7F;
  156. CHAR ShKillLineCharacter = 0x0B;
  157. //
  158. // Define the size of the command history.
  159. //
  160. LONG ShCommandHistorySize = DEFAULT_COMMAND_HISTORY_SIZE;
  161. PSTR *ShCommandHistory;
  162. LONG ShCommandHistoryIndex;
  163. //
  164. // Set this flag to enable printing of file completion suggestions in color.
  165. //
  166. BOOL ShColorFileSuggestions = TRUE;
  167. //
  168. // Set this flag to "guess" when there are multiple file matches, and then
  169. // cycle through the guesses.
  170. //
  171. BOOL ShGuessFileMatch = TRUE;
  172. //
  173. // ------------------------------------------------------------------ Functions
  174. //
  175. BOOL
  176. ShReadLine (
  177. PSHELL Shell,
  178. PSTR *ReturnedCommand,
  179. PULONG ReturnedCommandLength
  180. )
  181. /*++
  182. Routine Description:
  183. This routine reads a command in from the user.
  184. Arguments:
  185. Shell - Supplies a pointer to the shell.
  186. ReturnedCommand - Supplies a pointer where a pointer to a null terminated
  187. string (allocated via malloc) containing the command will be returned.
  188. This may be set to NULL if the end of file indicator was returned from
  189. stdin.
  190. ReturnedCommandLength - Supplies a pointer where the size of the command
  191. being returned will be returned, including the null terminator, in
  192. bytes.
  193. Return Value:
  194. TRUE on success.
  195. FALSE on failure.
  196. --*/
  197. {
  198. ULONG BaseOffset;
  199. int Byte;
  200. ULONG ByteIndex;
  201. PSTR Command;
  202. ULONG CommandLength;
  203. ULONG CommandSize;
  204. ULONG CompletionPosition;
  205. PSTR HistoryEntry;
  206. LONG HistoryOffset;
  207. TERMINAL_KEY_DATA KeyData;
  208. PSTR NewBuffer;
  209. ULONG NewCommandSize;
  210. FILE *Output;
  211. TERMINAL_PARSE_RESULT ParseResult;
  212. ULONG Position;
  213. BOOL Result;
  214. CommandLength = 0;
  215. CompletionPosition = (ULONG)-1;
  216. HistoryEntry = NULL;
  217. HistoryOffset = 0;
  218. Output = Shell->NonStandardError;
  219. Result = TRUE;
  220. Position = 0;
  221. memset(&KeyData, 0, sizeof(TERMINAL_KEY_DATA));
  222. ShSetTerminalMode(Shell, TRUE);
  223. //
  224. // Allocate the initial command buffer.
  225. //
  226. CommandSize = INITIAL_COMMAND_LENGTH;
  227. Command = malloc(CommandSize);
  228. if (Command == NULL) {
  229. Result = FALSE;
  230. goto ReadLineEnd;
  231. }
  232. Command[0] = '\0';
  233. //
  234. // Loop reading in characters.
  235. //
  236. while (TRUE) {
  237. fflush(Output);
  238. Byte = SwReadInputCharacter();
  239. if (Byte == -1) {
  240. printf("sh: Input error: %s\n", strerror(errno));
  241. break;
  242. }
  243. //
  244. // A newline signals the end of the command.
  245. //
  246. if ((Byte == '\n') || (Byte == '\r')) {
  247. break;
  248. }
  249. //
  250. // A Control-C aborts the current command.
  251. //
  252. if (Byte == CONTROL_C_CHARACTER) {
  253. fputs("^C\n", Output);
  254. Result = FALSE;
  255. goto ReadLineEnd;
  256. }
  257. ParseResult = TermProcessInput(&KeyData, Byte);
  258. switch (ParseResult) {
  259. case TerminalParseResultNormalCharacter:
  260. //
  261. // Handle a backspace.
  262. //
  263. if (Byte == ShBackspaceCharacter) {
  264. Byte = -1;
  265. //
  266. // If there is already nothing, throw this out.
  267. //
  268. if (Position == 0) {
  269. continue;
  270. }
  271. //
  272. // Adjust the buffer for the backspace. The standard memory
  273. // copy function cannot be used since the buffers are
  274. // overlapping and so copy order matters.
  275. //
  276. Position -= 1;
  277. CommandLength -= 1;
  278. for (ByteIndex = Position;
  279. ByteIndex < CommandLength;
  280. ByteIndex += 1) {
  281. Command[ByteIndex] = Command[ByteIndex + 1];
  282. }
  283. //
  284. // Go back one and print the shifted command, plus a space to
  285. // cover up the old ending character.
  286. //
  287. assert(Command[CommandLength + 1] == '\0');
  288. Command[CommandLength] = ' ';
  289. SwMoveCursorRelative(Output, -1, NULL);
  290. fwrite(Command + Position,
  291. CommandLength - Position + 1,
  292. 1,
  293. Output);
  294. Command[CommandLength] = '\0';
  295. //
  296. // But oh dear, now the cursor is at the end of the line. Print
  297. // backspaces to get it back.
  298. //
  299. SwMoveCursorRelative(Output,
  300. -(CommandLength - Position + 1),
  301. NULL);
  302. //
  303. // Tabs do file completion.
  304. //
  305. } else if (Byte == '\t') {
  306. ShCompleteFilePath(Shell,
  307. &Command,
  308. &CommandLength,
  309. &CommandSize,
  310. &CompletionPosition,
  311. &Position);
  312. Byte = -1;
  313. //
  314. // In the case where the returned completion position is not the
  315. // same as the current position, continue directly to avoid
  316. // updating it, so that pressing tab again guesses from the
  317. // previous completion position.
  318. //
  319. if (CompletionPosition != (ULONG)-1) {
  320. continue;
  321. }
  322. //
  323. // Handle the kill line character.
  324. //
  325. } else if (Byte == ShKillLineCharacter) {
  326. ShCleanLine(Output, Position, CommandLength);
  327. Position = 0;
  328. CommandLength = 0;
  329. Command[0] = '\0';
  330. Byte = -1;
  331. } else if (iscntrl(Byte)) {
  332. Byte = -1;
  333. }
  334. break;
  335. case TerminalParseResultPartialCommand:
  336. Byte = -1;
  337. break;
  338. case TerminalParseResultCompleteCommand:
  339. Byte = -1;
  340. //
  341. // Handle command history entries.
  342. //
  343. switch (KeyData.Key) {
  344. case TerminalKeyUp:
  345. HistoryEntry = ShGetCommandHistoryEntry(HistoryOffset + 1);
  346. if (HistoryEntry != NULL) {
  347. HistoryOffset += 1;
  348. }
  349. break;
  350. case TerminalKeyDown:
  351. HistoryEntry = ShGetCommandHistoryEntry(HistoryOffset - 1);
  352. if (HistoryEntry != NULL) {
  353. HistoryOffset -= 1;
  354. }
  355. break;
  356. case TerminalKeyRight:
  357. if (Position < CommandLength) {
  358. SwMoveCursorRelative(Output, 1, &(Command[Position]));
  359. Position += 1;
  360. }
  361. break;
  362. case TerminalKeyLeft:
  363. if (Position != 0) {
  364. Position -= 1;
  365. SwMoveCursorRelative(Output, -1, NULL);
  366. }
  367. break;
  368. case TerminalKeyHome:
  369. SwMoveCursorRelative(Output, -Position, NULL);
  370. Position = 0;
  371. break;
  372. case TerminalKeyEnd:
  373. SwMoveCursorRelative(Output,
  374. CommandLength - Position,
  375. &(Command[Position]));
  376. Position = CommandLength;
  377. break;
  378. case TerminalKeyDelete:
  379. if ((CommandLength == 0) || (Position == CommandLength)) {
  380. break;
  381. }
  382. CommandLength -= 1;
  383. for (ByteIndex = Position;
  384. ByteIndex < CommandLength;
  385. ByteIndex += 1) {
  386. Command[ByteIndex] = Command[ByteIndex + 1];
  387. }
  388. //
  389. // Go back one and print the shifted command, plus a space
  390. // to cover up the old ending character.
  391. //
  392. assert(Command[CommandLength + 1] == '\0');
  393. Command[CommandLength] = ' ';
  394. fwrite(Command + Position,
  395. CommandLength - Position + 1,
  396. 1,
  397. Output);
  398. Command[CommandLength] = '\0';
  399. //
  400. // But oh dear, now the cursor is at the end of the line.
  401. // Print backspaces to get it back.
  402. //
  403. SwMoveCursorRelative(Output,
  404. -(CommandLength - Position + 1),
  405. NULL);
  406. break;
  407. case TerminalKeyPageUp:
  408. SwScrollTerminal(-SCROLL_LINE_COUNT);
  409. break;
  410. case TerminalKeyPageDown:
  411. SwScrollTerminal(SCROLL_LINE_COUNT);
  412. break;
  413. default:
  414. break;
  415. }
  416. break;
  417. default:
  418. assert(FALSE);
  419. break;
  420. }
  421. CompletionPosition = (ULONG)-1;
  422. //
  423. // If a history entry was found, then kill the current line and use it.
  424. //
  425. if (HistoryEntry != NULL) {
  426. Byte = -1;
  427. ShCleanLine(Output, Position, CommandLength);
  428. if (Command != NULL) {
  429. free(Command);
  430. }
  431. Command = HistoryEntry;
  432. CommandLength = strlen(Command);
  433. CommandSize = CommandLength + 1;
  434. Position = CommandLength;
  435. fwrite(Command, 1, CommandLength, Output);
  436. HistoryEntry = NULL;
  437. }
  438. //
  439. // If after all the processing there are no new characters, loop on to
  440. // get more.
  441. //
  442. if (Byte == -1) {
  443. continue;
  444. }
  445. //
  446. // There are characters in the character buffer, move them to the
  447. // command. Start by seeing if adding these characters would overrun
  448. // the buffer, and expand if so.
  449. //
  450. if (CommandLength + 2 > CommandSize) {
  451. NewCommandSize = CommandSize * 2;
  452. if (NewCommandSize < INITIAL_COMMAND_LENGTH) {
  453. NewCommandSize = INITIAL_COMMAND_LENGTH;
  454. }
  455. assert(CommandLength + 2 < NewCommandSize);
  456. NewBuffer = realloc(Command, NewCommandSize);
  457. if (NewBuffer == NULL) {
  458. Result = FALSE;
  459. goto ReadLineEnd;
  460. }
  461. CommandSize = NewCommandSize;
  462. Command = NewBuffer;
  463. }
  464. //
  465. // If the current position is not at the end of the string, then
  466. // room will need to be made. The standard memory copy routines cannot
  467. // be used since the copy buffers are overlapping, and therefore copy
  468. // order matters.
  469. //
  470. if (Position != CommandLength) {
  471. BaseOffset = CommandLength - 1;
  472. for (ByteIndex = 0;
  473. ByteIndex < CommandLength - Position;
  474. ByteIndex += 1) {
  475. Command[BaseOffset + 1 - ByteIndex] =
  476. Command[BaseOffset - ByteIndex];
  477. }
  478. }
  479. Command[Position] = Byte;
  480. //
  481. // Write out the remainder of the line.
  482. //
  483. CommandLength += 1;
  484. fwrite(Command + Position, CommandLength - Position, 1, Output);
  485. Position += 1;
  486. if (Position != CommandLength) {
  487. SwMoveCursorRelative(Output, -(CommandLength - Position), 0);
  488. }
  489. Byte = -1;
  490. Command[CommandLength] = '\0';
  491. }
  492. assert(Command[CommandLength] == '\0');
  493. //
  494. // If the current position is not the end, move to the end.
  495. //
  496. if (Position != CommandLength) {
  497. SwMoveCursorRelative(Output,
  498. CommandLength - Position,
  499. &(Command[Position]));
  500. }
  501. fputc('\n', Output);
  502. CommandLength += 1;
  503. ShAddCommandHistoryEntry(Command);
  504. ReadLineEnd:
  505. fflush(Output);
  506. ShSetTerminalMode(Shell, FALSE);
  507. if (Result == FALSE) {
  508. CommandLength = 0;
  509. if (Command != NULL) {
  510. free(Command);
  511. Command = NULL;
  512. }
  513. }
  514. *ReturnedCommand = Command;
  515. *ReturnedCommandLength = CommandLength;
  516. return Result;
  517. }
  518. //
  519. // --------------------------------------------------------- Internal Functions
  520. //
  521. VOID
  522. ShCompleteFilePath (
  523. PSHELL Shell,
  524. PSTR *Command,
  525. PULONG CommandLength,
  526. PULONG CommandSize,
  527. PULONG CompletionPosition,
  528. PULONG Position
  529. )
  530. /*++
  531. Routine Description:
  532. This routine performs file path completion in response to a tab character.
  533. Arguments:
  534. Shell - Supplies a pointer to the shell context.
  535. Command - Supplies a pointer that on input contains a pointer to the
  536. command so far. On output this will return the potentially reallocated
  537. completed command.
  538. CommandLength - Supplies a pointer that on input contains the command
  539. length. This will be updated to reflect any changes.
  540. CommandSize - Supplies a pointer that on input contains the size of the
  541. command buffer's allocation. On output this will contain the updated
  542. value if the command was reallocated.
  543. CompletionPosition - Supplies a pointer that on input contains the index to
  544. stem completions off of. This may be different from the current
  545. position if cycling through guesses. This may be updated if a
  546. definitive selection is chosen.
  547. Position - Supplies a pointer that on input contains the position within
  548. the command. On output this may be updated.
  549. Return Value:
  550. None.
  551. --*/
  552. {
  553. UINTN BigCommandCapacity;
  554. UINTN BigCommandSize;
  555. ULONG ColumnCount;
  556. ULONG ColumnIndex;
  557. size_t ColumnSize;
  558. int ConsoleWidth;
  559. ULONG FileStartIndex;
  560. PSTR Match;
  561. ULONG MatchCount;
  562. PSTR *Matches;
  563. ULONG MatchIndex;
  564. size_t MatchLength;
  565. LONG Offset;
  566. FILE *Output;
  567. PSTR PreviousGuess;
  568. ULONG ReplacedSize;
  569. PSTR Replacement;
  570. size_t ReplacementSize;
  571. BOOL Result;
  572. ULONG RoundedCount;
  573. PSTR *RoundedMatches;
  574. ULONG RowCount;
  575. PSTR UserString;
  576. Matches = NULL;
  577. MatchCount = 0;
  578. Output = Shell->NonStandardError;
  579. PreviousGuess = NULL;
  580. Replacement = NULL;
  581. RoundedMatches = NULL;
  582. UserString = NULL;
  583. //
  584. // Restore the terminal mode since the shell may execute expansions.
  585. //
  586. ShSetTerminalMode(Shell, FALSE);
  587. Result = ShGetFileCompletionPortion(Shell,
  588. *Command,
  589. CompletionPosition,
  590. *Position,
  591. &FileStartIndex,
  592. &UserString,
  593. &PreviousGuess);
  594. if (Result == FALSE) {
  595. goto CompleteFilePathEnd;
  596. }
  597. ShGetFileMatches(UserString, &Matches, &MatchCount);
  598. if ((Matches == NULL) || (MatchCount == 0)) {
  599. goto CompleteFilePathEnd;
  600. }
  601. //
  602. // Sort the match array alphabetically.
  603. //
  604. if (MatchCount > 1) {
  605. qsort(Matches, MatchCount, sizeof(PSTR), ShCompareStringArrayElements);
  606. ShRemoveDuplicateFileMatches(Matches, &MatchCount);
  607. }
  608. //
  609. // Using the matches, attempt to come up with a replacement string.
  610. //
  611. Replacement = ShGetFileReplacementString(UserString,
  612. PreviousGuess,
  613. Matches,
  614. MatchCount);
  615. //
  616. // Perform the replacement if there was one.
  617. //
  618. if (Replacement != NULL) {
  619. BigCommandSize = *CommandLength;
  620. BigCommandCapacity = *CommandSize;
  621. if (BigCommandSize < BigCommandCapacity) {
  622. BigCommandSize += 1;
  623. }
  624. ReplacedSize = *Position - FileStartIndex;
  625. ReplacementSize = strlen(Replacement);
  626. assert((*Command)[*CommandLength] == '\0');
  627. Result = SwStringReplaceRegion(Command,
  628. &BigCommandSize,
  629. &BigCommandCapacity,
  630. FileStartIndex,
  631. FileStartIndex + ReplacedSize,
  632. Replacement,
  633. ReplacementSize + 1);
  634. if (Result == FALSE) {
  635. goto CompleteFilePathEnd;
  636. }
  637. *CommandLength = BigCommandSize - 1;
  638. *CommandSize = BigCommandCapacity;
  639. assert((*Command)[*CommandLength] == '\0');
  640. //
  641. // Redraw the command. Go back to the beginning of the replacement
  642. // region, print the command, print spaces for any leftover parts, then
  643. // back up over the spaces.
  644. //
  645. SwMoveCursorRelative(Output, -(*Position - FileStartIndex), NULL);
  646. fprintf(Output, "%s", *Command + FileStartIndex);
  647. Offset = 0;
  648. if (ReplacedSize > ReplacementSize) {
  649. Offset = ReplacedSize - ReplacementSize;
  650. ShPrintSpaces(Output, Offset);
  651. }
  652. //
  653. // The negative of the offset gets back to the end of the new command.
  654. // Back up to the beginning, then forward to the end of the replacement.
  655. //
  656. Offset += *CommandLength - (FileStartIndex + ReplacementSize);
  657. if (Offset != 0) {
  658. SwMoveCursorRelative(Output, -Offset, NULL);
  659. }
  660. *Position += ReplacementSize - ReplacedSize;
  661. }
  662. //
  663. // If the match was ambiguous and this is the first time taking a stab at
  664. // it, print the options.
  665. //
  666. if ((MatchCount > 1) && (PreviousGuess == NULL)) {
  667. //
  668. // Figure out the column size to display these matches.
  669. //
  670. ColumnSize = COMPLETION_COLUMN_PADDING;
  671. for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex += 1) {
  672. MatchLength = strlen(Matches[MatchIndex]);
  673. if (ColumnSize < MatchLength + COMPLETION_COLUMN_PADDING) {
  674. ColumnSize = MatchLength + COMPLETION_COLUMN_PADDING;
  675. }
  676. }
  677. Result = SwGetTerminalDimensions(&ConsoleWidth, NULL);
  678. if (Result != 0) {
  679. ConsoleWidth = COMPLETION_DEFAULT_TERMINAL_WIDTH;
  680. }
  681. ColumnCount = (ConsoleWidth - 1) / ColumnSize;
  682. if (ColumnCount == 0) {
  683. ColumnCount = 1;
  684. }
  685. RowCount = MatchCount / ColumnCount;
  686. if ((MatchCount % ColumnCount) != 0) {
  687. RowCount += 1;
  688. }
  689. //
  690. // Potentially reallocate a rounded up rectangular array.
  691. //
  692. RoundedMatches = Matches;
  693. RoundedCount = MatchCount;
  694. if (MatchCount > ColumnCount) {
  695. RoundedCount = RowCount * ColumnCount;
  696. if (RoundedCount > MatchCount) {
  697. RoundedMatches = malloc(RoundedCount * sizeof(PSTR));
  698. if (RoundedMatches == NULL) {
  699. goto CompleteFilePathEnd;
  700. }
  701. memcpy(RoundedMatches, Matches, MatchCount * sizeof(PSTR));
  702. memset(RoundedMatches + MatchCount,
  703. 0,
  704. (RoundedCount - MatchCount) * sizeof(PSTR));
  705. }
  706. Result = SwRotatePointerArray((PVOID *)RoundedMatches,
  707. ColumnCount,
  708. RowCount);
  709. if (Result == FALSE) {
  710. goto CompleteFilePathEnd;
  711. }
  712. }
  713. if (*Position != *CommandLength) {
  714. SwMoveCursorRelative(Output,
  715. *CommandLength - *Position,
  716. *Command + *Position);
  717. }
  718. fputc('\n', Output);
  719. ColumnIndex = 0;
  720. for (MatchIndex = 0; MatchIndex < RoundedCount; MatchIndex += 1) {
  721. Match = RoundedMatches[MatchIndex];
  722. if (Match == NULL) {
  723. if ((MatchIndex == 0) ||
  724. (RoundedMatches[MatchIndex - 1] != NULL)) {
  725. fputc('\n', Output);
  726. }
  727. ColumnIndex = 0;
  728. continue;
  729. }
  730. MatchLength = strlen(Match);
  731. if ((MatchLength != 0) && (Match[MatchLength - 1] == '/') &&
  732. (ShColorFileSuggestions != FALSE)) {
  733. SwPrintInColor(ConsoleColorDefault,
  734. ConsoleColorBlue,
  735. "%-*s",
  736. ColumnSize,
  737. Match);
  738. } else {
  739. fprintf(Output, "%-*s", ColumnSize, Match);
  740. }
  741. ColumnIndex += 1;
  742. if (ColumnIndex == ColumnCount) {
  743. fputc('\n', Output);
  744. ColumnIndex = 0;
  745. }
  746. }
  747. if (ColumnIndex != 0) {
  748. fputc('\n', Output);
  749. }
  750. //
  751. // Reprint the command.
  752. //
  753. fprintf(Output, "%s", Shell->Prompt);
  754. fprintf(Output, "%s", *Command);
  755. if (*Position != *CommandLength) {
  756. SwMoveCursorRelative(Output, -(*CommandLength - *Position), NULL);
  757. }
  758. }
  759. CompleteFilePathEnd:
  760. assert((*Command)[*CommandLength] == '\0');
  761. ShSetTerminalMode(Shell, TRUE);
  762. if (UserString != NULL) {
  763. free(UserString);
  764. }
  765. if (PreviousGuess != NULL) {
  766. free(PreviousGuess);
  767. }
  768. if ((RoundedMatches != NULL) && (RoundedMatches != Matches)) {
  769. free(RoundedMatches);
  770. }
  771. if (Matches != NULL) {
  772. ShDestroyStringArray(Matches, MatchCount);
  773. }
  774. if (Replacement != NULL) {
  775. free(Replacement);
  776. }
  777. return;
  778. }
  779. VOID
  780. ShGetFileMatches (
  781. PSTR File,
  782. PSTR **Matches,
  783. PULONG MatchCount
  784. )
  785. /*++
  786. Routine Description:
  787. This routine returns any files that match the given file name.
  788. Arguments:
  789. File - Supplies a pointer to the string containing the file prefix.
  790. Matches - Supplies a pointer where an array of matches will be returned on
  791. success, or NULL if nothing matches.
  792. MatchCount - Supplies a pointer where the number of elements in the
  793. match array will be returned.
  794. Return Value:
  795. None.
  796. --*/
  797. {
  798. PSTR BaseName;
  799. PSTR CurrentPath;
  800. PSTR Directory;
  801. PSTR *DirectoryMatchArray;
  802. ULONG DirectoryMatchArraySize;
  803. PSTR FileCopy;
  804. size_t FileLength;
  805. PSTR *MatchArray;
  806. ULONG MatchArrayCapacity;
  807. ULONG MatchArraySize;
  808. PSTR NextSeparator;
  809. PSTR Path;
  810. PSTR PathCopy;
  811. CHAR Separator;
  812. BaseName = NULL;
  813. Directory = NULL;
  814. MatchArray = NULL;
  815. MatchArrayCapacity = 0;
  816. MatchArraySize = 0;
  817. PathCopy = NULL;
  818. FileCopy = strdup(File);
  819. if (FileCopy == NULL) {
  820. goto GetFileMatchesEnd;
  821. }
  822. FileLength = strlen(FileCopy);
  823. //
  824. // Get the directory portion and the file name portion of the string.
  825. // If the last character is a slash, then treat the whole thing like the
  826. // directory name.
  827. //
  828. if ((FileLength != 0) && (FileCopy[FileLength - 1] == '/')) {
  829. BaseName = strdup("");
  830. Directory = FileCopy;
  831. FileCopy = NULL;
  832. } else {
  833. BaseName = basename(FileCopy);
  834. if (BaseName == NULL) {
  835. goto GetFileMatchesEnd;
  836. }
  837. BaseName = strdup(BaseName);
  838. Directory = dirname(FileCopy);
  839. if (Directory == NULL) {
  840. goto GetFileMatchesEnd;
  841. }
  842. Directory = strdup(Directory);
  843. }
  844. if ((BaseName == NULL) || (Directory == NULL)) {
  845. goto GetFileMatchesEnd;
  846. }
  847. //
  848. // Search the directory as specified directly.
  849. //
  850. ShGetFileMatchesInDirectory(BaseName,
  851. Directory,
  852. &MatchArray,
  853. &MatchArraySize);
  854. MatchArrayCapacity = MatchArraySize;
  855. //
  856. // If the path has a slash in it, then it's fully specified, so this set is
  857. // it.
  858. //
  859. if (strchr(File, '/') != NULL) {
  860. goto GetFileMatchesEnd;
  861. }
  862. //
  863. // Get the path and create a copy to tokenize over.
  864. //
  865. Path = getenv("PATH");
  866. if (Path == NULL) {
  867. goto GetFileMatchesEnd;
  868. }
  869. PathCopy = strdup(Path);
  870. if (PathCopy == NULL) {
  871. goto GetFileMatchesEnd;
  872. }
  873. CurrentPath = PathCopy;
  874. Separator = PATH_LIST_SEPARATOR;
  875. while (TRUE) {
  876. NextSeparator = strchr(CurrentPath, Separator);
  877. if (NextSeparator != NULL) {
  878. *NextSeparator = '\0';
  879. }
  880. DirectoryMatchArray = NULL;
  881. DirectoryMatchArraySize = 0;
  882. ShGetFileMatchesInDirectory(BaseName,
  883. CurrentPath,
  884. &DirectoryMatchArray,
  885. &DirectoryMatchArraySize);
  886. ShMergeStringArrays(&MatchArray,
  887. &MatchArraySize,
  888. &MatchArrayCapacity,
  889. DirectoryMatchArray,
  890. DirectoryMatchArraySize);
  891. if (NextSeparator == NULL) {
  892. break;
  893. }
  894. CurrentPath = NextSeparator + 1;
  895. }
  896. GetFileMatchesEnd:
  897. if (FileCopy != NULL) {
  898. free(FileCopy);
  899. }
  900. if (BaseName != NULL) {
  901. free(BaseName);
  902. }
  903. if (Directory != NULL) {
  904. free(Directory);
  905. }
  906. if (PathCopy != NULL) {
  907. free(PathCopy);
  908. }
  909. *Matches = MatchArray;
  910. *MatchCount = MatchArraySize;
  911. return;
  912. }
  913. VOID
  914. ShGetFileMatchesInDirectory (
  915. PSTR FileName,
  916. PSTR DirectoryName,
  917. PSTR **Matches,
  918. PULONG MatchCount
  919. )
  920. /*++
  921. Routine Description:
  922. This routine returns any files that match the given file name in the given
  923. directory.
  924. Arguments:
  925. FileName - Supplies a pointer to the string containing the file prefix.
  926. DirectoryName - Supplies a pointer to a string containing the directory to
  927. search in.
  928. Matches - Supplies a pointer where an array of matches will be returned on
  929. success, or NULL if nothing matches.
  930. MatchCount - Supplies a pointer where the number of elements in the
  931. match array will be returned.
  932. Return Value:
  933. None.
  934. --*/
  935. {
  936. DIR *Directory;
  937. size_t DirectoryLength;
  938. struct dirent Entry;
  939. size_t EntryLength;
  940. struct dirent *EntryPointer;
  941. size_t FileNameLength;
  942. PSTR FullPath;
  943. PSTR *MatchArray;
  944. ULONG MatchArrayCapacity;
  945. ULONG MatchArraySize;
  946. int Result;
  947. int SlashSize;
  948. struct stat Stat;
  949. MatchArray = NULL;
  950. MatchArraySize = 0;
  951. MatchArrayCapacity = 0;
  952. Directory = opendir(DirectoryName);
  953. if (Directory == NULL) {
  954. goto GetFileMatchesInDirectoryEnd;
  955. }
  956. DirectoryLength = strlen(DirectoryName);
  957. FileNameLength = strlen(FileName);
  958. //
  959. // Loop reading directory entries.
  960. //
  961. while (TRUE) {
  962. Result = SwReadDirectory(Directory, &Entry, &EntryPointer);
  963. if (Result != 0) {
  964. goto GetFileMatchesInDirectoryEnd;
  965. }
  966. if (EntryPointer == NULL) {
  967. break;
  968. }
  969. if ((strcmp(Entry.d_name, ".") == 0) ||
  970. (strcmp(Entry.d_name, "..") == 0)) {
  971. continue;
  972. }
  973. if (strncasecmp(FileName, Entry.d_name, FileNameLength) == 0) {
  974. EntryLength = strlen(Entry.d_name);
  975. FullPath = malloc(DirectoryLength + EntryLength + 3);
  976. if (FullPath == NULL) {
  977. continue;
  978. }
  979. memcpy(FullPath, DirectoryName, DirectoryLength);
  980. SlashSize = 0;
  981. if ((DirectoryLength != 0) &&
  982. (DirectoryName[DirectoryLength - 1] != '/')) {
  983. FullPath[DirectoryLength] = '/';
  984. SlashSize = 1;
  985. }
  986. strcpy(FullPath + DirectoryLength + SlashSize, Entry.d_name);
  987. Result = SwStat(FullPath, TRUE, &Stat);
  988. if ((Result == 0) && (S_ISDIR(Stat.st_mode))) {
  989. strcpy(FullPath + DirectoryLength + SlashSize + EntryLength,
  990. "/");
  991. }
  992. ShAddStringToArray(&MatchArray,
  993. FullPath + DirectoryLength + SlashSize,
  994. &MatchArraySize,
  995. &MatchArrayCapacity);
  996. free(FullPath);
  997. }
  998. }
  999. GetFileMatchesInDirectoryEnd:
  1000. if (Directory != NULL) {
  1001. closedir(Directory);
  1002. }
  1003. *Matches = MatchArray;
  1004. *MatchCount = MatchArraySize;
  1005. return;
  1006. }
  1007. BOOL
  1008. ShGetFileCompletionPortion (
  1009. PSHELL Shell,
  1010. PSTR Command,
  1011. PULONG CompletionPosition,
  1012. ULONG CommandLength,
  1013. PULONG FileStartPosition,
  1014. PSTR *FileString,
  1015. PSTR *PreviousGuess
  1016. )
  1017. /*++
  1018. Routine Description:
  1019. This routine gets the portion of the end of the command that is eligible
  1020. for file path expansion.
  1021. Arguments:
  1022. Shell - Supplies a pointer to the shell context.
  1023. Command - Supplies a pointer to the command.
  1024. CompletionPosition - Supplies a pointer to the position to base completions
  1025. off of. This may be different than the current position
  1026. (supplied command length) if cycling through guesses. If -1 is supplied,
  1027. then the completion position will be filled in for the expanded current
  1028. path.
  1029. CommandLength - Supplies the length of the command, not including the null
  1030. terminating byte. Usually the current position is passed in here.
  1031. FileStartPosition - Supplies a pointer where the index into the command
  1032. where the beginning of the file path replacement will be returned.
  1033. FileString - Supplies a pointer where a newly allocated string
  1034. will be returned containing the portion of the command that should be
  1035. expanded.
  1036. PreviousGuess - Supplies a pointer where the previous guess will be
  1037. returned if the completion portion and command length are not the same.
  1038. Return Value:
  1039. TRUE on success.
  1040. FALSE on failure.
  1041. --*/
  1042. {
  1043. PSTR BaseName;
  1044. size_t BaseNameLength;
  1045. CHAR Character;
  1046. ULONG CommandEnd;
  1047. PSTR ExpandedField;
  1048. UINTN ExpandedFieldSize;
  1049. ULONG ExpandOptions;
  1050. PSTR Guess;
  1051. PSTR GuessBaseName;
  1052. UINTN GuessSize;
  1053. ULONG Index;
  1054. PSTR LastField;
  1055. ULONG LastFieldIndex;
  1056. UINTN LastFieldLength;
  1057. CHAR Quote;
  1058. BOOL Result;
  1059. BOOL TrailingSlash;
  1060. BOOL WasBackslash;
  1061. BOOL WasBlank;
  1062. ExpandedField = NULL;
  1063. Guess = NULL;
  1064. GuessBaseName = NULL;
  1065. LastField = NULL;
  1066. Quote = 0;
  1067. WasBackslash = FALSE;
  1068. WasBlank = FALSE;
  1069. LastFieldIndex = 0;
  1070. Result = FALSE;
  1071. if (*CompletionPosition == (ULONG)-1) {
  1072. CommandEnd = CommandLength;
  1073. } else {
  1074. CommandEnd = *CompletionPosition;
  1075. }
  1076. //
  1077. // Loop to find the start of the last field, honoring quotes.
  1078. //
  1079. Index = 0;
  1080. while (Index < CommandEnd) {
  1081. Character = Command[Index];
  1082. if (Quote != 0) {
  1083. if (Quote == '\'') {
  1084. if (Character == '\'') {
  1085. Quote = 0;
  1086. }
  1087. } else if (Quote == '"') {
  1088. if ((Character == '"') && (WasBackslash == FALSE)) {
  1089. Quote = 0;
  1090. }
  1091. }
  1092. } else {
  1093. if (WasBackslash == FALSE) {
  1094. //
  1095. // An unquoted unescaped blank starts a new field.
  1096. //
  1097. if (isblank(Character)) {
  1098. WasBlank = TRUE;
  1099. //
  1100. // This is not a blank. If the last character was, then this is
  1101. // the start of the new field.
  1102. //
  1103. } else {
  1104. if (WasBlank != FALSE) {
  1105. LastFieldIndex = Index;
  1106. }
  1107. WasBlank = FALSE;
  1108. //
  1109. // Look to see if quotes are beginning.
  1110. //
  1111. if ((Character == '\'') || (Character == '"')) {
  1112. Quote = Character;
  1113. }
  1114. }
  1115. }
  1116. }
  1117. if (Character == '\\') {
  1118. WasBackslash = !WasBackslash;
  1119. } else {
  1120. WasBackslash = FALSE;
  1121. }
  1122. Index += 1;
  1123. }
  1124. if (WasBlank != FALSE) {
  1125. LastFieldIndex = CommandEnd;
  1126. }
  1127. //
  1128. // Create a copy of the last field. If the last field is zero in size, use
  1129. // the current directory.
  1130. //
  1131. if ((strlen(Command + LastFieldIndex) == 0) ||
  1132. (LastFieldIndex == CommandEnd)) {
  1133. LastField = strdup("./");
  1134. if (LastField == NULL) {
  1135. goto GetFileCompletionPositionEnd;
  1136. }
  1137. LastFieldLength = 2;
  1138. } else {
  1139. LastField = strdup(Command + LastFieldIndex);
  1140. if (LastField == NULL) {
  1141. goto GetFileCompletionPositionEnd;
  1142. }
  1143. LastFieldLength = CommandEnd - LastFieldIndex;
  1144. LastField[LastFieldLength] = '\0';
  1145. }
  1146. //
  1147. // Escape and control-quote the string. Since it's only tab completion,
  1148. // there's no need for this to be perfect.
  1149. //
  1150. Index = 0;
  1151. WasBackslash = FALSE;
  1152. while (LastField[Index] != '\0') {
  1153. //
  1154. // Handle a double quote region.
  1155. //
  1156. if (LastField[Index] == '\'') {
  1157. LastField[Index] = SHELL_CONTROL_QUOTE;
  1158. do {
  1159. Index += 1;
  1160. } while ((LastField[Index] != '\'') && (LastField[Index] != '\0'));
  1161. if (LastField[Index] == '\'') {
  1162. LastField[Index] = SHELL_CONTROL_QUOTE;
  1163. Index += 1;
  1164. }
  1165. //
  1166. // Handle a single quote region.
  1167. //
  1168. } else if (LastField[Index] == '"') {
  1169. LastField[Index] = SHELL_CONTROL_QUOTE;
  1170. while ((LastField[Index] != '\0') && (LastField[Index] != '"')) {
  1171. if (LastField[Index] == '\\') {
  1172. Index += 1;
  1173. if ((LastField[Index] == '$') ||
  1174. (LastField[Index] == '`') ||
  1175. (LastField[Index] == '"') ||
  1176. (LastField[Index] == '\\') ||
  1177. (LastField[Index] == '\r') ||
  1178. (LastField[Index] == '\n')) {
  1179. LastField[Index - 1] = SHELL_CONTROL_ESCAPE;
  1180. }
  1181. }
  1182. Index += 1;
  1183. }
  1184. if (LastField[Index] == '"') {
  1185. LastField[Index] = SHELL_CONTROL_QUOTE;
  1186. Index += 1;
  1187. }
  1188. } else {
  1189. if (LastField[Index] == '\\') {
  1190. LastField[Index] = SHELL_CONTROL_ESCAPE;
  1191. Index += 1;
  1192. }
  1193. Index += 1;
  1194. }
  1195. }
  1196. //
  1197. // Expand the string.
  1198. //
  1199. ExpandOptions = SHELL_EXPANSION_OPTION_NO_FIELD_SPLIT |
  1200. SHELL_EXPANSION_OPTION_NO_PATH_EXPANSION;
  1201. Result = ShPerformExpansions(Shell,
  1202. LastField,
  1203. LastFieldLength + 1,
  1204. ExpandOptions,
  1205. &ExpandedField,
  1206. &ExpandedFieldSize,
  1207. NULL,
  1208. NULL);
  1209. if (Result == FALSE) {
  1210. goto GetFileCompletionPositionEnd;
  1211. }
  1212. //
  1213. // If the completion position has not been set, this is the first time
  1214. // trying.
  1215. //
  1216. if (*CompletionPosition == (ULONG)-1) {
  1217. *CompletionPosition = LastFieldIndex + ExpandedFieldSize - 1;
  1218. Result = TRUE;
  1219. goto GetFileCompletionPositionEnd;
  1220. }
  1221. GuessSize = CommandLength - LastFieldIndex + 1;
  1222. Guess = malloc(GuessSize);
  1223. if (Guess == NULL) {
  1224. goto GetFileCompletionPositionEnd;
  1225. }
  1226. memcpy(Guess, Command + LastFieldIndex, GuessSize - 1);
  1227. Guess[GuessSize - 1] = '\0';
  1228. //
  1229. // Temporarily remove a trailing slash as it gets basename all flustered.
  1230. //
  1231. TrailingSlash = FALSE;
  1232. if ((GuessSize > 1) && (Guess[GuessSize - 2] == '/')) {
  1233. GuessSize -= 1;
  1234. Guess[GuessSize - 1] = '\0';
  1235. TrailingSlash = TRUE;
  1236. }
  1237. BaseName = basename(Guess);
  1238. if (BaseName == NULL) {
  1239. goto GetFileCompletionPositionEnd;
  1240. }
  1241. BaseNameLength = strlen(BaseName);
  1242. GuessBaseName = malloc(BaseNameLength + 2);
  1243. if (GuessBaseName == NULL) {
  1244. goto GetFileCompletionPositionEnd;
  1245. }
  1246. strcpy(GuessBaseName, BaseName);
  1247. if (TrailingSlash != FALSE) {
  1248. GuessBaseName[BaseNameLength] = '/';
  1249. GuessBaseName[BaseNameLength + 1] = '\0';
  1250. }
  1251. Result = TRUE;
  1252. GetFileCompletionPositionEnd:
  1253. if (LastField != NULL) {
  1254. free(LastField);
  1255. }
  1256. if (Guess != NULL) {
  1257. free(Guess);
  1258. }
  1259. *FileStartPosition = LastFieldIndex;
  1260. *FileString = ExpandedField;
  1261. *PreviousGuess = GuessBaseName;
  1262. return Result;
  1263. }
  1264. PSTR
  1265. ShGetFileReplacementString (
  1266. PSTR UserString,
  1267. PSTR PreviousGuess,
  1268. PSTR *Matches,
  1269. ULONG MatchCount
  1270. )
  1271. /*++
  1272. Routine Description:
  1273. This routine attempts to get the replacement string to the file path
  1274. completion if there is one.
  1275. Arguments:
  1276. UserString - Supplies the query string.
  1277. PreviousGuess - Supplies a pointer to a string containing the previous
  1278. guess for this query.
  1279. Matches - Supplies the array of strings that match.
  1280. MatchCount - Supplies the number of elements in the match array.
  1281. Return Value:
  1282. Returns a pointer to a string representing the complete replacement, which
  1283. will be the first match if there is only one, or the common prefix of all
  1284. matches if there is one and it's longer than the input.
  1285. --*/
  1286. {
  1287. PSTR BaseName;
  1288. size_t BaseNameLength;
  1289. ULONG CharacterIndex;
  1290. ULONG CombinedLength;
  1291. PSTR CombinedString;
  1292. PSTR CurrentMatch;
  1293. PSTR Directory;
  1294. size_t DirectoryLength;
  1295. ULONG LongestPrefix;
  1296. PSTR Match;
  1297. ULONG MatchIndex;
  1298. PSTR QuotedString;
  1299. PSTR UserCopy;
  1300. size_t UserStringLength;
  1301. BaseName = NULL;
  1302. CombinedString = NULL;
  1303. Directory = NULL;
  1304. Match = NULL;
  1305. UserCopy = NULL;
  1306. QuotedString = NULL;
  1307. if ((Matches == NULL) || (MatchCount == 0)) {
  1308. goto GetFileReplacementStringEnd;
  1309. }
  1310. //
  1311. // Get the directory portion and the file name portion of the string.
  1312. //
  1313. UserCopy = strdup(UserString);
  1314. if (UserCopy == NULL) {
  1315. goto GetFileReplacementStringEnd;
  1316. }
  1317. UserStringLength = strlen(UserCopy);
  1318. if ((UserStringLength != 0) && (UserString[UserStringLength - 1] == '/')) {
  1319. BaseName = strdup("");
  1320. Directory = UserCopy;
  1321. UserCopy = NULL;
  1322. } else {
  1323. BaseName = basename(UserCopy);
  1324. if (BaseName == NULL) {
  1325. goto GetFileReplacementStringEnd;
  1326. }
  1327. BaseName = strdup(BaseName);
  1328. //
  1329. // If the given path doesn't have a slash in it, then use an empty
  1330. // directory name as the found results may come from the path.
  1331. //
  1332. if (strchr(UserString, '/') == NULL) {
  1333. Directory = "";
  1334. } else {
  1335. Directory = dirname(UserCopy);
  1336. if (Directory == NULL) {
  1337. goto GetFileReplacementStringEnd;
  1338. }
  1339. }
  1340. Directory = strdup(Directory);
  1341. }
  1342. if ((BaseName == NULL) || (Directory == NULL)) {
  1343. goto GetFileReplacementStringEnd;
  1344. }
  1345. //
  1346. // If there's only one match, use that.
  1347. //
  1348. if (MatchCount == 1) {
  1349. Match = strdup(Matches[0]);
  1350. //
  1351. // If guessing, come up with a guess. If there is no previous guess, then
  1352. // just pick the first one. Otherwise, find the previous guess in the list
  1353. // and then guess the next thing.
  1354. //
  1355. } else if (ShGuessFileMatch != FALSE) {
  1356. if (PreviousGuess == NULL) {
  1357. Match = strdup(Matches[0]);
  1358. } else {
  1359. for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex += 1) {
  1360. CurrentMatch = Matches[MatchIndex];
  1361. if (strcmp(CurrentMatch, PreviousGuess) == 0) {
  1362. break;
  1363. }
  1364. }
  1365. //
  1366. // Advance to the next index. If it goes beyond the end (or was
  1367. // already beyond the end because no match was found, start at the
  1368. // beginning.
  1369. //
  1370. MatchIndex += 1;
  1371. if (MatchIndex >= MatchCount) {
  1372. MatchIndex = 0;
  1373. }
  1374. Match = strdup(Matches[MatchIndex]);
  1375. }
  1376. //
  1377. // There are a bunch of matches. Find the longest common prefix of all
  1378. // the matches.
  1379. //
  1380. } else {
  1381. BaseNameLength = strlen(BaseName);
  1382. LongestPrefix = strlen(Matches[0]);
  1383. for (MatchIndex = 1; MatchIndex < MatchCount; MatchIndex += 1) {
  1384. CurrentMatch = Matches[MatchIndex];
  1385. CharacterIndex = 0;
  1386. while ((CharacterIndex < LongestPrefix) &&
  1387. (CurrentMatch[CharacterIndex] ==
  1388. Matches[0][CharacterIndex])) {
  1389. CharacterIndex += 1;
  1390. }
  1391. //
  1392. // If this match can't keep up for as long, then the new common
  1393. // prefix is his length.
  1394. //
  1395. if (CharacterIndex < LongestPrefix) {
  1396. LongestPrefix = CharacterIndex;
  1397. }
  1398. //
  1399. // Stop bothering to look if it's no longer than the basename.
  1400. //
  1401. if (LongestPrefix <= BaseNameLength) {
  1402. break;
  1403. }
  1404. }
  1405. //
  1406. // If the longest prefix is no longer than the base name, don't return
  1407. // anything.
  1408. //
  1409. if (LongestPrefix <= BaseNameLength) {
  1410. goto GetFileReplacementStringEnd;
  1411. }
  1412. //
  1413. // Create a copy of the longest prefix.
  1414. //
  1415. Match = strdup(Matches[0]);
  1416. if (Match == NULL) {
  1417. goto GetFileReplacementStringEnd;
  1418. }
  1419. Match[LongestPrefix] = '\0';
  1420. }
  1421. //
  1422. // If there's no match, then just cut out now.
  1423. //
  1424. if (Match == NULL) {
  1425. goto GetFileReplacementStringEnd;
  1426. }
  1427. //
  1428. // Create the combined string, which is the user supplied directory name
  1429. // and this match.
  1430. //
  1431. DirectoryLength = strlen(Directory);
  1432. CombinedLength = DirectoryLength + 1 + strlen(Match) + 2;
  1433. CombinedString = malloc(CombinedLength);
  1434. if (CombinedString == NULL) {
  1435. goto GetFileReplacementStringEnd;
  1436. }
  1437. memcpy(CombinedString, Directory, DirectoryLength);
  1438. if ((DirectoryLength != 0) && (Directory[DirectoryLength - 1] != '/')) {
  1439. CombinedString[DirectoryLength] = '/';
  1440. DirectoryLength += 1;
  1441. }
  1442. strcpy(CombinedString + DirectoryLength, Match);
  1443. //
  1444. // Quote the string.
  1445. //
  1446. ShQuoteString(CombinedString, &QuotedString);
  1447. GetFileReplacementStringEnd:
  1448. if (UserCopy != NULL) {
  1449. free(UserCopy);
  1450. }
  1451. if (BaseName != NULL) {
  1452. free(BaseName);
  1453. }
  1454. if (Directory != NULL) {
  1455. free(Directory);
  1456. }
  1457. if (Match != NULL) {
  1458. free(Match);
  1459. }
  1460. if (CombinedString != NULL) {
  1461. free(CombinedString);
  1462. }
  1463. return QuotedString;
  1464. }
  1465. BOOL
  1466. ShQuoteString (
  1467. PSTR String,
  1468. PSTR *QuotedString
  1469. )
  1470. /*++
  1471. Routine Description:
  1472. This routine quotes a given string.
  1473. Arguments:
  1474. String - Supplies a pointer to the string to quote.
  1475. QuotedString - Supplies a pointer where an allocated string will be
  1476. returned. The caller is responsible for freeing this memory.
  1477. Return Value:
  1478. TRUE on success.
  1479. FALSE on allocation failure.
  1480. --*/
  1481. {
  1482. CHAR Character;
  1483. PSTR Final;
  1484. ULONG FinalIndex;
  1485. ULONG InputIndex;
  1486. size_t InputLength;
  1487. BOOL NeedsBackslash;
  1488. *QuotedString = NULL;
  1489. InputLength = strlen(String);
  1490. //
  1491. // Allocate a new buffer for the final string. In the worst case every
  1492. // character needs a backslash.
  1493. //
  1494. Final = malloc(InputLength * 2);
  1495. if (Final == NULL) {
  1496. return FALSE;
  1497. }
  1498. FinalIndex = 0;
  1499. InputIndex = 0;
  1500. while (InputIndex < InputLength) {
  1501. Character = String[InputIndex];
  1502. InputIndex += 1;
  1503. NeedsBackslash = TRUE;
  1504. if ((isalnum(Character)) || (Character == '.') || (Character == '_') ||
  1505. (Character == '/') || (Character == ',') || (Character == '+') ||
  1506. (Character == '-')) {
  1507. NeedsBackslash = FALSE;
  1508. }
  1509. if (NeedsBackslash != FALSE) {
  1510. Final[FinalIndex] = '\\';
  1511. FinalIndex += 1;
  1512. }
  1513. Final[FinalIndex] = Character;
  1514. FinalIndex += 1;
  1515. }
  1516. Final[FinalIndex] = '\0';
  1517. assert(InputIndex < InputLength * 2);
  1518. *QuotedString = Final;
  1519. return TRUE;
  1520. }
  1521. VOID
  1522. ShAddCommandHistoryEntry (
  1523. PSTR Command
  1524. )
  1525. /*++
  1526. Routine Description:
  1527. This routine adds a command to the command history.
  1528. Arguments:
  1529. Command - Supplies a pointer to the command string to add.
  1530. Return Value:
  1531. None.
  1532. --*/
  1533. {
  1534. PSTR Copy;
  1535. INT PreviousIndex;
  1536. INT Result;
  1537. Copy = NULL;
  1538. Result = ENOMEM;
  1539. if (ShCommandHistory == NULL) {
  1540. if (ShCommandHistorySize == 0) {
  1541. Result = 0;
  1542. goto ShAddCommandHistoryEntryEnd;
  1543. }
  1544. ShCommandHistory = malloc(ShCommandHistorySize * sizeof(PSTR));
  1545. if (ShCommandHistory == NULL) {
  1546. goto ShAddCommandHistoryEntryEnd;
  1547. }
  1548. memset(ShCommandHistory, 0, ShCommandHistorySize * sizeof(PSTR));
  1549. }
  1550. assert(ShCommandHistoryIndex < ShCommandHistorySize);
  1551. //
  1552. // Don't add the command if it's exactly the same as the last one.
  1553. //
  1554. PreviousIndex = ShCommandHistoryIndex - 1;
  1555. if (PreviousIndex < 0) {
  1556. PreviousIndex += ShCommandHistorySize;
  1557. }
  1558. if ((ShCommandHistory[PreviousIndex] != NULL) &&
  1559. (strcmp(Command, ShCommandHistory[PreviousIndex]) == 0)) {
  1560. Result = 0;
  1561. goto ShAddCommandHistoryEntryEnd;
  1562. }
  1563. Copy = strdup(Command);
  1564. if (Copy == NULL) {
  1565. goto ShAddCommandHistoryEntryEnd;
  1566. }
  1567. if (ShCommandHistory[ShCommandHistoryIndex] != NULL) {
  1568. free(ShCommandHistory[ShCommandHistoryIndex]);
  1569. }
  1570. ShCommandHistory[ShCommandHistoryIndex] = Copy;
  1571. ShCommandHistoryIndex += 1;
  1572. if (ShCommandHistoryIndex >= ShCommandHistorySize) {
  1573. ShCommandHistoryIndex = 0;
  1574. }
  1575. Result = 0;
  1576. ShAddCommandHistoryEntryEnd:
  1577. if (Result != 0) {
  1578. if (Copy != NULL) {
  1579. free(Copy);
  1580. }
  1581. }
  1582. return;
  1583. }
  1584. PSTR
  1585. ShGetCommandHistoryEntry (
  1586. LONG Offset
  1587. )
  1588. /*++
  1589. Routine Description:
  1590. This routine cleans the current line and resets the position to zero.
  1591. Arguments:
  1592. Offset - Supplies the offset from the most recent command to reach for.
  1593. Return Value:
  1594. Returns a pointer to a newly allocated string containing the historical
  1595. command on success.
  1596. NULL on failure or if there is no command that far back.
  1597. --*/
  1598. {
  1599. PSTR Copy;
  1600. LONG Index;
  1601. if ((Offset < 0) || (Offset > ShCommandHistorySize)) {
  1602. return NULL;
  1603. }
  1604. if (ShCommandHistory == NULL) {
  1605. return NULL;
  1606. }
  1607. Index = ShCommandHistoryIndex - Offset;
  1608. while (Index < 0) {
  1609. Index += ShCommandHistorySize;
  1610. }
  1611. if (ShCommandHistory[Index] == NULL) {
  1612. return NULL;
  1613. }
  1614. Copy = strdup(ShCommandHistory[Index]);
  1615. return Copy;
  1616. }
  1617. VOID
  1618. ShCleanLine (
  1619. FILE *Output,
  1620. ULONG Position,
  1621. ULONG CommandLength
  1622. )
  1623. /*++
  1624. Routine Description:
  1625. This routine cleans the current line and resets the position to zero.
  1626. Arguments:
  1627. Output - Supplies a pointer to the output file stream.
  1628. Position - Supplies the current position index.
  1629. CommandLength - Supplies the length of the command.
  1630. Return Value:
  1631. None.
  1632. --*/
  1633. {
  1634. if (CommandLength != 0) {
  1635. if (Position != 0) {
  1636. SwMoveCursorRelative(Output, -Position, NULL);
  1637. Position = 0;
  1638. }
  1639. ShPrintSpaces(Output, CommandLength);
  1640. SwMoveCursorRelative(Output, -CommandLength, NULL);
  1641. CommandLength = 0;
  1642. }
  1643. return;
  1644. }
  1645. VOID
  1646. ShPrintSpaces (
  1647. FILE *Output,
  1648. INT Count
  1649. )
  1650. /*++
  1651. Routine Description:
  1652. This routine simply prints spaces.
  1653. Arguments:
  1654. Output - Supplies a pointer to the output stream to write to.
  1655. Count - Supplies the number of spaces to print.
  1656. Return Value:
  1657. None.
  1658. --*/
  1659. {
  1660. INT FillCount;
  1661. CHAR Spaces[50];
  1662. FillCount = sizeof(Spaces);
  1663. if (FillCount > Count) {
  1664. FillCount = Count;
  1665. }
  1666. memset(Spaces, ' ', FillCount);
  1667. while (Count > FillCount) {
  1668. fwrite(Spaces, 1, FillCount, Output);
  1669. Count -= FillCount;
  1670. }
  1671. fwrite(Spaces, 1, Count, Output);
  1672. return;
  1673. }
  1674. BOOL
  1675. ShAddStringToArray (
  1676. PSTR **Array,
  1677. PSTR Entry,
  1678. PULONG ArraySize,
  1679. PULONG ArrayCapacity
  1680. )
  1681. /*++
  1682. Routine Description:
  1683. This routine adds a string to the given string array.
  1684. Arguments:
  1685. Array - Supplies a pointer that on input contains a pointer to the string
  1686. array. On output, this may be updated if the string array is
  1687. reallocated.
  1688. Entry - Supplies the string entry to add. A copy of this string will be
  1689. made.
  1690. ArraySize - Supplies a pointer that on input contains the size of the array,
  1691. in elements. This value will be incremented on success.
  1692. ArrayCapacity - Supplies a pointer that on input contains the array
  1693. capacity. This value will be updated if the array is reallocated.
  1694. Return Value:
  1695. TRUE on success.
  1696. FALSE on failure.
  1697. --*/
  1698. {
  1699. PSTR EntryCopy;
  1700. PSTR *NewBuffer;
  1701. ULONG NewCapacity;
  1702. if (*ArraySize >= *ArrayCapacity) {
  1703. if (*ArrayCapacity == 0) {
  1704. NewCapacity = INITIAL_STRING_ARRAY_SIZE;
  1705. } else {
  1706. NewCapacity = *ArrayCapacity * 2;
  1707. }
  1708. NewBuffer = realloc(*Array, NewCapacity * sizeof(PSTR));
  1709. if (NewBuffer == NULL) {
  1710. return FALSE;
  1711. }
  1712. *Array = NewBuffer;
  1713. *ArrayCapacity = NewCapacity;
  1714. }
  1715. EntryCopy = strdup(Entry);
  1716. if (EntryCopy == NULL) {
  1717. return FALSE;
  1718. }
  1719. (*Array)[*ArraySize] = EntryCopy;
  1720. *ArraySize += 1;
  1721. return TRUE;
  1722. }
  1723. BOOL
  1724. ShMergeStringArrays (
  1725. PSTR **Array1,
  1726. PULONG Array1Size,
  1727. PULONG Array1Capacity,
  1728. PSTR *Array2,
  1729. ULONG Array2Size
  1730. )
  1731. /*++
  1732. Routine Description:
  1733. This routine merges two string arrays.
  1734. Arguments:
  1735. Array1 - Supplies a pointer that on input contains a pointer to the
  1736. destination string array. On output, this may be updated if the string
  1737. array is reallocated.
  1738. Array1Size - Supplies a pointer that on input contains the size of the
  1739. primary array, in elements. This value will be incremented on success.
  1740. Array1Capacity - Supplies a pointer that on input contains the primary array
  1741. capacity. This value will be updated if the array is reallocated.
  1742. Array2 - Supplies the array of strings to add to the primary array. After
  1743. this function this array will be destroyed.
  1744. Array2Size - Supplies the number of elements in the second array.
  1745. Return Value:
  1746. TRUE on success.
  1747. FALSE on failure.
  1748. --*/
  1749. {
  1750. ULONG Array2Index;
  1751. PSTR *NewBuffer;
  1752. ULONG NewCapacity;
  1753. if (Array2 == NULL) {
  1754. return TRUE;
  1755. }
  1756. if (*Array1Size + Array2Size >= *Array1Capacity) {
  1757. NewCapacity = INITIAL_STRING_ARRAY_SIZE;
  1758. while (NewCapacity < *Array1Size + Array2Size) {
  1759. NewCapacity *= 2;
  1760. }
  1761. NewBuffer = realloc(*Array1, NewCapacity * sizeof(PSTR));
  1762. if (NewBuffer == NULL) {
  1763. return FALSE;
  1764. }
  1765. *Array1 = NewBuffer;
  1766. *Array1Capacity = NewCapacity;
  1767. }
  1768. for (Array2Index = 0; Array2Index < Array2Size; Array2Index += 1) {
  1769. (*Array1)[*Array1Size + Array2Index] = Array2[Array2Index];
  1770. Array2[Array2Index] = NULL;
  1771. }
  1772. free(Array2);
  1773. *Array1Size += Array2Size;
  1774. return TRUE;
  1775. }
  1776. VOID
  1777. ShRemoveDuplicateFileMatches (
  1778. PSTR *Array,
  1779. PULONG ArraySize
  1780. )
  1781. /*++
  1782. Routine Description:
  1783. This routine removes duplicates from the given array. This routine assumes
  1784. the array has already been sorted.
  1785. Arguments:
  1786. Array - Supplies the array of strings.
  1787. ArraySize - Supplies a pointer that on input contains the number of
  1788. elements in the string array. On output, returns the potentially
  1789. reduced number after duplicates have been removed.
  1790. Return Value:
  1791. None.
  1792. --*/
  1793. {
  1794. ULONG Index;
  1795. ULONG MoveIndex;
  1796. for (Index = 1; Index < *ArraySize; Index += 1) {
  1797. while ((Index < *ArraySize) &&
  1798. (strcmp(Array[Index], Array[Index - 1]) == 0)) {
  1799. free(Array[Index]);
  1800. //
  1801. // Move the other entries down.
  1802. //
  1803. for (MoveIndex = Index;
  1804. MoveIndex < *ArraySize - 1;
  1805. MoveIndex += 1) {
  1806. Array[MoveIndex] = Array[MoveIndex + 1];
  1807. }
  1808. *ArraySize -= 1;
  1809. }
  1810. }
  1811. return;
  1812. }
  1813. VOID
  1814. ShDestroyStringArray (
  1815. PSTR *Array,
  1816. ULONG ArraySize
  1817. )
  1818. /*++
  1819. Routine Description:
  1820. This routine destroys a string array. This routine will free both the
  1821. array and each element.
  1822. Arguments:
  1823. Array - Supplies a pointer to the array of strings.
  1824. ArraySize - Supplies the number of elements in the string array.
  1825. Return Value:
  1826. None.
  1827. --*/
  1828. {
  1829. ULONG Index;
  1830. if (Array == NULL) {
  1831. return;
  1832. }
  1833. for (Index = 0; Index < ArraySize; Index += 1) {
  1834. if (Array[Index] != NULL) {
  1835. free(Array[Index]);
  1836. }
  1837. }
  1838. free(Array);
  1839. return;
  1840. }
  1841. int
  1842. ShCompareStringArrayElements (
  1843. const void *LeftElement,
  1844. const void *RightElement
  1845. )
  1846. /*++
  1847. Routine Description:
  1848. This routine compares two pointers to strings. This prototype is
  1849. compatible with the qsort function compare routine.
  1850. Arguments:
  1851. LeftElement - Supplies a pointer where a pointer to the left string resides.
  1852. RightElement - Supplies a pointer where a pointer to the right string
  1853. resides.
  1854. Return Value:
  1855. <0 if the left is less than the right.
  1856. 0 if the left is equal to the right.
  1857. >0 if th left is greater than the right.
  1858. --*/
  1859. {
  1860. PSTR LeftString;
  1861. int Result;
  1862. PSTR RightString;
  1863. LeftString = *((PSTR *)LeftElement);
  1864. RightString = *((PSTR *)RightElement);
  1865. Result = strcasecmp(LeftString, RightString);
  1866. return Result;
  1867. }