embedded_cli.h 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511
  1. /**
  2. * This header was automatically built using
  3. * embedded_cli.h and embedded_cli.c
  4. * @date 2022-11-03
  5. *
  6. * MIT License
  7. *
  8. * Copyright (c) 2021 Sviatoslav Kokurin (funbiscuit)
  9. *
  10. * Permission is hereby granted, free of charge, to any person obtaining a copy
  11. * of this software and associated documentation files (the "Software"), to deal
  12. * in the Software without restriction, including without limitation the rights
  13. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  14. * copies of the Software, and to permit persons to whom the Software is
  15. * furnished to do so, subject to the following conditions:
  16. *
  17. * The above copyright notice and this permission notice shall be included in all
  18. * copies or substantial portions of the Software.
  19. *
  20. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  21. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  22. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  23. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  24. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  25. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  26. * SOFTWARE.
  27. */
  28. #ifndef EMBEDDED_CLI_H
  29. #define EMBEDDED_CLI_H
  30. #ifdef __cplusplus
  31. extern "C" {
  32. #else
  33. #include <stdbool.h>
  34. #endif
  35. // cstdint is available only since C++11, so use C header
  36. #include <stdint.h>
  37. // used for proper alignment of cli buffer
  38. #if UINTPTR_MAX == 0xFFFF
  39. #define CLI_UINT uint16_t
  40. #elif UINTPTR_MAX == 0xFFFFFFFF
  41. #define CLI_UINT uint32_t
  42. #elif UINTPTR_MAX == 0xFFFFFFFFFFFFFFFFu
  43. #define CLI_UINT uint64_t
  44. #else
  45. #error unsupported pointer size
  46. #endif
  47. #define CLI_UINT_SIZE (sizeof(CLI_UINT))
  48. // convert size in bytes to size in terms of CLI_UINTs (rounded up
  49. // if bytes is not divisible by size of single CLI_UINT)
  50. #define BYTES_TO_CLI_UINTS(bytes) \
  51. (((bytes) + CLI_UINT_SIZE - 1)/CLI_UINT_SIZE)
  52. typedef struct CliCommand CliCommand;
  53. typedef struct CliCommandBinding CliCommandBinding;
  54. typedef struct EmbeddedCli EmbeddedCli;
  55. typedef struct EmbeddedCliConfig EmbeddedCliConfig;
  56. struct CliCommand {
  57. /**
  58. * Name of the command.
  59. * In command "set led 1 1" "set" is name
  60. */
  61. const char *name;
  62. /**
  63. * String of arguments of the command.
  64. * In command "set led 1 1" "led 1 1" is string of arguments
  65. * Is ended with double 0x00 char
  66. * Use tokenize functions to easily get individual tokens
  67. */
  68. char *args;
  69. };
  70. /**
  71. * Struct to describe binding of command to function and
  72. */
  73. struct CliCommandBinding {
  74. /**
  75. * Name of command to bind. Should not be NULL.
  76. */
  77. const char *name;
  78. /**
  79. * Help string that will be displayed when "help <cmd>" is executed.
  80. * Can have multiple lines separated with "\r\n"
  81. * Can be NULL if no help is provided.
  82. */
  83. const char *help;
  84. /**
  85. * Flag to perform tokenization before calling binding function.
  86. */
  87. bool tokenizeArgs;
  88. /**
  89. * Pointer to any specific app context that is required for this binding.
  90. * It will be provided in binding callback.
  91. */
  92. void *context;
  93. /**
  94. * Binding function for when command is received.
  95. * If null, default callback (onCommand) will be called.
  96. * @param cli - pointer to cli that is calling this binding
  97. * @param args - string of args (if tokenizeArgs is false) or tokens otherwise
  98. * @param context
  99. */
  100. void (*binding)(EmbeddedCli *cli, char *args, void *context);
  101. };
  102. struct EmbeddedCli {
  103. /**
  104. * Should write char to connection
  105. * @param cli - pointer to cli that executed this function
  106. * @param c - actual character to write
  107. */
  108. void (*writeChar)(EmbeddedCli *cli, char c);
  109. /**
  110. * Called when command is received and command not found in list of
  111. * command bindings (or binding function is null).
  112. * @param cli - pointer to cli that executed this function
  113. * @param command - pointer to received command
  114. */
  115. void (*onCommand)(EmbeddedCli *cli, CliCommand *command);
  116. /**
  117. * Can be used by for any application context
  118. */
  119. void *appContext;
  120. /**
  121. * Pointer to actual implementation, do not use.
  122. */
  123. void *_impl;
  124. };
  125. /**
  126. * Configuration to create CLI
  127. */
  128. struct EmbeddedCliConfig {
  129. /**
  130. * Size of buffer that is used to store characters until they're processed
  131. */
  132. uint16_t rxBufferSize;
  133. /**
  134. * Size of buffer that is used to store current input that is not yet
  135. * sended as command (return not pressed yet)
  136. */
  137. uint16_t cmdBufferSize;
  138. /**
  139. * Size of buffer that is used to store previously entered commands
  140. * Only unique commands are stored in buffer. If buffer is smaller than
  141. * entered command (including arguments), command is discarded from history
  142. */
  143. uint16_t historyBufferSize;
  144. /**
  145. * Maximum amount of bindings that can be added via addBinding function.
  146. * Cli increases takes extra bindings for internal commands:
  147. * - help
  148. */
  149. uint16_t maxBindingCount;
  150. /**
  151. * Buffer to use for cli and all internal structures. If NULL, memory will
  152. * be allocated dynamically. Otherwise this buffer is used and no
  153. * allocations are made
  154. */
  155. CLI_UINT *cliBuffer;
  156. /**
  157. * Size of buffer for cli and internal structures (in bytes).
  158. */
  159. uint16_t cliBufferSize;
  160. /**
  161. * Whether autocompletion should be enabled.
  162. * If false, autocompletion is disabled but you still can use 'tab' to
  163. * complete current command manually.
  164. */
  165. bool enableAutoComplete;
  166. };
  167. /**
  168. * Returns pointer to default configuration for cli creation. It is safe to
  169. * modify it and then send to embeddedCliNew().
  170. * Returned structure is always the same so do not free and try to use it
  171. * immediately.
  172. * Default values:
  173. * <ul>
  174. * <li>rxBufferSize = 64</li>
  175. * <li>cmdBufferSize = 64</li>
  176. * <li>historyBufferSize = 128</li>
  177. * <li>cliBuffer = NULL (use dynamic allocation)</li>
  178. * <li>cliBufferSize = 0</li>
  179. * <li>maxBindingCount = 8</li>
  180. * <li>enableAutoComplete = true</li>
  181. * </ul>
  182. * @return configuration for cli creation
  183. */
  184. EmbeddedCliConfig *embeddedCliDefaultConfig(void);
  185. /**
  186. * Returns how many space in config buffer is required for cli creation
  187. * If you provide buffer with less space, embeddedCliNew will return NULL
  188. * This amount will always be divisible by CLI_UINT_SIZE so allocated buffer
  189. * and internal structures can be properly aligned
  190. * @param config
  191. * @return
  192. */
  193. uint16_t embeddedCliRequiredSize(EmbeddedCliConfig *config);
  194. /**
  195. * Create new CLI.
  196. * Memory is allocated dynamically if cliBuffer in config is NULL.
  197. * After CLI is created, override function pointers to start using it
  198. * @param config - config for cli creation
  199. * @return pointer to created CLI
  200. */
  201. EmbeddedCli *embeddedCliNew(EmbeddedCliConfig *config);
  202. /**
  203. * Same as calling embeddedCliNew with default config.
  204. * @return
  205. */
  206. EmbeddedCli *embeddedCliNewDefault(void);
  207. /**
  208. * Receive character and put it to internal buffer
  209. * Actual processing is done inside embeddedCliProcess
  210. * You can call this function from something like interrupt service routine,
  211. * just make sure that you call it only from single place. Otherwise input
  212. * might get corrupted
  213. * @param cli
  214. * @param c - received char
  215. */
  216. void embeddedCliReceiveChar(EmbeddedCli *cli, char c);
  217. /**
  218. * Process rx/tx buffers. Command callbacks are called from here
  219. * @param cli
  220. */
  221. void embeddedCliProcess(EmbeddedCli *cli);
  222. /**
  223. * Add specified binding to list of bindings. If list is already full, binding
  224. * is not added and false is returned
  225. * @param cli
  226. * @param binding
  227. * @return true if binding was added, false otherwise
  228. */
  229. bool embeddedCliAddBinding(EmbeddedCli *cli, CliCommandBinding binding);
  230. /**
  231. * Print specified string and account for currently entered but not submitted
  232. * command.
  233. * Current command is deleted, provided string is printed (with new line) after
  234. * that current command is printed again, so user can continue typing it.
  235. * @param cli
  236. * @param string
  237. */
  238. void embeddedCliPrint(EmbeddedCli *cli, const char *string);
  239. /**
  240. * Free allocated for cli memory
  241. * @param cli
  242. */
  243. void embeddedCliFree(EmbeddedCli *cli);
  244. /**
  245. * Perform tokenization of arguments string. Original string is modified and
  246. * should not be used directly (only inside other token functions).
  247. * Individual tokens are separated by single 0x00 char, double 0x00 is put at
  248. * the end of token list. After calling this function, you can use other
  249. * token functions to get individual tokens and token count.
  250. *
  251. * Important: Call this function only once. Otherwise information will be lost if
  252. * more than one token existed
  253. * @param args - string to tokenize (must have extra writable char after 0x00)
  254. * @return
  255. */
  256. void embeddedCliTokenizeArgs(char *args);
  257. /**
  258. * Return specific token from tokenized string
  259. * @param tokenizedStr
  260. * @param pos (counted from 1)
  261. * @return token
  262. */
  263. const char *embeddedCliGetToken(const char *tokenizedStr, uint16_t pos);
  264. /**
  265. * Same as embeddedCliGetToken but works on non-const buffer
  266. * @param tokenizedStr
  267. * @param pos (counted from 1)
  268. * @return token
  269. */
  270. char *embeddedCliGetTokenVariable(char *tokenizedStr, uint16_t pos);
  271. /**
  272. * Find token in provided tokens string and return its position (counted from 1)
  273. * If no such token is found - 0 is returned.
  274. * @param tokenizedStr
  275. * @param token - token to find
  276. * @return position (increased by 1) or zero if no such token found
  277. */
  278. uint16_t embeddedCliFindToken(const char *tokenizedStr, const char *token);
  279. /**
  280. * Return number of tokens in tokenized string
  281. * @param tokenizedStr
  282. * @return number of tokens
  283. */
  284. uint16_t embeddedCliGetTokenCount(const char *tokenizedStr);
  285. #ifdef __cplusplus
  286. }
  287. #endif
  288. #endif //EMBEDDED_CLI_H
  289. #ifdef EMBEDDED_CLI_IMPL
  290. #ifndef EMBEDDED_CLI_IMPL_GUARD
  291. #define EMBEDDED_CLI_IMPL_GUARD
  292. #ifdef __cplusplus
  293. extern "C" {
  294. #endif
  295. #include <stdlib.h>
  296. #include <string.h>
  297. #define CLI_TOKEN_NPOS 0xffff
  298. #define UNUSED(x) (void)x
  299. #define PREPARE_IMPL(t) \
  300. EmbeddedCliImpl* impl = (EmbeddedCliImpl*)t->_impl
  301. #define IS_FLAG_SET(flags, flag) (((flags) & (flag)) != 0)
  302. #define SET_FLAG(flags, flag) ((flags) |= (flag))
  303. #define UNSET_U8FLAG(flags, flag) ((flags) &= (uint8_t) ~(flag))
  304. /**
  305. * Marks binding as candidate for autocompletion
  306. * This flag is updated each time getAutocompletedCommand is called
  307. */
  308. #define BINDING_FLAG_AUTOCOMPLETE 1u
  309. /**
  310. * Indicates that rx buffer overflow happened. In such case last command
  311. * that wasn't finished (no \r or \n were received) will be discarded
  312. */
  313. #define CLI_FLAG_OVERFLOW 0x01u
  314. /**
  315. * Indicates that initialization is completed. Initialization is completed in
  316. * first call to process and needed, for example, to print invitation message.
  317. */
  318. #define CLI_FLAG_INIT_COMPLETE 0x02u
  319. /**
  320. * Indicates that CLI structure and internal structures were allocated with
  321. * malloc and should bre freed
  322. */
  323. #define CLI_FLAG_ALLOCATED 0x04u
  324. /**
  325. * Indicates that CLI structure and internal structures were allocated with
  326. * malloc and should bre freed
  327. */
  328. #define CLI_FLAG_ESCAPE_MODE 0x08u
  329. /**
  330. * Indicates that CLI in mode when it will print directly to output without
  331. * clear of current command and printing it back
  332. */
  333. #define CLI_FLAG_DIRECT_PRINT 0x10u
  334. /**
  335. * Indicates that live autocompletion is enabled
  336. */
  337. #define CLI_FLAG_AUTOCOMPLETE_ENABLED 0x20u
  338. typedef struct EmbeddedCliImpl EmbeddedCliImpl;
  339. typedef struct AutocompletedCommand AutocompletedCommand;
  340. typedef struct FifoBuf FifoBuf;
  341. typedef struct CliHistory CliHistory;
  342. struct FifoBuf {
  343. char *buf;
  344. /**
  345. * Position of first element in buffer. From this position elements are taken
  346. */
  347. uint16_t front;
  348. /**
  349. * Position after last element. At this position new elements are inserted
  350. */
  351. uint16_t back;
  352. /**
  353. * Size of buffer
  354. */
  355. uint16_t size;
  356. };
  357. struct CliHistory {
  358. /**
  359. * Items in buffer are separated by null-chars
  360. */
  361. char *buf;
  362. /**
  363. * Total size of buffer
  364. */
  365. uint16_t bufferSize;
  366. /**
  367. * Index of currently selected element. This allows to navigate history
  368. * After command is sent, current element is reset to 0 (no element)
  369. */
  370. uint16_t current;
  371. /**
  372. * Number of items in buffer
  373. * Items are counted from top to bottom (and are 1 based).
  374. * So the most recent item is 1 and the oldest is itemCount.
  375. */
  376. uint16_t itemsCount;
  377. };
  378. struct EmbeddedCliImpl {
  379. /**
  380. * Invitation string. Is printed at the beginning of each line with user
  381. * input
  382. */
  383. const char *invitation;
  384. CliHistory history;
  385. /**
  386. * Buffer for storing received chars.
  387. * Chars are stored in FIFO mode.
  388. */
  389. FifoBuf rxBuffer;
  390. /**
  391. * Buffer for current command
  392. */
  393. char *cmdBuffer;
  394. /**
  395. * Size of current command
  396. */
  397. uint16_t cmdSize;
  398. /**
  399. * Total size of command buffer
  400. */
  401. uint16_t cmdMaxSize;
  402. CliCommandBinding *bindings;
  403. /**
  404. * Flags for each binding. Sizes are the same as for bindings array
  405. */
  406. uint8_t *bindingsFlags;
  407. uint16_t bindingsCount;
  408. uint16_t maxBindingsCount;
  409. /**
  410. * Total length of input line. This doesn't include invitation but
  411. * includes current command and its live autocompletion
  412. */
  413. uint16_t inputLineLength;
  414. /**
  415. * Stores last character that was processed.
  416. */
  417. char lastChar;
  418. /**
  419. * Flags are defined as CLI_FLAG_*
  420. */
  421. uint8_t flags;
  422. };
  423. struct AutocompletedCommand {
  424. /**
  425. * Name of autocompleted command (or first candidate for autocompletion if
  426. * there are multiple candidates).
  427. * NULL if autocomplete not possible.
  428. */
  429. const char *firstCandidate;
  430. /**
  431. * Number of characters that can be completed safely. For example, if there
  432. * are two possible commands "get-led" and "get-adc", then for prefix "g"
  433. * autocompletedLen will be 4. If there are only one candidate, this number
  434. * is always equal to length of the command.
  435. */
  436. uint16_t autocompletedLen;
  437. /**
  438. * Total number of candidates for autocompletion
  439. */
  440. uint16_t candidateCount;
  441. };
  442. static EmbeddedCliConfig defaultConfig;
  443. /**
  444. * Number of commands that cli adds. Commands:
  445. * - help
  446. */
  447. static const uint16_t cliInternalBindingCount = 1;
  448. static const char *lineBreak = "\r\n";
  449. /**
  450. * Navigate through command history back and forth. If navigateUp is true,
  451. * navigate to older commands, otherwise navigate to newer.
  452. * When history end is reached, nothing happens.
  453. * @param cli
  454. * @param navigateUp
  455. */
  456. static void navigateHistory(EmbeddedCli *cli, bool navigateUp);
  457. /**
  458. * Process escaped character. After receiving ESC+[ sequence, all chars up to
  459. * ending character are sent to this function
  460. * @param cli
  461. * @param c
  462. */
  463. static void onEscapedInput(EmbeddedCli *cli, char c);
  464. /**
  465. * Process input character. Character is valid displayable char and should be
  466. * added to current command string and displayed to client.
  467. * @param cli
  468. * @param c
  469. */
  470. static void onCharInput(EmbeddedCli *cli, char c);
  471. /**
  472. * Process control character (like \r or \n) possibly altering state of current
  473. * command or executing onCommand callback.
  474. * @param cli
  475. * @param c
  476. */
  477. static void onControlInput(EmbeddedCli *cli, char c);
  478. /**
  479. * Parse command in buffer and execute callback
  480. * @param cli
  481. */
  482. static void parseCommand(EmbeddedCli *cli);
  483. /**
  484. * Setup bindings for internal commands, like help
  485. * @param cli
  486. */
  487. static void initInternalBindings(EmbeddedCli *cli);
  488. /**
  489. * Show help for given tokens (or default help if no tokens)
  490. * @param cli
  491. * @param tokens
  492. * @param context - not used
  493. */
  494. static void onHelp(EmbeddedCli *cli, char *tokens, void *context);
  495. /**
  496. * Show error about unknown command
  497. * @param cli
  498. * @param name
  499. */
  500. static void onUnknownCommand(EmbeddedCli *cli, const char *name);
  501. /**
  502. * Return autocompleted command for given prefix.
  503. * Prefix is compared to all known command bindings and autocompleted result
  504. * is returned
  505. * @param cli
  506. * @param prefix
  507. * @return
  508. */
  509. static AutocompletedCommand getAutocompletedCommand(EmbeddedCli *cli, const char *prefix);
  510. /**
  511. * Prints autocompletion result while keeping current command unchanged
  512. * Prints only if autocompletion is present and only one candidate exists.
  513. * @param cli
  514. */
  515. static void printLiveAutocompletion(EmbeddedCli *cli);
  516. /**
  517. * Handles autocomplete request. If autocomplete possible - fills current
  518. * command with autocompleted command. When multiple commands satisfy entered
  519. * prefix, they are printed to output.
  520. * @param cli
  521. */
  522. static void onAutocompleteRequest(EmbeddedCli *cli);
  523. /**
  524. * Removes all input from current line (replaces it with whitespaces)
  525. * And places cursor at the beginning of the line
  526. * @param cli
  527. */
  528. static void clearCurrentLine(EmbeddedCli *cli);
  529. /**
  530. * Write given string to cli output
  531. * @param cli
  532. * @param str
  533. */
  534. static void writeToOutput(EmbeddedCli *cli, const char *str);
  535. /**
  536. * Returns true if provided char is a supported control char:
  537. * \r, \n, \b or 0x7F (treated as \b)
  538. * @param c
  539. * @return
  540. */
  541. static bool isControlChar(char c);
  542. /**
  543. * Returns true if provided char is a valid displayable character:
  544. * a-z, A-Z, 0-9, whitespace, punctuation, etc.
  545. * Currently only ASCII is supported
  546. * @param c
  547. * @return
  548. */
  549. static bool isDisplayableChar(char c);
  550. /**
  551. * How many elements are currently available in buffer
  552. * @param buffer
  553. * @return number of elements
  554. */
  555. static uint16_t fifoBufAvailable(FifoBuf *buffer);
  556. /**
  557. * Return first character from buffer and remove it from buffer
  558. * Buffer must be non-empty, otherwise 0 is returned
  559. * @param buffer
  560. * @return
  561. */
  562. static char fifoBufPop(FifoBuf *buffer);
  563. /**
  564. * Push character into fifo buffer. If there is no space left, character is
  565. * discarded and false is returned
  566. * @param buffer
  567. * @param a - character to add
  568. * @return true if char was added to buffer, false otherwise
  569. */
  570. static bool fifoBufPush(FifoBuf *buffer, char a);
  571. /**
  572. * Copy provided string to the history buffer.
  573. * If it is already inside history, it will be removed from it and added again.
  574. * So after addition, it will always be on top
  575. * If available size is not enough (and total size is enough) old elements will
  576. * be removed from history so this item can be put to it
  577. * @param history
  578. * @param str
  579. * @return true if string was put in history
  580. */
  581. static bool historyPut(CliHistory *history, const char *str);
  582. /**
  583. * Get item from history. Items are counted from 1 so if item is 0 or greater
  584. * than itemCount, NULL is returned
  585. * @param history
  586. * @param item
  587. * @return true if string was put in history
  588. */
  589. static const char *historyGet(CliHistory *history, uint16_t item);
  590. /**
  591. * Remove specific item from history
  592. * @param history
  593. * @param str - string to remove
  594. * @return
  595. */
  596. static void historyRemove(CliHistory *history, const char *str);
  597. /**
  598. * Return position (index of first char) of specified token
  599. * @param tokenizedStr - tokenized string (separated by \0 with
  600. * \0\0 at the end)
  601. * @param pos - token position (counted from 1)
  602. * @return index of first char of specified token
  603. */
  604. static uint16_t getTokenPosition(const char *tokenizedStr, uint16_t pos);
  605. EmbeddedCliConfig *embeddedCliDefaultConfig(void) {
  606. defaultConfig.rxBufferSize = 64;
  607. defaultConfig.cmdBufferSize = 64;
  608. defaultConfig.historyBufferSize = 128;
  609. defaultConfig.cliBuffer = NULL;
  610. defaultConfig.cliBufferSize = 0;
  611. defaultConfig.maxBindingCount = 8;
  612. defaultConfig.enableAutoComplete = true;
  613. return &defaultConfig;
  614. }
  615. uint16_t embeddedCliRequiredSize(EmbeddedCliConfig *config) {
  616. uint16_t bindingCount = (uint16_t) (config->maxBindingCount + cliInternalBindingCount);
  617. return (uint16_t) (CLI_UINT_SIZE * (
  618. BYTES_TO_CLI_UINTS(sizeof(EmbeddedCli)) +
  619. BYTES_TO_CLI_UINTS(sizeof(EmbeddedCliImpl)) +
  620. BYTES_TO_CLI_UINTS(config->rxBufferSize * sizeof(char)) +
  621. BYTES_TO_CLI_UINTS(config->cmdBufferSize * sizeof(char)) +
  622. BYTES_TO_CLI_UINTS(config->historyBufferSize * sizeof(char)) +
  623. BYTES_TO_CLI_UINTS(bindingCount * sizeof(CliCommandBinding)) +
  624. BYTES_TO_CLI_UINTS(bindingCount * sizeof(uint8_t))));
  625. }
  626. EmbeddedCli *embeddedCliNew(EmbeddedCliConfig *config) {
  627. EmbeddedCli *cli = NULL;
  628. uint16_t bindingCount = (uint16_t) (config->maxBindingCount + cliInternalBindingCount);
  629. size_t totalSize = embeddedCliRequiredSize(config);
  630. bool allocated = false;
  631. if (config->cliBuffer == NULL) {
  632. config->cliBuffer = (CLI_UINT *) malloc(totalSize); // malloc guarantees alignment.
  633. if (config->cliBuffer == NULL)
  634. return NULL;
  635. allocated = true;
  636. } else if (config->cliBufferSize < totalSize) {
  637. return NULL;
  638. }
  639. CLI_UINT *buf = config->cliBuffer;
  640. memset(buf, 0, totalSize);
  641. cli = (EmbeddedCli *) buf;
  642. buf += BYTES_TO_CLI_UINTS(sizeof(EmbeddedCli));
  643. cli->_impl = (EmbeddedCliImpl *) buf;
  644. buf += BYTES_TO_CLI_UINTS(sizeof(EmbeddedCliImpl));
  645. PREPARE_IMPL(cli);
  646. impl->rxBuffer.buf = (char *) buf;
  647. buf += BYTES_TO_CLI_UINTS(config->rxBufferSize * sizeof(char));
  648. impl->cmdBuffer = (char *) buf;
  649. buf += BYTES_TO_CLI_UINTS(config->cmdBufferSize * sizeof(char));
  650. impl->bindings = (CliCommandBinding *) buf;
  651. buf += BYTES_TO_CLI_UINTS(bindingCount * sizeof(CliCommandBinding));
  652. impl->bindingsFlags = (uint8_t *) buf;
  653. buf += BYTES_TO_CLI_UINTS(bindingCount);
  654. impl->history.buf = (char *) buf;
  655. impl->history.bufferSize = config->historyBufferSize;
  656. if (allocated)
  657. SET_FLAG(impl->flags, CLI_FLAG_ALLOCATED);
  658. if (config->enableAutoComplete)
  659. SET_FLAG(impl->flags, CLI_FLAG_AUTOCOMPLETE_ENABLED);
  660. impl->rxBuffer.size = config->rxBufferSize;
  661. impl->rxBuffer.front = 0;
  662. impl->rxBuffer.back = 0;
  663. impl->cmdMaxSize = config->cmdBufferSize;
  664. impl->bindingsCount = 0;
  665. impl->maxBindingsCount = (uint16_t) (config->maxBindingCount + cliInternalBindingCount);
  666. impl->lastChar = '\0';
  667. impl->invitation = "> ";
  668. initInternalBindings(cli);
  669. return cli;
  670. }
  671. EmbeddedCli *embeddedCliNewDefault(void) {
  672. return embeddedCliNew(embeddedCliDefaultConfig());
  673. }
  674. void embeddedCliReceiveChar(EmbeddedCli *cli, char c) {
  675. PREPARE_IMPL(cli);
  676. if (!fifoBufPush(&impl->rxBuffer, c)) {
  677. SET_FLAG(impl->flags, CLI_FLAG_OVERFLOW);
  678. }
  679. }
  680. void embeddedCliProcess(EmbeddedCli *cli) {
  681. if (cli->writeChar == NULL)
  682. return;
  683. PREPARE_IMPL(cli);
  684. if (!IS_FLAG_SET(impl->flags, CLI_FLAG_INIT_COMPLETE)) {
  685. SET_FLAG(impl->flags, CLI_FLAG_INIT_COMPLETE);
  686. writeToOutput(cli, impl->invitation);
  687. }
  688. while (fifoBufAvailable(&impl->rxBuffer)) {
  689. char c = fifoBufPop(&impl->rxBuffer);
  690. if (IS_FLAG_SET(impl->flags, CLI_FLAG_ESCAPE_MODE)) {
  691. onEscapedInput(cli, c);
  692. } else if (impl->lastChar == 0x1B && c == '[') {
  693. //enter escape mode
  694. SET_FLAG(impl->flags, CLI_FLAG_ESCAPE_MODE);
  695. } else if (isControlChar(c)) {
  696. onControlInput(cli, c);
  697. } else if (isDisplayableChar(c)) {
  698. onCharInput(cli, c);
  699. }
  700. printLiveAutocompletion(cli);
  701. impl->lastChar = c;
  702. }
  703. // discard unfinished command if overflow happened
  704. if (IS_FLAG_SET(impl->flags, CLI_FLAG_OVERFLOW)) {
  705. impl->cmdSize = 0;
  706. impl->cmdBuffer[impl->cmdSize] = '\0';
  707. UNSET_U8FLAG(impl->flags, CLI_FLAG_OVERFLOW);
  708. }
  709. }
  710. bool embeddedCliAddBinding(EmbeddedCli *cli, CliCommandBinding binding) {
  711. PREPARE_IMPL(cli);
  712. if (impl->bindingsCount == impl->maxBindingsCount)
  713. return false;
  714. impl->bindings[impl->bindingsCount] = binding;
  715. ++impl->bindingsCount;
  716. return true;
  717. }
  718. void embeddedCliPrint(EmbeddedCli *cli, const char *string) {
  719. if (cli->writeChar == NULL)
  720. return;
  721. PREPARE_IMPL(cli);
  722. // remove chars for autocompletion and live command
  723. if (!IS_FLAG_SET(impl->flags, CLI_FLAG_DIRECT_PRINT))
  724. clearCurrentLine(cli);
  725. // print provided string
  726. writeToOutput(cli, string);
  727. writeToOutput(cli, lineBreak);
  728. // print current command back to screen
  729. if (!IS_FLAG_SET(impl->flags, CLI_FLAG_DIRECT_PRINT)) {
  730. writeToOutput(cli, impl->invitation);
  731. writeToOutput(cli, impl->cmdBuffer);
  732. impl->inputLineLength = impl->cmdSize;
  733. printLiveAutocompletion(cli);
  734. }
  735. }
  736. void embeddedCliFree(EmbeddedCli *cli) {
  737. PREPARE_IMPL(cli);
  738. if (IS_FLAG_SET(impl->flags, CLI_FLAG_ALLOCATED)) {
  739. // allocation is done in single call to malloc, so need only single free
  740. free(cli);
  741. }
  742. }
  743. void embeddedCliTokenizeArgs(char *args) {
  744. if (args == NULL)
  745. return;
  746. // for now only space, but can add more later
  747. const char *separators = " ";
  748. // indicates that arg is quoted so separators are copied as is
  749. bool quotesEnabled = false;
  750. // indicates that previous char was a slash, so next char is copied as is
  751. bool escapeActivated = false;
  752. int insertPos = 0;
  753. int i = 0;
  754. char currentChar;
  755. while ((currentChar = args[i]) != '\0') {
  756. ++i;
  757. if (escapeActivated) {
  758. escapeActivated = false;
  759. } else if (currentChar == '\\') {
  760. escapeActivated = true;
  761. continue;
  762. } else if (currentChar == '"') {
  763. quotesEnabled = !quotesEnabled;
  764. currentChar = '\0';
  765. } else if (!quotesEnabled && strchr(separators, currentChar) != NULL) {
  766. currentChar = '\0';
  767. }
  768. // null chars are only copied once and not copied to the beginning
  769. if (currentChar != '\0' || (insertPos > 0 && args[insertPos - 1] != '\0')) {
  770. args[insertPos] = currentChar;
  771. ++insertPos;
  772. }
  773. }
  774. // make args double null-terminated source buffer must be big enough to contain extra spaces
  775. args[insertPos] = '\0';
  776. args[insertPos + 1] = '\0';
  777. }
  778. const char *embeddedCliGetToken(const char *tokenizedStr, uint16_t pos) {
  779. uint16_t i = getTokenPosition(tokenizedStr, pos);
  780. if (i != CLI_TOKEN_NPOS)
  781. return &tokenizedStr[i];
  782. else
  783. return NULL;
  784. }
  785. char *embeddedCliGetTokenVariable(char *tokenizedStr, uint16_t pos) {
  786. uint16_t i = getTokenPosition(tokenizedStr, pos);
  787. if (i != CLI_TOKEN_NPOS)
  788. return &tokenizedStr[i];
  789. else
  790. return NULL;
  791. }
  792. uint16_t embeddedCliFindToken(const char *tokenizedStr, const char *token) {
  793. if (tokenizedStr == NULL || token == NULL)
  794. return 0;
  795. uint16_t size = embeddedCliGetTokenCount(tokenizedStr);
  796. for (uint16_t i = 1; i <= size; ++i) {
  797. if (strcmp(embeddedCliGetToken(tokenizedStr, i), token) == 0)
  798. return i;
  799. }
  800. return 0;
  801. }
  802. uint16_t embeddedCliGetTokenCount(const char *tokenizedStr) {
  803. if (tokenizedStr == NULL || tokenizedStr[0] == '\0')
  804. return 0;
  805. int i = 0;
  806. uint16_t tokenCount = 1;
  807. while (true) {
  808. if (tokenizedStr[i] == '\0') {
  809. if (tokenizedStr[i + 1] == '\0')
  810. break;
  811. ++tokenCount;
  812. }
  813. ++i;
  814. }
  815. return tokenCount;
  816. }
  817. static void navigateHistory(EmbeddedCli *cli, bool navigateUp) {
  818. PREPARE_IMPL(cli);
  819. if (impl->history.itemsCount == 0 ||
  820. (navigateUp && impl->history.current == impl->history.itemsCount) ||
  821. (!navigateUp && impl->history.current == 0))
  822. return;
  823. clearCurrentLine(cli);
  824. writeToOutput(cli, impl->invitation);
  825. if (navigateUp)
  826. ++impl->history.current;
  827. else
  828. --impl->history.current;
  829. const char *item = historyGet(&impl->history, impl->history.current);
  830. // simple way to handle empty command the same way as others
  831. if (item == NULL)
  832. item = "";
  833. uint16_t len = (uint16_t) strlen(item);
  834. memcpy(impl->cmdBuffer, item, len);
  835. impl->cmdBuffer[len] = '\0';
  836. impl->cmdSize = len;
  837. writeToOutput(cli, impl->cmdBuffer);
  838. impl->inputLineLength = impl->cmdSize;
  839. printLiveAutocompletion(cli);
  840. }
  841. static void onEscapedInput(EmbeddedCli *cli, char c) {
  842. PREPARE_IMPL(cli);
  843. if (c >= 64 && c <= 126) {
  844. // handle escape sequence
  845. UNSET_U8FLAG(impl->flags, CLI_FLAG_ESCAPE_MODE);
  846. if (c == 'A' || c == 'B') {
  847. // treat \e[..A as cursor up and \e[..B as cursor down
  848. // there might be extra chars between [ and A/B, just ignore them
  849. navigateHistory(cli, c == 'A');
  850. }
  851. }
  852. }
  853. static void onCharInput(EmbeddedCli *cli, char c) {
  854. PREPARE_IMPL(cli);
  855. // have to reserve two extra chars for command ending (used in tokenization)
  856. if (impl->cmdSize + 2 >= impl->cmdMaxSize)
  857. return;
  858. impl->cmdBuffer[impl->cmdSize] = c;
  859. ++impl->cmdSize;
  860. impl->cmdBuffer[impl->cmdSize] = '\0';
  861. cli->writeChar(cli, c);
  862. }
  863. static void onControlInput(EmbeddedCli *cli, char c) {
  864. PREPARE_IMPL(cli);
  865. // process \r\n and \n\r as single \r\n command
  866. if ((impl->lastChar == '\r' && c == '\n') ||
  867. (impl->lastChar == '\n' && c == '\r'))
  868. return;
  869. if (c == '\r' || c == '\n') {
  870. // try to autocomplete command and then process it
  871. onAutocompleteRequest(cli);
  872. writeToOutput(cli, lineBreak);
  873. if (impl->cmdSize > 0)
  874. parseCommand(cli);
  875. impl->cmdSize = 0;
  876. impl->cmdBuffer[impl->cmdSize] = '\0';
  877. impl->inputLineLength = 0;
  878. impl->history.current = 0;
  879. writeToOutput(cli, impl->invitation);
  880. } else if ((c == '\b' || c == 0x7F) && impl->cmdSize > 0) {
  881. // remove char from screen
  882. cli->writeChar(cli, '\b');
  883. cli->writeChar(cli, ' ');
  884. cli->writeChar(cli, '\b');
  885. // and from buffer
  886. --impl->cmdSize;
  887. impl->cmdBuffer[impl->cmdSize] = '\0';
  888. } else if (c == '\t') {
  889. onAutocompleteRequest(cli);
  890. }
  891. }
  892. static void parseCommand(EmbeddedCli *cli) {
  893. PREPARE_IMPL(cli);
  894. bool isEmpty = true;
  895. for (int i = 0; i < impl->cmdSize; ++i) {
  896. if (impl->cmdBuffer[i] != ' ') {
  897. isEmpty = false;
  898. break;
  899. }
  900. }
  901. // do not process empty commands
  902. if (isEmpty)
  903. return;
  904. // push command to history before buffer is modified
  905. historyPut(&impl->history, impl->cmdBuffer);
  906. char *cmdName = NULL;
  907. char *cmdArgs = NULL;
  908. bool nameFinished = false;
  909. // find command name and command args inside command buffer
  910. for (int i = 0; i < impl->cmdSize; ++i) {
  911. char c = impl->cmdBuffer[i];
  912. if (c == ' ') {
  913. // all spaces between name and args are filled with zeros
  914. // so name is a correct null-terminated string
  915. if (cmdArgs == NULL)
  916. impl->cmdBuffer[i] = '\0';
  917. if (cmdName != NULL)
  918. nameFinished = true;
  919. } else if (cmdName == NULL) {
  920. cmdName = &impl->cmdBuffer[i];
  921. } else if (cmdArgs == NULL && nameFinished) {
  922. cmdArgs = &impl->cmdBuffer[i];
  923. }
  924. }
  925. // we keep two last bytes in cmd buffer reserved so cmdSize is always by 2
  926. // less than cmdMaxSize
  927. impl->cmdBuffer[impl->cmdSize + 1] = '\0';
  928. if (cmdName == NULL)
  929. return;
  930. // try to find command in bindings
  931. for (int i = 0; i < impl->bindingsCount; ++i) {
  932. if (strcmp(cmdName, impl->bindings[i].name) == 0) {
  933. if (impl->bindings[i].binding == NULL)
  934. break;
  935. if (impl->bindings[i].tokenizeArgs)
  936. embeddedCliTokenizeArgs(cmdArgs);
  937. // currently, output is blank line, so we can just print directly
  938. SET_FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT);
  939. impl->bindings[i].binding(cli, cmdArgs, impl->bindings[i].context);
  940. UNSET_U8FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT);
  941. return;
  942. }
  943. }
  944. // command not found in bindings or binding was null
  945. // try to call default callback
  946. if (cli->onCommand != NULL) {
  947. CliCommand command;
  948. command.name = cmdName;
  949. command.args = cmdArgs;
  950. // currently, output is blank line, so we can just print directly
  951. SET_FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT);
  952. cli->onCommand(cli, &command);
  953. UNSET_U8FLAG(impl->flags, CLI_FLAG_DIRECT_PRINT);
  954. } else {
  955. onUnknownCommand(cli, cmdName);
  956. }
  957. }
  958. static void initInternalBindings(EmbeddedCli *cli) {
  959. CliCommandBinding b = {
  960. "help",
  961. "Print list of commands",
  962. true,
  963. NULL,
  964. onHelp
  965. };
  966. embeddedCliAddBinding(cli, b);
  967. }
  968. static void onHelp(EmbeddedCli *cli, char *tokens, void *context) {
  969. UNUSED(context);
  970. PREPARE_IMPL(cli);
  971. if (impl->bindingsCount == 0) {
  972. writeToOutput(cli, "Help is not available");
  973. writeToOutput(cli, lineBreak);
  974. return;
  975. }
  976. uint16_t tokenCount = embeddedCliGetTokenCount(tokens);
  977. if (tokenCount == 0) {
  978. for (int i = 0; i < impl->bindingsCount; ++i) {
  979. writeToOutput(cli, " * ");
  980. writeToOutput(cli, impl->bindings[i].name);
  981. writeToOutput(cli, lineBreak);
  982. if (impl->bindings[i].help != NULL) {
  983. cli->writeChar(cli, '\t');
  984. writeToOutput(cli, impl->bindings[i].help);
  985. writeToOutput(cli, lineBreak);
  986. }
  987. }
  988. } else if (tokenCount == 1) {
  989. // try find command
  990. const char *helpStr = NULL;
  991. const char *cmdName = embeddedCliGetToken(tokens, 1);
  992. bool found = false;
  993. for (int i = 0; i < impl->bindingsCount; ++i) {
  994. if (strcmp(impl->bindings[i].name, cmdName) == 0) {
  995. helpStr = impl->bindings[i].help;
  996. found = true;
  997. break;
  998. }
  999. }
  1000. if (found && helpStr != NULL) {
  1001. writeToOutput(cli, " * ");
  1002. writeToOutput(cli, cmdName);
  1003. writeToOutput(cli, lineBreak);
  1004. cli->writeChar(cli, '\t');
  1005. writeToOutput(cli, helpStr);
  1006. writeToOutput(cli, lineBreak);
  1007. } else if (found) {
  1008. writeToOutput(cli, "Help is not available");
  1009. writeToOutput(cli, lineBreak);
  1010. } else {
  1011. onUnknownCommand(cli, cmdName);
  1012. }
  1013. } else {
  1014. writeToOutput(cli, "Command \"help\" receives one or zero arguments");
  1015. writeToOutput(cli, lineBreak);
  1016. }
  1017. }
  1018. static void onUnknownCommand(EmbeddedCli *cli, const char *name) {
  1019. writeToOutput(cli, "Unknown command: \"");
  1020. writeToOutput(cli, name);
  1021. writeToOutput(cli, "\". Write \"help\" for a list of available commands");
  1022. writeToOutput(cli, lineBreak);
  1023. }
  1024. static AutocompletedCommand getAutocompletedCommand(EmbeddedCli *cli, const char *prefix) {
  1025. AutocompletedCommand cmd = {NULL, 0, 0};
  1026. size_t prefixLen = strlen(prefix);
  1027. PREPARE_IMPL(cli);
  1028. if (impl->bindingsCount == 0 || prefixLen == 0)
  1029. return cmd;
  1030. for (int i = 0; i < impl->bindingsCount; ++i) {
  1031. const char *name = impl->bindings[i].name;
  1032. size_t len = strlen(name);
  1033. // unset autocomplete flag
  1034. UNSET_U8FLAG(impl->bindingsFlags[i], BINDING_FLAG_AUTOCOMPLETE);
  1035. if (len < prefixLen)
  1036. continue;
  1037. // check if this command is candidate for autocomplete
  1038. bool isCandidate = true;
  1039. for (size_t j = 0; j < prefixLen; ++j) {
  1040. if (prefix[j] != name[j]) {
  1041. isCandidate = false;
  1042. break;
  1043. }
  1044. }
  1045. if (!isCandidate)
  1046. continue;
  1047. impl->bindingsFlags[i] |= BINDING_FLAG_AUTOCOMPLETE;
  1048. if (cmd.candidateCount == 0 || len < cmd.autocompletedLen)
  1049. cmd.autocompletedLen = (uint16_t) len;
  1050. ++cmd.candidateCount;
  1051. if (cmd.candidateCount == 1) {
  1052. cmd.firstCandidate = name;
  1053. continue;
  1054. }
  1055. for (size_t j = impl->cmdSize; j < cmd.autocompletedLen; ++j) {
  1056. if (cmd.firstCandidate[j] != name[j]) {
  1057. cmd.autocompletedLen = (uint16_t) j;
  1058. break;
  1059. }
  1060. }
  1061. }
  1062. return cmd;
  1063. }
  1064. static void printLiveAutocompletion(EmbeddedCli *cli) {
  1065. PREPARE_IMPL(cli);
  1066. if (!IS_FLAG_SET(impl->flags, CLI_FLAG_AUTOCOMPLETE_ENABLED))
  1067. return;
  1068. AutocompletedCommand cmd = getAutocompletedCommand(cli, impl->cmdBuffer);
  1069. if (cmd.candidateCount == 0) {
  1070. cmd.autocompletedLen = impl->cmdSize;
  1071. }
  1072. // print live autocompletion (or nothing, if it doesn't exist)
  1073. for (size_t i = impl->cmdSize; i < cmd.autocompletedLen; ++i) {
  1074. cli->writeChar(cli, cmd.firstCandidate[i]);
  1075. }
  1076. // replace with spaces previous autocompletion
  1077. for (size_t i = cmd.autocompletedLen; i < impl->inputLineLength; ++i) {
  1078. cli->writeChar(cli, ' ');
  1079. }
  1080. impl->inputLineLength = cmd.autocompletedLen;
  1081. cli->writeChar(cli, '\r');
  1082. // print current command again so cursor is moved to initial place
  1083. writeToOutput(cli, impl->invitation);
  1084. writeToOutput(cli, impl->cmdBuffer);
  1085. }
  1086. static void onAutocompleteRequest(EmbeddedCli *cli) {
  1087. PREPARE_IMPL(cli);
  1088. AutocompletedCommand cmd = getAutocompletedCommand(cli, impl->cmdBuffer);
  1089. if (cmd.candidateCount == 0)
  1090. return;
  1091. if (cmd.candidateCount == 1 || cmd.autocompletedLen > impl->cmdSize) {
  1092. // can copy from index cmdSize, but prefix is the same, so copy everything
  1093. memcpy(impl->cmdBuffer, cmd.firstCandidate, cmd.autocompletedLen);
  1094. if (cmd.candidateCount == 1) {
  1095. impl->cmdBuffer[cmd.autocompletedLen] = ' ';
  1096. ++cmd.autocompletedLen;
  1097. }
  1098. impl->cmdBuffer[cmd.autocompletedLen] = '\0';
  1099. writeToOutput(cli, &impl->cmdBuffer[impl->cmdSize]);
  1100. impl->cmdSize = cmd.autocompletedLen;
  1101. impl->inputLineLength = impl->cmdSize;
  1102. return;
  1103. }
  1104. // with multiple candidates when we already completed to common prefix
  1105. // we show all candidates and print input again
  1106. // we need to completely clear current line since it begins with invitation
  1107. clearCurrentLine(cli);
  1108. for (int i = 0; i < impl->bindingsCount; ++i) {
  1109. // autocomplete flag is set for all candidates by last call to
  1110. // getAutocompletedCommand
  1111. if (!(impl->bindingsFlags[i] & BINDING_FLAG_AUTOCOMPLETE))
  1112. continue;
  1113. const char *name = impl->bindings[i].name;
  1114. writeToOutput(cli, name);
  1115. writeToOutput(cli, lineBreak);
  1116. }
  1117. writeToOutput(cli, impl->invitation);
  1118. writeToOutput(cli, impl->cmdBuffer);
  1119. impl->inputLineLength = impl->cmdSize;
  1120. }
  1121. static void clearCurrentLine(EmbeddedCli *cli) {
  1122. PREPARE_IMPL(cli);
  1123. size_t len = impl->inputLineLength + strlen(impl->invitation);
  1124. cli->writeChar(cli, '\r');
  1125. for (size_t i = 0; i < len; ++i) {
  1126. cli->writeChar(cli, ' ');
  1127. }
  1128. cli->writeChar(cli, '\r');
  1129. impl->inputLineLength = 0;
  1130. }
  1131. static void writeToOutput(EmbeddedCli *cli, const char *str) {
  1132. size_t len = strlen(str);
  1133. for (size_t i = 0; i < len; ++i) {
  1134. cli->writeChar(cli, str[i]);
  1135. }
  1136. }
  1137. static bool isControlChar(char c) {
  1138. return c == '\r' || c == '\n' || c == '\b' || c == '\t' || c == 0x7F;
  1139. }
  1140. static bool isDisplayableChar(char c) {
  1141. return (c >= 32 && c <= 126);
  1142. }
  1143. static uint16_t fifoBufAvailable(FifoBuf *buffer) {
  1144. if (buffer->back >= buffer->front)
  1145. return (uint16_t) (buffer->back - buffer->front);
  1146. else
  1147. return (uint16_t) (buffer->size - buffer->front + buffer->back);
  1148. }
  1149. static char fifoBufPop(FifoBuf *buffer) {
  1150. char a = '\0';
  1151. if (buffer->front != buffer->back) {
  1152. a = buffer->buf[buffer->front];
  1153. buffer->front = (uint16_t) (buffer->front + 1) % buffer->size;
  1154. }
  1155. return a;
  1156. }
  1157. static bool fifoBufPush(FifoBuf *buffer, char a) {
  1158. uint16_t newBack = (uint16_t) (buffer->back + 1) % buffer->size;
  1159. if (newBack != buffer->front) {
  1160. buffer->buf[buffer->back] = a;
  1161. buffer->back = newBack;
  1162. return true;
  1163. }
  1164. return false;
  1165. }
  1166. static bool historyPut(CliHistory *history, const char *str) {
  1167. size_t len = strlen(str);
  1168. // each item is ended with \0 so, need to have that much space at least
  1169. if (history->bufferSize < len + 1)
  1170. return false;
  1171. // remove str from history (if it's present) so we don't get duplicates
  1172. historyRemove(history, str);
  1173. size_t usedSize;
  1174. // remove old items if new one can't fit into buffer
  1175. while (history->itemsCount > 0) {
  1176. const char *item = historyGet(history, history->itemsCount);
  1177. size_t itemLen = strlen(item);
  1178. usedSize = ((size_t) (item - history->buf)) + itemLen + 1;
  1179. size_t freeSpace = history->bufferSize - usedSize;
  1180. if (freeSpace >= len + 1)
  1181. break;
  1182. // space not enough, remove last element
  1183. --history->itemsCount;
  1184. }
  1185. if (history->itemsCount > 0) {
  1186. // when history not empty, shift elements so new item is first
  1187. memmove(&history->buf[len + 1], history->buf, usedSize);
  1188. }
  1189. memcpy(history->buf, str, len + 1);
  1190. ++history->itemsCount;
  1191. return true;
  1192. }
  1193. static const char *historyGet(CliHistory *history, uint16_t item) {
  1194. if (item == 0 || item > history->itemsCount)
  1195. return NULL;
  1196. // items are stored in the same way (separated by \0 and counted from 1),
  1197. // so can use this call
  1198. return embeddedCliGetToken(history->buf, item);
  1199. }
  1200. static void historyRemove(CliHistory *history, const char *str) {
  1201. if (str == NULL || history->itemsCount == 0)
  1202. return;
  1203. char *item = NULL;
  1204. uint16_t itemPosition;
  1205. for (itemPosition = 1; itemPosition <= history->itemsCount; ++itemPosition) {
  1206. // items are stored in the same way (separated by \0 and counted from 1),
  1207. // so can use this call
  1208. item = embeddedCliGetTokenVariable(history->buf, itemPosition);
  1209. if (strcmp(item, str) == 0) {
  1210. break;
  1211. }
  1212. item = NULL;
  1213. }
  1214. if (item == NULL)
  1215. return;
  1216. --history->itemsCount;
  1217. if (itemPosition == (history->itemsCount + 1)) {
  1218. // if this is a last element, nothing is remaining to move
  1219. return;
  1220. }
  1221. size_t len = strlen(item);
  1222. size_t remaining = (size_t) (history->bufferSize - (item + len + 1 - history->buf));
  1223. // move everything to the right of found item
  1224. memmove(item, &item[len + 1], remaining);
  1225. }
  1226. static uint16_t getTokenPosition(const char *tokenizedStr, uint16_t pos) {
  1227. if (tokenizedStr == NULL || pos == 0)
  1228. return CLI_TOKEN_NPOS;
  1229. uint16_t i = 0;
  1230. uint16_t tokenCount = 1;
  1231. while (true) {
  1232. if (tokenCount == pos)
  1233. break;
  1234. if (tokenizedStr[i] == '\0') {
  1235. ++tokenCount;
  1236. if (tokenizedStr[i + 1] == '\0')
  1237. break;
  1238. }
  1239. ++i;
  1240. }
  1241. if (tokenizedStr[i] != '\0')
  1242. return i;
  1243. else
  1244. return CLI_TOKEN_NPOS;
  1245. }
  1246. #ifdef __cplusplus
  1247. }
  1248. #endif
  1249. #endif // EMBEDDED_CLI_IMPL_GUARD
  1250. #endif // EMBEDDED_CLI_IMPL