35 |
35 |
/******************************************************************************/
|
36 |
36 |
|
37 |
37 |
/**
|
|
38 |
* @brief The character the input buffer is initialized with.
|
|
39 |
*/
|
|
40 |
#define INBUF_INIT_CHAR '\x07'
|
|
41 |
|
|
42 |
/**
|
38 |
43 |
* @brief Event mask to be set on OS related events.
|
39 |
44 |
*/
|
40 |
|
#define AOS_SHELL_EVENTMASK_OS EVENT_MASK(0)
|
|
45 |
#define EVENTMASK_OS EVENT_MASK(0)
|
41 |
46 |
|
42 |
47 |
/**
|
43 |
48 |
* @brief Event mask to be set on a input event.
|
44 |
49 |
*/
|
45 |
|
#define AOS_SHELL_EVENTMASK_INPUT EVENT_MASK(1)
|
|
50 |
#define EVENTMASK_INPUT EVENT_MASK(1)
|
|
51 |
|
|
52 |
/**
|
|
53 |
* @brief String that defines the INSERT key as specified by VT100.
|
|
54 |
*/
|
|
55 |
#define KEYSTRING_INSERT "\x1B\x5B\x32\x7E"
|
|
56 |
|
|
57 |
/**
|
|
58 |
* @brief String that defines the DEL key as specified by VT100.
|
|
59 |
*/
|
|
60 |
#define KEYSTRING_DELETE "\x1B\x5B\x33\x7E"
|
|
61 |
|
|
62 |
/**
|
|
63 |
* @brief String that defines the HOME key as specified by VT100.
|
|
64 |
*/
|
|
65 |
#define KEYSTRING_HOME "\x1B\x5B\x48"
|
|
66 |
|
|
67 |
/**
|
|
68 |
* @brief String that defines the END key as specified by VT100.
|
|
69 |
*/
|
|
70 |
#define KEYSTRING_END "\x1B\x5B\x46"
|
|
71 |
|
|
72 |
/**
|
|
73 |
* @brief String that defines the PGUP key as specified by VT100.
|
|
74 |
*/
|
|
75 |
#define KEYSTRING_PAGEUP "\x1B\x5B\x35\x7E"
|
|
76 |
|
|
77 |
/**
|
|
78 |
* @brief String that defines the PGUP key as specified by VT100.
|
|
79 |
*/
|
|
80 |
#define KEYSTRING_PAGEDOWN "\x1B\x5B\x36\x7E"
|
|
81 |
|
|
82 |
/**
|
|
83 |
* @brief String that defines the 'arrow down' key as specified by VT100.
|
|
84 |
*/
|
|
85 |
#define KEYSTRING_ARROWUP "\x1B\x5B\x41"
|
|
86 |
|
|
87 |
/**
|
|
88 |
* @brief String that defines the 'arrow up' key as specified by VT100.
|
|
89 |
*/
|
|
90 |
#define KEYSTRING_ARROWDOWN "\x1B\x5B\x42"
|
|
91 |
|
|
92 |
/**
|
|
93 |
* @brief String that defines the 'arrow left' key as specified by VT100.
|
|
94 |
*/
|
|
95 |
#define KEYSTRING_ARROWLEFT "\x1B\x5B\x44"
|
|
96 |
|
|
97 |
/**
|
|
98 |
* @brief String that defines the 'arrow right' key as specified by VT100.
|
|
99 |
*/
|
|
100 |
#define KEYSTRING_ARROWRIGHT "\x1B\x5B\x43"
|
|
101 |
|
|
102 |
/**
|
|
103 |
* @brief String that defines the CRTL + 'arrow up' key combination as specified by VT100.
|
|
104 |
*/
|
|
105 |
#define KEYSTRING_CTRL_ARROWUP "\x1B\x5B\x31\x3B\x35\x41"
|
|
106 |
|
|
107 |
/**
|
|
108 |
* @brief String that defines the CRTL + 'arrow down' key combination as specified by VT100.
|
|
109 |
*/
|
|
110 |
#define KEYSTRING_CTRL_ARROWDOWN "\x1B\x5B\x31\x3B\x35\x42"
|
|
111 |
|
|
112 |
/**
|
|
113 |
* @brief String that defines the CRTL + 'arrow left' key combination as specified by VT100.
|
|
114 |
*/
|
|
115 |
#define KEYSTRING_CTRL_ARROWLEFT "\x1B\x5B\x31\x3B\x35\x44"
|
|
116 |
|
|
117 |
/**
|
|
118 |
* @brief String that defines the CRTL + 'arrow right' key combination as specified by VT100.
|
|
119 |
*/
|
|
120 |
#define KEYSTRING_CTRL_ARROWRIGHT "\x1B\x5B\x31\x3B\x35\x43"
|
46 |
121 |
|
47 |
122 |
/******************************************************************************/
|
48 |
123 |
/* EXPORTED VARIABLES */
|
... | ... | |
97 |
172 |
KEY_UNKNOWN, /**< any/unknow key */
|
98 |
173 |
KEY_AMBIGUOUS, /**< key is ambiguous */
|
99 |
174 |
KEY_TAB, /**< tabulator key */
|
100 |
|
KEY_ESCAPE, /**< escape key */
|
101 |
175 |
KEY_BACKSPACE, /**< backspace key */
|
102 |
176 |
KEY_INSERT, /**< insert key */
|
103 |
177 |
KEY_DELETE, /**< delete key */
|
|
178 |
KEY_ESCAPE, /**< escape key */
|
104 |
179 |
KEY_HOME, /**< home key */
|
105 |
180 |
KEY_END, /**< end key */
|
106 |
|
KEY_PAGE_UP, /**< page up key */
|
107 |
|
KEY_PAGE_DOWN, /**< page down key */
|
108 |
|
KEY_ARROW_UP, /**< arrow up key */
|
109 |
|
KEY_ARROW_DOWN, /**< arrow down key */
|
110 |
|
KEY_ARROW_LEFT, /**< arrow left key */
|
111 |
|
KEY_ARROW_RIGHT, /**< arrow right key */
|
112 |
|
KEY_CTRL_ARROW_UP, /**< CTRL + arrow up key */
|
113 |
|
KEY_CTRL_ARROW_DOWN, /**< CTRL + arrow down key */
|
114 |
|
KEY_CTRL_ARROW_LEFT, /**< CTRL + arrow left key */
|
115 |
|
KEY_CTRL_ARROW_RIGHT, /**< CTRL + arrow right key */
|
|
181 |
KEY_PAGEUP, /**< page up key */
|
|
182 |
KEY_PAGEDOWN, /**< page down key */
|
|
183 |
KEY_ARROWUP, /**< arrow up key */
|
|
184 |
KEY_ARROWDOWN, /**< arrow down key */
|
|
185 |
KEY_ARROWLEFT, /**< arrow left key */
|
|
186 |
KEY_ARROWRIGHT, /**< arrow right key */
|
|
187 |
KEY_CTRL_ARROWUP, /**< CTRL + arrow up key */
|
|
188 |
KEY_CTRL_ARROWDOWN, /**< CTRL + arrow down key */
|
|
189 |
KEY_CTRL_ARROWLEFT, /**< CTRL + arrow left key */
|
|
190 |
KEY_CTRL_ARROWRIGHT, /**< CTRL + arrow right key */
|
|
191 |
KEY_CTRL_C, /**< CTRL + C key */
|
116 |
192 |
} special_key_t;
|
117 |
193 |
|
118 |
194 |
/**
|
... | ... | |
124 |
200 |
CHAR_MATCH_CASE = 2, /**< Characters do match with case. */
|
125 |
201 |
} charmatch_t;
|
126 |
202 |
|
|
203 |
/**
|
|
204 |
* @brief Enumerator to encode shell actions.
|
|
205 |
*/
|
|
206 |
typedef enum aos_shellaction {
|
|
207 |
ACTION_NONE, /**< No action at all. */
|
|
208 |
ACTION_READCHAR, /**< Read a printable character. */
|
|
209 |
ACTION_AUTOCOMPLETE, /**< Automatically comlete input by using available command. */
|
|
210 |
ACTION_SUGGEST, /**< Suggest matching available commands. */
|
|
211 |
ACTION_EXECUTE, /**< Execute input. */
|
|
212 |
ACTION_DELETEBACKWARD, /**< Delete a single character backwards. */
|
|
213 |
ACTION_DELETEFORWARD, /**< Delete a single character forwards. */
|
|
214 |
ACTION_CLEAR, /**< Clear the input. */
|
|
215 |
ACTION_RECALLPREVIOUS, /**< Recall the previous (older) entry in the history. */
|
|
216 |
ACTION_RECALLNEXT, /**< Recall the next (more recent) entry in the history. */
|
|
217 |
ACTION_RECALLOLDEST, /**< Recall the oldest entry in the history. */
|
|
218 |
ACTION_RECALLCURRENT, /**< Recall the current input. */
|
|
219 |
ACTION_CURSORLEFT, /**< Move cursor one character to the left. */
|
|
220 |
ACTION_CURSORRIGHT, /**< Move cursor one character to the right. */
|
|
221 |
ACTION_CURSORWORDLEFT, /**< Move cursor one word to the left. */
|
|
222 |
ACTION_CURSORWORDRIGHT, /**< Move cursor one word to the right. */
|
|
223 |
ACTION_CURSOR2END, /**< Move cursor to the very right. */
|
|
224 |
ACTION_CURSOR2START, /**< Move cursor to the very left. */
|
|
225 |
ACTION_RESET, /**< Reset the current input. */
|
|
226 |
ACTION_INSERTTOGGLE, /**< Toggle insertion mode. */
|
|
227 |
ACTION_ESCSTART, /**< Start an escape sequence (special keys). */
|
|
228 |
ACTION_PRINTUNKNOWNSEQUENCE, /**< Print an unknown escape sequence. */
|
|
229 |
} action_t;
|
|
230 |
|
|
231 |
/**
|
|
232 |
* @brief Struct that holds most important runtime data for the shell.
|
|
233 |
* @details The structure is to be used by the shell thread main function as some kind of structured stack, which can be easily passed to other functions.
|
|
234 |
*/
|
|
235 |
typedef struct runtimedata {
|
|
236 |
/**
|
|
237 |
* @brief Data related to the current input.
|
|
238 |
*/
|
|
239 |
struct {
|
|
240 |
/**
|
|
241 |
* @brief Length of the input.
|
|
242 |
*/
|
|
243 |
size_t length;
|
|
244 |
|
|
245 |
/**
|
|
246 |
* @brief Current position of the cursor in the input line.
|
|
247 |
*/
|
|
248 |
size_t cursorpos;
|
|
249 |
|
|
250 |
/**
|
|
251 |
* @brief Buffer to store escape sequences, which describe special characters.
|
|
252 |
*/
|
|
253 |
char escseq[AOS_SHELL_ESCSEQUENCE_LENGTH];
|
|
254 |
} input;
|
|
255 |
|
|
256 |
/**
|
|
257 |
* @brief Data related to the entry or history buffer.
|
|
258 |
*/
|
|
259 |
struct {
|
|
260 |
/**
|
|
261 |
* @brief Current entry to be filled and executed.
|
|
262 |
*/
|
|
263 |
size_t current;
|
|
264 |
|
|
265 |
/**
|
|
266 |
* @brief Selected entry in the 'history' as preview.
|
|
267 |
* @details A value of 0 indicates, that the line is cleared as a preview.
|
|
268 |
* A value of 1 indicates, that the current entry is selected.
|
|
269 |
* A value of t>1 indicates, that the entry t-1 in the past is selected.
|
|
270 |
* The value must never be greater than the number of entries available, of course.
|
|
271 |
*/
|
|
272 |
size_t selected;
|
|
273 |
|
|
274 |
/**
|
|
275 |
* @brief Selected entry in the 'history' that has been edited by the user.
|
|
276 |
* A value of 0 indicates, that there was no modification by the user yet (i.e. charcters, deletions or autofill).
|
|
277 |
* A value of 1 indicates, that the current entry was edited.
|
|
278 |
* A value of t>1 indicated, that a history entry was recalled and then edited.
|
|
279 |
*/
|
|
280 |
size_t edited;
|
|
281 |
} buffer;
|
|
282 |
|
|
283 |
/**
|
|
284 |
* @brief The last action executed by the shell.
|
|
285 |
*/
|
|
286 |
action_t lastaction;
|
|
287 |
} runtimedata_t;
|
|
288 |
|
127 |
289 |
/******************************************************************************/
|
128 |
290 |
/* LOCAL VARIABLES */
|
129 |
291 |
/******************************************************************************/
|
... | ... | |
248 |
410 |
return MSG_OK;
|
249 |
411 |
}
|
250 |
412 |
|
|
413 |
/**
|
|
414 |
* @brief Implementation of the BaseSequentialStream write() method.
|
|
415 |
*/
|
251 |
416 |
static size_t _streamwrite(void *instance, const uint8_t *bp, size_t n)
|
252 |
417 |
{
|
253 |
418 |
aosDbgCheck(instance != NULL);
|
... | ... | |
267 |
432 |
return maxbytes;
|
268 |
433 |
}
|
269 |
434 |
|
|
435 |
/**
|
|
436 |
* @brief Implementation of the BaseSequentialStream read() method.
|
|
437 |
*/
|
270 |
438 |
static size_t _stremread(void *instance, uint8_t *bp, size_t n)
|
271 |
439 |
{
|
272 |
440 |
(void)instance;
|
... | ... | |
276 |
444 |
return 0;
|
277 |
445 |
}
|
278 |
446 |
|
|
447 |
/**
|
|
448 |
* @brief Implementation of the BaseSequentialStream put() method.
|
|
449 |
*/
|
279 |
450 |
static msg_t _streamput(void *instance, uint8_t b)
|
280 |
451 |
{
|
281 |
452 |
aosDbgCheck(instance != NULL);
|
... | ... | |
294 |
465 |
return ret;
|
295 |
466 |
}
|
296 |
467 |
|
|
468 |
/**
|
|
469 |
* @brief Implementation of the BaseSequentialStream get() method.
|
|
470 |
*/
|
297 |
471 |
static msg_t _streamget(void *instance)
|
298 |
472 |
{
|
299 |
473 |
(void)instance;
|
... | ... | |
302 |
476 |
}
|
303 |
477 |
|
304 |
478 |
/**
|
|
479 |
* @brief Retreive a pointer to the string buffer of a specified entry in the input buffer.
|
|
480 |
*
|
|
481 |
* @param[in] shell Pointer to a shell object.
|
|
482 |
* @param[in] entry Entry to be retrieved.
|
|
483 |
*
|
|
484 |
* @return Pointer to the entry in the input buffer.
|
|
485 |
*/
|
|
486 |
static inline char* _getAbsoluteEntry(const aos_shell_t* shell, size_t entry)
|
|
487 |
{
|
|
488 |
aosDbgCheck(shell != NULL);
|
|
489 |
aosDbgCheck(entry < shell->input.nentries);
|
|
490 |
|
|
491 |
return &(shell->input.buffer[entry * shell->input.linewidth * sizeof(char)]);
|
|
492 |
}
|
|
493 |
|
|
494 |
/**
|
|
495 |
* @brief Calculate absolute entry from history offset.
|
|
496 |
*
|
|
497 |
* @param[in] shell Pointer to a shell object.
|
|
498 |
* @param[in] rdata Pointer to a runtime data object.
|
|
499 |
* @param[in] offset Relative offset of the entry to be retreived.
|
|
500 |
*
|
|
501 |
* @return Absolute index of the historic entry.
|
|
502 |
*/
|
|
503 |
static inline size_t _historyOffset2EntryIndex(const aos_shell_t* shell, const runtimedata_t* rdata, size_t offset)
|
|
504 |
{
|
|
505 |
aosDbgCheck(shell != NULL);
|
|
506 |
aosDbgCheck(rdata != NULL);
|
|
507 |
aosDbgCheck(offset < shell->input.nentries);
|
|
508 |
|
|
509 |
return ((shell->input.nentries + rdata->buffer.current - offset) % shell->input.nentries);
|
|
510 |
}
|
|
511 |
|
|
512 |
/**
|
|
513 |
* @brief Retreive a pointer to the string buffer of a historic entry in the input buffer.
|
|
514 |
*
|
|
515 |
* @param[in] shell Pointer to a shell object.
|
|
516 |
* @param[in] rdata Pointer to a runtime data object.
|
|
517 |
* @param[in] offset Relative offset of the entry to be retreived.
|
|
518 |
*
|
|
519 |
* @return Pointer to the entry in the input buffer.
|
|
520 |
*/
|
|
521 |
static inline char* _getRelativeEntry(const aos_shell_t* shell, const runtimedata_t* rdata, size_t offset)
|
|
522 |
{
|
|
523 |
aosDbgCheck(shell != NULL);
|
|
524 |
aosDbgCheck(rdata != NULL);
|
|
525 |
aosDbgCheck(offset < shell->input.nentries);
|
|
526 |
|
|
527 |
return _getAbsoluteEntry(shell, _historyOffset2EntryIndex(shell, rdata, offset));
|
|
528 |
}
|
|
529 |
|
|
530 |
/**
|
|
531 |
* @brief Retreive a pointer to the current entry string in the input buffer.
|
|
532 |
*
|
|
533 |
* @param[in] shell Pointer to a shell object.
|
|
534 |
* @param[in] rdata Pointer to a runtime data object.
|
|
535 |
*
|
|
536 |
* @return Pointer to the string of the current entry in the input buffer.
|
|
537 |
*/
|
|
538 |
static inline char* _getCurrentEntry(const aos_shell_t* shell, const runtimedata_t* rdata)
|
|
539 |
{
|
|
540 |
aosDbgCheck(shell != NULL);
|
|
541 |
aosDbgCheck(rdata != NULL);
|
|
542 |
|
|
543 |
return _getAbsoluteEntry(shell, rdata->buffer.current);
|
|
544 |
}
|
|
545 |
|
|
546 |
/**
|
|
547 |
* @brief Retreive a pointer to the currently selected entry.
|
|
548 |
*
|
|
549 |
* @param[in] shell Pointer to a shell object.
|
|
550 |
* @param[in] rdata Pointer to a runtime data object.
|
|
551 |
*
|
|
552 |
* @return Pointer to the currently selected entry or NULL if no entry is selected (cleared preview).
|
|
553 |
*/
|
|
554 |
static inline char* _getSelectedEntry(const aos_shell_t* shell, const runtimedata_t* rdata)
|
|
555 |
{
|
|
556 |
aosDbgCheck(shell != NULL);
|
|
557 |
aosDbgCheck(rdata != NULL);
|
|
558 |
|
|
559 |
if (rdata->buffer.selected > 0) {
|
|
560 |
return _getRelativeEntry(shell, rdata, rdata->buffer.selected - 1);
|
|
561 |
} else {
|
|
562 |
return NULL;
|
|
563 |
}
|
|
564 |
}
|
|
565 |
|
|
566 |
/**
|
|
567 |
* @brief Retreive the currently visualized entry.
|
|
568 |
*
|
|
569 |
* @param[in] shell Pointer to a shell object.
|
|
570 |
* @param[in] rdata Pointer to a runtime data object.
|
|
571 |
*
|
|
572 |
* @return Pointer to the currently visualized entry or NULL if the input has been cleared (cleared preview).
|
|
573 |
*/
|
|
574 |
static inline char* _getVisualisedEntry(const aos_shell_t* shell, const runtimedata_t* rdata)
|
|
575 |
{
|
|
576 |
aosDbgCheck(shell != NULL);
|
|
577 |
aosDbgCheck(rdata != NULL);
|
|
578 |
|
|
579 |
if (rdata->buffer.selected == 0) {
|
|
580 |
// cleared preview, nothing visualized
|
|
581 |
return NULL;
|
|
582 |
} else {
|
|
583 |
if (rdata->buffer.selected == 1 || rdata->buffer.selected == rdata->buffer.edited) {
|
|
584 |
// the current or a modified entry is selected
|
|
585 |
return _getCurrentEntry(shell, rdata);
|
|
586 |
} else {
|
|
587 |
// a historic, unmodified entry is selected
|
|
588 |
return _getRelativeEntry(shell, rdata, rdata->buffer.selected - 1);
|
|
589 |
}
|
|
590 |
}
|
|
591 |
}
|
|
592 |
|
|
593 |
/**
|
305 |
594 |
* @brief Print the shell prompt
|
306 |
595 |
* @details Depending on the configuration flags, the system uptime is printed before the prompt string.
|
307 |
596 |
*
|
... | ... | |
371 |
660 |
static special_key_t _interpreteEscapeSequence(const char seq[])
|
372 |
661 |
{
|
373 |
662 |
// local variables
|
374 |
|
char str[AOS_SHELL_ESCSEQUENCE_LENGTH];
|
375 |
663 |
unsigned long strl = 0;
|
376 |
664 |
const unsigned long seql = strlen(seq);
|
377 |
665 |
bool ambiguous = false;
|
378 |
666 |
|
379 |
667 |
// TAB
|
380 |
|
/* not supported yet; use "\x09" instead */
|
|
668 |
/* not supported yet; use '\x09' instead */
|
381 |
669 |
|
382 |
670 |
// BACKSPACE
|
383 |
|
/* not supported yet; use "\x08" instead */
|
|
671 |
/* not supported yet; use '\x08' instead */
|
384 |
672 |
|
385 |
673 |
// ESCAPE
|
386 |
|
strncpy(str, "\x1B", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
387 |
|
strl = strlen(str);
|
388 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
389 |
|
return KEY_ESCAPE;
|
390 |
|
} else if(seql < strl && strncmp(seq, str, seql) == 0) {
|
391 |
|
ambiguous = true;
|
392 |
|
}
|
|
674 |
/* not supported yes; use '\x1B' instead */
|
|
675 |
|
|
676 |
// CTRL + C
|
|
677 |
/* not defined yet; use '\x03' instead */
|
393 |
678 |
|
394 |
679 |
// INSERT
|
395 |
|
strncpy(str, "\x1B\x5B\x32\x7E", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
396 |
|
strl = strlen(str);
|
397 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
398 |
|
return KEY_INSERT;
|
399 |
|
} else if(seql < strl && strncmp(seq, str, seql) == 0) {
|
400 |
|
ambiguous = true;
|
|
680 |
if (strncmp(seq, KEYSTRING_INSERT, seql) == 0) {
|
|
681 |
strl = strlen(KEYSTRING_INSERT);
|
|
682 |
if (seql == strl) {
|
|
683 |
return KEY_INSERT;
|
|
684 |
} else if (seql < strl) {
|
|
685 |
ambiguous = true;
|
|
686 |
}
|
401 |
687 |
}
|
402 |
688 |
|
403 |
689 |
// DELETE
|
404 |
|
strncpy(str, "\x1B\x5B\x33\x7E", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
405 |
|
strl = strlen(str);
|
406 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
407 |
|
return KEY_DELETE;
|
408 |
|
} else if(seql < strl && strncmp(seq, str, seql) == 0) {
|
409 |
|
ambiguous = true;
|
|
690 |
if (strncmp(seq, KEYSTRING_DELETE, seql) == 0) {
|
|
691 |
strl = strlen(KEYSTRING_DELETE);
|
|
692 |
if (seql == strl) {
|
|
693 |
return KEY_DELETE;
|
|
694 |
} else if (seql < strl) {
|
|
695 |
ambiguous = true;
|
|
696 |
}
|
410 |
697 |
}
|
411 |
698 |
|
412 |
699 |
// HOME
|
413 |
|
strncpy(str, "\x1B\x5B\x48", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
414 |
|
strl = strlen(str);
|
415 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
416 |
|
return KEY_HOME;
|
417 |
|
} else if(seql < strl && strncmp(seq, str, seql) == 0) {
|
418 |
|
ambiguous = true;
|
|
700 |
if (strncmp(seq, KEYSTRING_HOME, seql) == 0) {
|
|
701 |
strl = strlen(KEYSTRING_HOME);
|
|
702 |
if (seql == strl) {
|
|
703 |
return KEY_HOME;
|
|
704 |
} else if (seql < strl) {
|
|
705 |
ambiguous = true;
|
|
706 |
}
|
419 |
707 |
}
|
420 |
708 |
|
421 |
709 |
// END
|
422 |
|
strncpy(str, "\x1B\x5B\x46", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
423 |
|
strl = strlen(str);
|
424 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
425 |
|
return KEY_END;
|
426 |
|
} else if(seql < strl && strncmp(seq, str, seql) == 0) {
|
427 |
|
ambiguous = true;
|
|
710 |
if (strncmp(seq, KEYSTRING_END, seql) == 0) {
|
|
711 |
strl = strlen(KEYSTRING_END);
|
|
712 |
if (seql == strl) {
|
|
713 |
return KEY_END;
|
|
714 |
} else if (seql < strl) {
|
|
715 |
ambiguous = true;
|
|
716 |
}
|
428 |
717 |
}
|
429 |
718 |
|
430 |
719 |
// PAGE UP
|
431 |
|
strncpy(str, "\x1B\x5B\x35\x7E", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
432 |
|
strl = strlen(str);
|
433 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
434 |
|
return KEY_PAGE_UP;
|
435 |
|
} else if(seql < strl && strncmp(seq, str, seql) == 0) {
|
436 |
|
ambiguous = true;
|
|
720 |
if (strncmp(seq, KEYSTRING_PAGEUP, seql) == 0) {
|
|
721 |
strl = strlen(KEYSTRING_PAGEUP);
|
|
722 |
if (seql == strl) {
|
|
723 |
return KEY_PAGEUP;
|
|
724 |
} else if (seql < strl) {
|
|
725 |
ambiguous = true;
|
|
726 |
}
|
437 |
727 |
}
|
438 |
728 |
|
439 |
729 |
// PAGE DOWN
|
440 |
|
strncpy(str, "\x1B\x5B\x36\x7E", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
441 |
|
strl = strlen(str);
|
442 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
443 |
|
return KEY_PAGE_DOWN;
|
444 |
|
} else if(seql < strl && strncmp(seq, str, seql) == 0) {
|
445 |
|
ambiguous = true;
|
|
730 |
if (strncmp(seq, KEYSTRING_PAGEDOWN, seql) == 0) {
|
|
731 |
strl = strlen(KEYSTRING_PAGEDOWN);
|
|
732 |
if (seql == strl) {
|
|
733 |
return KEY_PAGEDOWN;
|
|
734 |
} else if (seql < strl) {
|
|
735 |
ambiguous = true;
|
|
736 |
}
|
446 |
737 |
}
|
447 |
738 |
|
448 |
739 |
// ARROW UP
|
449 |
|
strncpy(str, "\x1B\x5B\x41", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
450 |
|
strl = strlen(str);
|
451 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
452 |
|
return KEY_ARROW_UP;
|
453 |
|
} else if(seql < strl && strncmp(seq, str, seql) == 0) {
|
454 |
|
ambiguous = true;
|
|
740 |
if (strncmp(seq, KEYSTRING_ARROWUP, seql) == 0) {
|
|
741 |
strl = strlen(KEYSTRING_ARROWUP);
|
|
742 |
if (seql == strl) {
|
|
743 |
return KEY_ARROWUP;
|
|
744 |
} else if (seql < strl) {
|
|
745 |
ambiguous = true;
|
|
746 |
}
|
455 |
747 |
}
|
456 |
748 |
|
457 |
749 |
// ARROW DOWN
|
458 |
|
strncpy(str, "\x1B\x5B\x42", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
459 |
|
strl = strlen(str);
|
460 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
461 |
|
return KEY_ARROW_DOWN;
|
462 |
|
} else if (seql < strl && strncmp(seq, str, seql) == 0) {
|
463 |
|
ambiguous = true;
|
|
750 |
if (strncmp(seq, KEYSTRING_ARROWDOWN, seql) == 0) {
|
|
751 |
strl = strlen(KEYSTRING_ARROWDOWN);
|
|
752 |
if (seql == strl) {
|
|
753 |
return KEY_ARROWDOWN;
|
|
754 |
} else if (seql < strl) {
|
|
755 |
ambiguous = true;
|
|
756 |
}
|
464 |
757 |
}
|
465 |
758 |
|
466 |
759 |
// ARROW LEFT
|
467 |
|
strncpy(str, "\x1B\x5B\x44", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
468 |
|
strl = strlen(str);
|
469 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
470 |
|
return KEY_ARROW_LEFT;
|
471 |
|
} else if (seql < strl && strncmp(seq, str, seql) == 0) {
|
472 |
|
ambiguous = true;
|
|
760 |
if (strncmp(seq, KEYSTRING_ARROWLEFT, seql) == 0) {
|
|
761 |
strl = strlen(KEYSTRING_ARROWLEFT);
|
|
762 |
if (seql == strl) {
|
|
763 |
return KEY_ARROWLEFT;
|
|
764 |
} else if (seql < strl) {
|
|
765 |
ambiguous = true;
|
|
766 |
}
|
473 |
767 |
}
|
474 |
768 |
|
475 |
769 |
// ARROW RIGHT
|
476 |
|
strncpy(str, "\x1B\x5B\x43", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
477 |
|
strl = strlen(str);
|
478 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
479 |
|
return KEY_ARROW_RIGHT;
|
480 |
|
} else if (seql < strl && strncmp(seq, str, seql) == 0) {
|
481 |
|
ambiguous = true;
|
|
770 |
if (strncmp(seq, KEYSTRING_ARROWRIGHT, seql) == 0) {
|
|
771 |
strl = strlen(KEYSTRING_ARROWRIGHT);
|
|
772 |
if (seql == strl) {
|
|
773 |
return KEY_ARROWRIGHT;
|
|
774 |
} else if (seql < strl) {
|
|
775 |
ambiguous = true;
|
|
776 |
}
|
482 |
777 |
}
|
483 |
778 |
|
484 |
779 |
// CTRL + ARROW UP
|
485 |
|
strncpy(str, "\x1B\x5B\x31\x3B\x35\x41", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
486 |
|
strl = strlen(str);
|
487 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
488 |
|
return KEY_CTRL_ARROW_UP;
|
489 |
|
} else if(seql < strl && strncmp(seq, str, seql) == 0) {
|
490 |
|
ambiguous = true;
|
|
780 |
if (strncmp(seq, KEYSTRING_CTRL_ARROWUP, seql) == 0) {
|
|
781 |
strl = strlen(KEYSTRING_CTRL_ARROWUP);
|
|
782 |
if (seql == strl) {
|
|
783 |
return KEY_CTRL_ARROWUP;
|
|
784 |
} else if (seql < strl) {
|
|
785 |
ambiguous = true;
|
|
786 |
}
|
491 |
787 |
}
|
492 |
788 |
|
493 |
789 |
// CTRL + ARROW DOWN
|
494 |
|
strncpy(str, "\x1B\x5B\x31\x3B\x35\x42", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
495 |
|
strl = strlen(str);
|
496 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
497 |
|
return KEY_CTRL_ARROW_DOWN;
|
498 |
|
} else if(seql < strl && strncmp(seq, str, seql) == 0) {
|
499 |
|
ambiguous = true;
|
|
790 |
if (strncmp(seq, KEYSTRING_CTRL_ARROWDOWN, seql) == 0) {
|
|
791 |
strl = strlen(KEYSTRING_CTRL_ARROWDOWN);
|
|
792 |
if (seql == strl) {
|
|
793 |
return KEY_CTRL_ARROWDOWN;
|
|
794 |
} else if (seql < strl) {
|
|
795 |
ambiguous = true;
|
|
796 |
}
|
500 |
797 |
}
|
501 |
798 |
|
502 |
799 |
// CTRL + ARROW LEFT
|
503 |
|
strncpy(str, "\x1B\x5B\x31\x3B\x35\x44", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
504 |
|
strl = strlen(str);
|
505 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
506 |
|
return KEY_CTRL_ARROW_LEFT;
|
507 |
|
} else if(seql < strl && strncmp(seq, str, seql) == 0) {
|
508 |
|
ambiguous = true;
|
|
800 |
if (strncmp(seq, KEYSTRING_CTRL_ARROWLEFT, seql) == 0) {
|
|
801 |
strl = strlen(KEYSTRING_CTRL_ARROWLEFT);
|
|
802 |
if (seql == strl) {
|
|
803 |
return KEY_CTRL_ARROWLEFT;
|
|
804 |
} else if (seql < strl) {
|
|
805 |
ambiguous = true;
|
|
806 |
}
|
509 |
807 |
}
|
510 |
808 |
|
511 |
809 |
// CTRL + ARROW RIGHT
|
512 |
|
strncpy(str, "\x1B\x5B\x31\x3B\x35\x43", AOS_SHELL_ESCSEQUENCE_LENGTH);
|
513 |
|
strl = strlen(str);
|
514 |
|
if (seql == strl && strncmp(seq, str, seql) == 0) {
|
515 |
|
return KEY_CTRL_ARROW_RIGHT;
|
516 |
|
} else if(seql < strl && strncmp(seq, str, seql) == 0) {
|
517 |
|
ambiguous = true;
|
|
810 |
if (strncmp(seq, KEYSTRING_CTRL_ARROWRIGHT, seql) == 0) {
|
|
811 |
strl = strlen(KEYSTRING_CTRL_ARROWRIGHT);
|
|
812 |
if (seql == strl) {
|
|
813 |
return KEY_CTRL_ARROWRIGHT;
|
|
814 |
} else if (seql < strl) {
|
|
815 |
ambiguous = true;
|
|
816 |
}
|
518 |
817 |
}
|
519 |
818 |
|
520 |
819 |
return ambiguous ? KEY_AMBIGUOUS : KEY_UNKNOWN;
|
521 |
820 |
}
|
522 |
821 |
|
523 |
822 |
/**
|
524 |
|
* @brief Move the cursor in the terminal
|
|
823 |
* @brief Move the cursor in the terminal.
|
525 |
824 |
*
|
526 |
825 |
* @param[in] shell Pointer to the shell object.
|
|
826 |
* @param[in] line Pointer to the current content of the line.
|
527 |
827 |
* @param[in] from Starting position of the cursor.
|
528 |
828 |
* @param[in] to Target position to move the cursor to.
|
529 |
829 |
*
|
530 |
830 |
* @return The number of positions moved.
|
531 |
831 |
*/
|
532 |
|
static int _moveCursor(aos_shell_t* shell, const size_t from, const size_t to)
|
|
832 |
static int _moveCursor(aos_shell_t* shell, const char* line, size_t from, size_t to)
|
533 |
833 |
{
|
534 |
834 |
aosDbgCheck(shell != NULL);
|
|
835 |
aosDbgCheck(line != NULL || from >= to);
|
|
836 |
aosDbgCheck(from <= shell->input.linewidth);
|
|
837 |
aosDbgCheck(to <= shell->input.linewidth);
|
535 |
838 |
|
536 |
839 |
// local variables
|
537 |
840 |
size_t pos = from;
|
... | ... | |
544 |
847 |
|
545 |
848 |
// move cursor right by printing line content
|
546 |
849 |
while (pos < to) {
|
547 |
|
streamPut(&shell->stream, (uint8_t)shell->input.line[pos]);
|
|
850 |
streamPut(&shell->stream, (uint8_t)line[pos]);
|
548 |
851 |
++pos;
|
549 |
852 |
}
|
550 |
853 |
|
... | ... | |
552 |
855 |
}
|
553 |
856 |
|
554 |
857 |
/**
|
555 |
|
* @brief Print content of the shell line
|
|
858 |
* @brief Print content of a given string to the shell output stream.
|
556 |
859 |
*
|
557 |
860 |
* @param[in] shell Pointer to the shell object.
|
|
861 |
* @param[in] line Pointer to the line to be printed.
|
558 |
862 |
* @param[in] from First position to start printing from.
|
559 |
863 |
* @param[in] to Position after the last character to print.
|
560 |
864 |
*
|
561 |
865 |
* @return Number of characters printed.
|
562 |
866 |
*/
|
563 |
|
static inline size_t _printLine(aos_shell_t* shell, const size_t from, const size_t to)
|
|
867 |
static size_t _printString(aos_shell_t* shell, const char* line, size_t from, size_t to)
|
564 |
868 |
{
|
565 |
869 |
aosDbgCheck(shell != NULL);
|
|
870 |
aosDbgCheck(line != NULL || from >= to);
|
|
871 |
aosDbgCheck(from < shell->input.linewidth);
|
|
872 |
aosDbgCheck(to <= shell->input.linewidth);
|
566 |
873 |
|
567 |
874 |
// local variables
|
568 |
875 |
size_t cnt;
|
569 |
876 |
|
570 |
877 |
for (cnt = 0; from + cnt < to; ++cnt) {
|
571 |
|
streamPut(&shell->stream, (uint8_t)shell->input.line[from + cnt]);
|
|
878 |
streamPut(&shell->stream, (uint8_t)line[from + cnt]);
|
572 |
879 |
}
|
573 |
880 |
|
574 |
881 |
return cnt;
|
575 |
882 |
}
|
576 |
883 |
|
577 |
|
static int _readChar(aos_shell_t* shell, const char c) {
|
|
884 |
/**
|
|
885 |
* @brief Print a single character to the input buffer and to the output stream.
|
|
886 |
*
|
|
887 |
* @param[in] shell Pointer to the shell object.
|
|
888 |
* @param[in] rdata Pointer to the runtim data object.
|
|
889 |
* @param[in] c Character to print.
|
|
890 |
*
|
|
891 |
* @return Number of successfully handled characters.
|
|
892 |
* The return value can be interpreted as boolean (1 = sucess; 0 = error).
|
|
893 |
*/
|
|
894 |
static int _printChar(aos_shell_t* shell, runtimedata_t* rdata, char c)
|
|
895 |
{
|
578 |
896 |
aosDbgCheck(shell != NULL);
|
|
897 |
aosDbgCheck(rdata != NULL);
|
579 |
898 |
|
580 |
899 |
// check whether input line is already full
|
581 |
|
if (shell->inputdata.lineend + 1 >= shell->input.size) {
|
|
900 |
if (rdata->input.length + 1 >= shell->input.linewidth) {
|
582 |
901 |
return 0;
|
583 |
|
} else {
|
584 |
|
// clear old line content on first input
|
585 |
|
if (shell->inputdata.noinput) {
|
586 |
|
memset(shell->input.line, '\0', shell->input.size);
|
587 |
|
shell->inputdata.noinput = false;
|
588 |
|
}
|
589 |
|
// overwrite content
|
590 |
|
if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
|
591 |
|
shell->input.line[shell->inputdata.cursorpos] = c;
|
592 |
|
++shell->inputdata.cursorpos;
|
593 |
|
shell->inputdata.lineend = (shell->inputdata.cursorpos > shell->inputdata.lineend) ? shell->inputdata.cursorpos : shell->inputdata.lineend;
|
594 |
|
streamPut(&shell->stream, (uint8_t)c);
|
595 |
|
}
|
596 |
|
// insert character
|
597 |
|
else {
|
598 |
|
memmove(&(shell->input.line[shell->inputdata.cursorpos+1]), &(shell->input.line[shell->inputdata.cursorpos]), shell->inputdata.lineend - shell->inputdata.cursorpos);
|
599 |
|
shell->input.line[shell->inputdata.cursorpos] = c;
|
600 |
|
++shell->inputdata.lineend;
|
601 |
|
_printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
|
602 |
|
++shell->inputdata.cursorpos;
|
603 |
|
_moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
|
604 |
|
}
|
|
902 |
}
|
|
903 |
|
|
904 |
// retreive entry in the input buffer
|
|
905 |
char* line = _getCurrentEntry(shell, rdata);
|
|
906 |
|
|
907 |
// overwrite content
|
|
908 |
if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
|
|
909 |
line[rdata->input.cursorpos] = c;
|
|
910 |
++rdata->input.cursorpos;
|
|
911 |
rdata->input.length = (rdata->input.cursorpos > rdata->input.length) ? rdata->input.cursorpos : rdata->input.length;
|
|
912 |
streamPut(&shell->stream, (uint8_t)c);
|
|
913 |
return 1;
|
|
914 |
}
|
|
915 |
// insert character
|
|
916 |
else {
|
|
917 |
memmove(&line[rdata->input.cursorpos + 1], &line[rdata->input.cursorpos], rdata->input.length - rdata->input.cursorpos);
|
|
918 |
line[rdata->input.cursorpos] = c;
|
|
919 |
++rdata->input.length;
|
|
920 |
_printString(shell, line, rdata->input.cursorpos, rdata->input.length);
|
|
921 |
++rdata->input.cursorpos;
|
|
922 |
_moveCursor(shell, line, rdata->input.length, rdata->input.cursorpos);
|
605 |
923 |
return 1;
|
606 |
924 |
}
|
607 |
925 |
}
|
608 |
926 |
|
609 |
927 |
/**
|
|
928 |
* @brief Overwrite the current output with a given line.
|
|
929 |
* @details If the current output is longer than the string, the additional characters are cleared.
|
|
930 |
*
|
|
931 |
* @param[in] shell Pointer to a shell object.
|
|
932 |
* @param[in] rdata Pointer to a runtime data object.
|
|
933 |
* @param[in] line The line to be printed.
|
|
934 |
*/
|
|
935 |
static void _overwriteOutput(aos_shell_t* shell, runtimedata_t* rdata, const char* line)
|
|
936 |
{
|
|
937 |
aosDbgCheck(shell != NULL);
|
|
938 |
aosDbgCheck(rdata != NULL);
|
|
939 |
aosDbgCheck(line != NULL);
|
|
940 |
|
|
941 |
// local variables
|
|
942 |
const size_t oldlength = rdata->input.length;
|
|
943 |
|
|
944 |
// print line (overwrite current output)
|
|
945 |
_moveCursor(shell, line, rdata->input.cursorpos, 0);
|
|
946 |
rdata->input.length = strlen(line);
|
|
947 |
_printString(shell, line, 0, rdata->input.length);
|
|
948 |
|
|
949 |
// clear any remaining symbols
|
|
950 |
if (oldlength > rdata->input.length) {
|
|
951 |
for (rdata->input.cursorpos = rdata->input.length; rdata->input.cursorpos < oldlength; ++rdata->input.cursorpos) {
|
|
952 |
streamPut(&shell->stream, ' ');
|
|
953 |
}
|
|
954 |
_moveCursor(shell, line, oldlength, rdata->input.length);
|
|
955 |
}
|
|
956 |
|
|
957 |
rdata->input.cursorpos = rdata->input.length;
|
|
958 |
|
|
959 |
return;
|
|
960 |
}
|
|
961 |
|
|
962 |
/**
|
610 |
963 |
* @brief Compare two characters.
|
611 |
964 |
*
|
612 |
965 |
* @param[in] lhs First character to compare.
|
... | ... | |
642 |
995 |
*
|
643 |
996 |
* @return The customly encoded character.
|
644 |
997 |
*/
|
645 |
|
static inline char _mapAscii2Custom(const char c)
|
|
998 |
static inline char _mapAscii2Custom(char c)
|
646 |
999 |
{
|
647 |
1000 |
if (c >= 'A' && c <= 'Z') {
|
648 |
1001 |
return ((c - 'A') * 2) + 'A' + 1;
|
... | ... | |
712 |
1065 |
}
|
713 |
1066 |
|
714 |
1067 |
/**
|
|
1068 |
* @brief Alters all intermediate NUL bytes in a string to spaces.
|
|
1069 |
*
|
|
1070 |
* @param[in] string The string to be handled.
|
|
1071 |
* @param[in] length Length of the string.
|
|
1072 |
*
|
|
1073 |
* @return Detected Length of the actual content of the string.
|
|
1074 |
*/
|
|
1075 |
static size_t _restoreWhitespace(char* string, size_t length)
|
|
1076 |
{
|
|
1077 |
aosDbgCheck(string != NULL || length == 0);
|
|
1078 |
|
|
1079 |
// local variables
|
|
1080 |
size_t c = length;
|
|
1081 |
|
|
1082 |
// seach for first non-NUL byte from the back
|
|
1083 |
while (c > 0) {
|
|
1084 |
--c;
|
|
1085 |
if (string[c] != '\0') {
|
|
1086 |
// store the detected length of the content
|
|
1087 |
length = ++c;
|
|
1088 |
break;
|
|
1089 |
}
|
|
1090 |
}
|
|
1091 |
|
|
1092 |
// iterate further and replace all encountered NUL bytes by spaces
|
|
1093 |
while (c > 0) {
|
|
1094 |
--c;
|
|
1095 |
if (string[c] == '\0') {
|
|
1096 |
string[c] = ' ';
|
|
1097 |
}
|
|
1098 |
}
|
|
1099 |
|
|
1100 |
return length;
|
|
1101 |
}
|
|
1102 |
|
|
1103 |
/**
|
|
1104 |
* @brief Performs required actions before an imminent modiifcation (character input, deletion or autofill).
|
|
1105 |
* @details This functions checks the current status and clears or copies entries in the input buffer as required.
|
|
1106 |
* Status information (runtime data) is altered accordingly as well.
|
|
1107 |
*
|
|
1108 |
* @param[in] shell Pointer to a shell object.
|
|
1109 |
* @param[in] rdata Pointer to a runtime data object.
|
|
1110 |
*
|
|
1111 |
* @return Pointer to the current entry in the input buffer.
|
|
1112 |
*/
|
|
1113 |
static char* _prepare4Modification(aos_shell_t* shell, runtimedata_t* rdata)
|
|
1114 |
{
|
|
1115 |
aosDbgCheck(shell != NULL);
|
|
1116 |
aosDbgCheck(rdata != NULL);
|
|
1117 |
|
|
1118 |
char* line = _getCurrentEntry(shell, rdata);
|
|
1119 |
|
|
1120 |
// cleared preview
|
|
1121 |
if (rdata->buffer.selected == 0) {
|
|
1122 |
// reset the current entry if required
|
|
1123 |
if (rdata->buffer.edited != 0) {
|
|
1124 |
memset(line, '\0', shell->input.linewidth * sizeof(char));
|
|
1125 |
}
|
|
1126 |
// set the current entry as the selected one and mark it as edited
|
|
1127 |
rdata->buffer.selected = 1;
|
|
1128 |
rdata->buffer.edited = 1;
|
|
1129 |
}
|
|
1130 |
// current entry
|
|
1131 |
else if (rdata->buffer.selected == 1) {
|
|
1132 |
// mark current entry as edited
|
|
1133 |
rdata->buffer.edited = 1;
|
|
1134 |
}
|
|
1135 |
// preview of historic entry
|
|
1136 |
else if (rdata->buffer.selected > 1) {
|
|
1137 |
// copy the selected entry before modification if required
|
|
1138 |
if (rdata->buffer.selected!= rdata->buffer.edited) {
|
|
1139 |
memcpy(line, _getSelectedEntry(shell, rdata), shell->input.linewidth * sizeof(char));
|
|
1140 |
}
|
|
1141 |
// mark the selected entry as edited
|
|
1142 |
rdata->buffer.edited = rdata->buffer.selected;
|
|
1143 |
}
|
|
1144 |
|
|
1145 |
return line;
|
|
1146 |
}
|
|
1147 |
|
|
1148 |
/**
|
715 |
1149 |
* @brief Read input from a channel as long as there is data available.
|
716 |
1150 |
*
|
717 |
|
* @param[in] shell Pointer to the shell object.
|
718 |
|
* @param[in] channel The channel to read from.
|
719 |
|
* @param[out] exec Optional pointer to a flag, which indicates, whether a command shall be executed.
|
|
1151 |
* @param[in] shell Pointer to the shell object.
|
|
1152 |
* @param[in,out] rdata Pointer to a runtime data object.
|
|
1153 |
* @param[in] channel The channel to read from.
|
720 |
1154 |
*
|
721 |
|
* @return Number of read characters.
|
|
1155 |
* @return Number of characters read.
|
722 |
1156 |
*/
|
723 |
|
static int _readChannel(aos_shell_t* shell, AosShellChannel* channel, bool* execute)
|
|
1157 |
static size_t _readChannel(aos_shell_t* shell, runtimedata_t* rdata, AosShellChannel* channel)
|
724 |
1158 |
{
|
725 |
1159 |
aosDbgCheck(shell != NULL);
|
|
1160 |
aosDbgCheck(rdata != NULL);
|
726 |
1161 |
aosDbgCheck(channel != NULL);
|
727 |
1162 |
|
728 |
1163 |
// local variables
|
729 |
|
aos_shellaction_t action = AOS_SHELL_ACTION_NONE;
|
|
1164 |
size_t bytes = 0;
|
730 |
1165 |
char c;
|
731 |
1166 |
special_key_t key;
|
732 |
|
int nchars = 0;
|
733 |
|
bool exec = false;
|
|
1167 |
action_t action;
|
734 |
1168 |
|
735 |
1169 |
// read character by character from the channel
|
736 |
1170 |
while (chnReadTimeout(channel, (uint8_t*)&c, 1, TIME_IMMEDIATE)) {
|
737 |
|
key = KEY_UNKNOWN;
|
|
1171 |
// increment byte counter
|
|
1172 |
++bytes;
|
738 |
1173 |
|
739 |
|
// drop any input after an execution request was detected
|
740 |
|
if (exec) {
|
|
1174 |
// drop any further input after an execution request was detected
|
|
1175 |
if (rdata->lastaction == ACTION_EXECUTE && bytes > 1) {
|
741 |
1176 |
continue;
|
742 |
1177 |
}
|
743 |
1178 |
|
744 |
|
// incremet character counter
|
745 |
|
++nchars;
|
746 |
|
|
747 |
|
// parse escape sequence
|
748 |
|
if (strlen(shell->inputdata.escseq) > 0) {
|
749 |
|
shell->inputdata.escseq[strlen(shell->inputdata.escseq)] = c;
|
750 |
|
key = _interpreteEscapeSequence(shell->inputdata.escseq);
|
751 |
|
switch (key) {
|
752 |
|
case KEY_AMBIGUOUS:
|
753 |
|
// read next byte to resolve ambiguity
|
754 |
|
continue;
|
755 |
|
case KEY_UNKNOWN:
|
756 |
|
// do nothing here, but handle the unknown sequence below
|
757 |
|
break;
|
758 |
|
default:
|
759 |
|
// reset the sequence variable and buffer
|
760 |
|
memset(shell->inputdata.escseq, '\0', sizeof(shell->inputdata.escseq)*sizeof(shell->inputdata.escseq[0]));
|
761 |
|
break;
|
|
1179 |
// try to interprete escape sequence
|
|
1180 |
{
|
|
1181 |
// set default
|
|
1182 |
key = KEY_UNKNOWN;
|
|
1183 |
// only interprete, if there is an escape sequence at all
|
|
1184 |
const size_t escl = strlen(rdata->input.escseq);
|
|
1185 |
if (escl > 0) {
|
|
1186 |
// append and 'consume' character
|
|
1187 |
rdata->input.escseq[escl] = c;
|
|
1188 |
c = '\0';
|
|
1189 |
// try to interprete sequence
|
|
1190 |
key = _interpreteEscapeSequence(rdata->input.escseq);
|
|
1191 |
switch (key) {
|
|
1192 |
// ambiguous key due to incomplete sequence
|
|
1193 |
case KEY_AMBIGUOUS:
|
|
1194 |
// read next byte to resolve ambiguity
|
|
1195 |
continue;
|
|
1196 |
// an unknown sequence has been encountered
|
|
1197 |
case KEY_UNKNOWN:
|
|
1198 |
// increment number of inputs but handle this unknown sequence below
|
|
1199 |
break;
|
|
1200 |
// a key was identified successfully
|
|
1201 |
default:
|
|
1202 |
// reset the sequence buffer
|
|
1203 |
memset(rdata->input.escseq, '\0', AOS_SHELL_ESCSEQUENCE_LENGTH * sizeof(char));
|
|
1204 |
break;
|
|
1205 |
}
|
762 |
1206 |
}
|
763 |
1207 |
}
|
764 |
1208 |
|
765 |
|
/* interprete keys or character */
|
766 |
|
{
|
767 |
|
// default
|
768 |
|
action = AOS_SHELL_ACTION_NONE;
|
|
1209 |
/*
|
|
1210 |
* Derive action to be executed from keypress.
|
|
1211 |
* This step handles all sanity checks, so any required prerequisites for the selected action are fulfilled.
|
|
1212 |
*/
|
|
1213 |
// set default
|
|
1214 |
action = ACTION_NONE;
|
|
1215 |
// if there is no escape sequence pending
|
|
1216 |
if (rdata->input.escseq[0] == '\0') {
|
769 |
1217 |
|
770 |
1218 |
// printable character
|
771 |
|
if (key == KEY_UNKNOWN && strlen(shell->inputdata.escseq) == 0 && c >= '\x20' && c <= '\x7E') {
|
772 |
|
action = AOS_SHELL_ACTION_READCHAR;
|
|
1219 |
if (c >= '\x20' && c <= '\x7E') {
|
|
1220 |
action = ACTION_READCHAR;
|
773 |
1221 |
}
|
774 |
1222 |
|
775 |
1223 |
// tab key or character
|
776 |
|
else if (key == KEY_TAB || c == '\x09') {
|
|
1224 |
else if (c == '\x09' || key == KEY_TAB) {
|
777 |
1225 |
/*
|
778 |
1226 |
* pressing tab once applies auto fill
|
779 |
|
* pressing tab a second time prints suggestions
|
|
1227 |
* pressing tab a second time (or more) prints suggestions
|
780 |
1228 |
*/
|
781 |
|
if (shell->inputdata.lastaction == AOS_SHELL_ACTION_AUTOFILL || shell->inputdata.lastaction == AOS_SHELL_ACTION_SUGGEST) {
|
782 |
|
action = AOS_SHELL_ACTION_SUGGEST;
|
|
1229 |
if (rdata->lastaction == ACTION_AUTOCOMPLETE || rdata->lastaction == ACTION_SUGGEST) {
|
|
1230 |
action = ACTION_SUGGEST;
|
783 |
1231 |
} else {
|
784 |
|
action = AOS_SHELL_ACTION_AUTOFILL;
|
|
1232 |
action = ACTION_AUTOCOMPLETE;
|
785 |
1233 |
}
|
786 |
1234 |
}
|
787 |
1235 |
|
788 |
|
// INS key
|
789 |
|
else if (key == KEY_INSERT) {
|
790 |
|
action = AOS_SHELL_ACTION_INSERTTOGGLE;
|
791 |
|
}
|
792 |
|
|
793 |
|
// DEL key or character
|
794 |
|
else if (key == KEY_DELETE || c == '\x7F') {
|
795 |
|
// ignore if cursor is at very right
|
796 |
|
if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
|
797 |
|
action = AOS_SHELL_ACTION_DELETEFORWARD;
|
798 |
|
}
|
|
1236 |
// carriage return ('\r') or line feed ('\n') character
|
|
1237 |
else if (c == '\x0D' || c == '\x0A') {
|
|
1238 |
action = ACTION_EXECUTE;
|
799 |
1239 |
}
|
800 |
1240 |
|
801 |
1241 |
// backspace key or character
|
802 |
|
else if (key == KEY_BACKSPACE || c == '\x08') {
|
|
1242 |
else if (c == '\x08' || key == KEY_BACKSPACE) {
|
803 |
1243 |
// ignore if cursor is at very left
|
804 |
|
if (shell->inputdata.cursorpos > 0) {
|
805 |
|
action = AOS_SHELL_ACTION_DELETEBACKWARD;
|
|
1244 |
if (rdata->input.cursorpos > 0) {
|
|
1245 |
action = ACTION_DELETEBACKWARD;
|
806 |
1246 |
}
|
807 |
1247 |
}
|
808 |
1248 |
|
809 |
|
// 'page up', 'arrow up', or key or CTRL + 'arrow up' key combination
|
810 |
|
else if (key == KEY_PAGE_UP || key == KEY_ARROW_UP || key == KEY_CTRL_ARROW_UP) {
|
811 |
|
// ignore if there was some input
|
812 |
|
if (shell->inputdata.noinput) {
|
813 |
|
action = AOS_SHELL_ACTION_RECALLLAST;
|
|
1249 |
// DEL key or character
|
|
1250 |
else if (c == '\x7F' || key == KEY_DELETE) {
|
|
1251 |
// ignore if cursor is at very right
|
|
1252 |
if (rdata->input.cursorpos < rdata->input.length) {
|
|
1253 |
action = ACTION_DELETEFORWARD;
|
814 |
1254 |
}
|
815 |
1255 |
}
|
816 |
1256 |
|
817 |
|
// 'page down' key, 'arrow down' key, 'end of test' character or 'end of transmission' character, or CTRL + 'arrow down' key combination
|
818 |
|
else if (key == KEY_PAGE_DOWN || key == KEY_ARROW_DOWN || c == '\x03' || c == '\x03' || key == KEY_CTRL_ARROW_DOWN) {
|
819 |
|
// ignore if line is empty
|
820 |
|
if (shell->inputdata.lineend > 0) {
|
821 |
|
action = AOS_SHELL_ACTION_CLEAR;
|
|
1257 |
// 'arrow up' key
|
|
1258 |
else if (key == KEY_ARROWUP) {
|
|
1259 |
// recall previous input from history only if
|
|
1260 |
// not the oldest entry is already selected and
|
|
1261 |
// the previous entry has been set.
|
|
1262 |
if (rdata->buffer.selected < shell->input.nentries &&
|
|
1263 |
(_getRelativeEntry(shell, rdata, rdata->buffer.selected))[0] != INBUF_INIT_CHAR) {
|
|
1264 |
action = ACTION_RECALLPREVIOUS;
|
822 |
1265 |
}
|
823 |
1266 |
}
|
824 |
1267 |
|
825 |
|
// 'home' key
|
826 |
|
else if (key == KEY_HOME) {
|
827 |
|
// ignore if cursor is very left
|
828 |
|
if (shell->inputdata.cursorpos > 0) {
|
829 |
|
action = AOS_SHELL_ACTION_CURSOR2START;
|
|
1268 |
// 'arrow down' key
|
|
1269 |
else if (key == KEY_ARROWDOWN) {
|
|
1270 |
// clear the line if
|
|
1271 |
// no historic entry is selected or
|
|
1272 |
// the most recent entry is selected, but the current one is occupied by a moodfied version of a historic entry
|
|
1273 |
if ((rdata->buffer.selected == 1) ||
|
|
1274 |
(rdata->buffer.selected == 2 && rdata->buffer.edited > 1)) {
|
|
1275 |
action = ACTION_CLEAR;
|
830 |
1276 |
}
|
831 |
|
}
|
832 |
|
|
833 |
|
// 'end' key
|
834 |
|
else if (key == KEY_END) {
|
835 |
|
// ignore if cursos is very right
|
836 |
|
if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
|
837 |
|
action = AOS_SHELL_ACTION_CURSOR2END;
|
|
1277 |
// if a historic entry is selected, recall the next input from history
|
|
1278 |
else if (rdata->buffer.selected > 1) {
|
|
1279 |
action = ACTION_RECALLNEXT;
|
838 |
1280 |
}
|
839 |
1281 |
}
|
840 |
1282 |
|
841 |
1283 |
// 'arrow left' key
|
842 |
|
else if (key == KEY_ARROW_LEFT) {
|
|
1284 |
else if (key == KEY_ARROWLEFT) {
|
843 |
1285 |
// ignore if cursor is very left
|
844 |
|
if (shell->inputdata.cursorpos > 0) {
|
845 |
|
action = AOS_SHELL_ACTION_CURSORLEFT;
|
|
1286 |
if (rdata->input.cursorpos > 0) {
|
|
1287 |
action = ACTION_CURSORLEFT;
|
846 |
1288 |
}
|
847 |
1289 |
}
|
848 |
1290 |
|
849 |
1291 |
// 'arrow right' key
|
850 |
|
else if (key == KEY_ARROW_RIGHT) {
|
|
1292 |
else if (key == KEY_ARROWRIGHT) {
|
851 |
1293 |
// ignore if cursor is very right
|
852 |
|
if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
|
853 |
|
action = AOS_SHELL_ACTION_CURSORRIGHT;
|
|
1294 |
if (rdata->input.cursorpos < rdata->input.length) {
|
|
1295 |
action = ACTION_CURSORRIGHT;
|
|
1296 |
}
|
|
1297 |
}
|
|
1298 |
|
|
1299 |
// CTRL + 'arrow up' key combination or 'page up' key
|
|
1300 |
else if (key == KEY_CTRL_ARROWUP || key == KEY_PAGEUP) {
|
|
1301 |
// recall oldest input from history only if
|
|
1302 |
// not the oldest entry is already selected and
|
|
1303 |
// there is at least one history entry set
|
|
1304 |
if (rdata->buffer.selected < shell->input.nentries &&
|
|
1305 |
(_getRelativeEntry(shell, rdata, (rdata->buffer.selected > 0) ? 1 : 0))[0] != INBUF_INIT_CHAR) {
|
|
1306 |
action = ACTION_RECALLOLDEST;
|
|
1307 |
}
|
|
1308 |
}
|
|
1309 |
|
|
1310 |
// CTRL + 'arrow down' key combination or 'page down' key
|
|
1311 |
else if (key == KEY_CTRL_ARROWDOWN || key == KEY_PAGEDOWN) {
|
|
1312 |
// clear the line if
|
|
1313 |
// no historic entry is selected or
|
|
1314 |
// the most recent entry is selected, but the current one is occupied by a moodfied version of a historic entry
|
|
1315 |
if ((rdata->buffer.selected == 1) ||
|
|
1316 |
(rdata->buffer.selected > 1 && rdata->buffer.edited > 1)) {
|
|
1317 |
action = ACTION_CLEAR;
|
|
1318 |
}
|
|
1319 |
// if a historic entry is selected, reset to the current input
|
|
1320 |
else if (rdata->buffer.selected > 1) {
|
|
1321 |
action = ACTION_RECALLCURRENT;
|
854 |
1322 |
}
|
855 |
1323 |
}
|
856 |
1324 |
|
857 |
1325 |
// CTRL + 'arrow left' key combination
|
858 |
|
else if (key == KEY_CTRL_ARROW_LEFT) {
|
|
1326 |
else if (key == KEY_CTRL_ARROWLEFT) {
|
859 |
1327 |
// ignore if cursor is very left
|
860 |
|
if (shell->inputdata.cursorpos > 0) {
|
861 |
|
action = AOS_SHELL_ACTION_CURSORWORDLEFT;
|
|
1328 |
if (rdata->input.cursorpos > 0) {
|
|
1329 |
action = ACTION_CURSORWORDLEFT;
|
862 |
1330 |
}
|
863 |
1331 |
}
|
864 |
1332 |
|
865 |
1333 |
// CTRL + 'arrow right' key combination
|
866 |
|
else if (key == KEY_CTRL_ARROW_RIGHT) {
|
|
1334 |
else if (key == KEY_CTRL_ARROWRIGHT) {
|
867 |
1335 |
// ignore if cursor is very right
|
868 |
|
if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
|
869 |
|
action = AOS_SHELL_ACTION_CURSORWORDRIGHT;
|
|
1336 |
if (rdata->input.cursorpos < rdata->input.length) {
|
|
1337 |
action = ACTION_CURSORWORDRIGHT;
|
870 |
1338 |
}
|
871 |
1339 |
}
|
872 |
1340 |
|
873 |
|
// carriage return ('\r') or line feed ('\n') character
|
874 |
|
else if (c == '\x0D' || c == '\x0A') {
|
875 |
|
action = AOS_SHELL_ACTION_EXECUTE;
|
|
1341 |
// 'end' key
|
|
1342 |
else if (key == KEY_END) {
|
|
1343 |
// ignore if cursos is very right
|
|
1344 |
if (rdata->input.cursorpos < rdata->input.length) {
|
|
1345 |
action = ACTION_CURSOR2END;
|
|
1346 |
}
|
876 |
1347 |
}
|
877 |
1348 |
|
878 |
|
// ESC key or [ESCAPE] character
|
879 |
|
else if (key == KEY_ESCAPE || c == '\x1B') {
|
880 |
|
action = AOS_SHELL_ACTION_ESCSTART;
|
|
1349 |
// 'home' key
|
|
1350 |
else if (key == KEY_HOME) {
|
|
1351 |
// ignore if cursor is very left
|
|
1352 |
if (rdata->input.cursorpos > 0) {
|
|
1353 |
action = ACTION_CURSOR2START;
|
|
1354 |
}
|
881 |
1355 |
}
|
882 |
1356 |
|
883 |
|
// unknown escape sequence
|
884 |
|
else if (key == KEY_UNKNOWN && strlen(shell->inputdata.escseq) > 0) {
|
885 |
|
action = AOS_SHELL_ACTION_PRINTUNKNOWNSEQUENCE;
|
|
1357 |
// CTRL + C key combination
|
|
1358 |
else if (c == '\x03' || key == KEY_CTRL_C) {
|
|
1359 |
action = ACTION_RESET;
|
|
1360 |
}
|
|
1361 |
|
|
1362 |
// INS key
|
|
1363 |
else if (key == KEY_INSERT) {
|
|
1364 |
action = ACTION_INSERTTOGGLE;
|
|
1365 |
}
|
|
1366 |
|
|
1367 |
// ESC key or [ESCAPE] character
|
|
1368 |
else if (c == '\x1B' || key == KEY_ESCAPE) {
|
|
1369 |
action = ACTION_ESCSTART;
|
886 |
1370 |
}
|
887 |
1371 |
}
|
|
1372 |
// ongoing escape sequence or interpretation failed
|
|
1373 |
else /* if (rdata->input.escseq[0] != '\0') */ {
|
|
1374 |
// unknown escape sequence (interpretation failed)
|
|
1375 |
if (key == KEY_UNKNOWN) {
|
|
1376 |
action = ACTION_PRINTUNKNOWNSEQUENCE;
|
|
1377 |
}
|
|
1378 |
} /* end of action selection */
|
888 |
1379 |
|
889 |
|
/* handle function */
|
|
1380 |
/*
|
|
1381 |
* execute action
|
|
1382 |
*/
|
890 |
1383 |
switch (action) {
|
891 |
|
case AOS_SHELL_ACTION_READCHAR:
|
|
1384 |
case ACTION_NONE:
|
|
1385 |
{
|
|
1386 |
// do nothing (ignore input) and read next byte
|
|
1387 |
break;
|
|
1388 |
}
|
|
1389 |
|
|
1390 |
case ACTION_READCHAR:
|
892 |
1391 |
{
|
893 |
|
if (_readChar(shell, c) == 0) {
|
|
1392 |
char* line = _prepare4Modification(shell, rdata);
|
|
1393 |
if (_printChar(shell, rdata, c) == 0) {
|
894 |
1394 |
// line is full
|
895 |
|
_moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
|
|
1395 |
_moveCursor(shell, line, rdata->input.cursorpos, rdata->input.length);
|
896 |
1396 |
chprintf((BaseSequentialStream*)&shell->stream, "\n\tmaximum line width reached\n");
|
897 |
1397 |
_printPrompt(shell);
|
898 |
|
_printLine(shell, 0, shell->inputdata.lineend);
|
899 |
|
_moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
|
|
1398 |
_printString(shell, line, 0, rdata->input.length);
|
|
1399 |
_moveCursor(shell, line, rdata->input.length, rdata->input.cursorpos);
|
900 |
1400 |
}
|
901 |
1401 |
break;
|
902 |
1402 |
}
|
903 |
1403 |
|
904 |
|
case AOS_SHELL_ACTION_AUTOFILL:
|
|
1404 |
case ACTION_AUTOCOMPLETE:
|
905 |
1405 |
{
|
906 |
|
const char* fill = shell->input.line;
|
907 |
|
size_t cmatch = shell->inputdata.cursorpos;
|
|
1406 |
// local variables
|
|
1407 |
char* line = _getVisualisedEntry(shell, rdata);
|
|
1408 |
const char* fill = line;
|
|
1409 |
size_t cmatch = rdata->input.cursorpos;
|
908 |
1410 |
charmatch_t matchlevel = CHAR_MATCH_NOT;
|
909 |
1411 |
size_t n;
|
910 |
|
// iterate through command list
|
911 |
|
for (aos_shellcommand_t* cmd = shell->commands; cmd != NULL; cmd = cmd->next) {
|
912 |
|
// compare current match with command
|
913 |
|
n = cmatch;
|
914 |
|
charmatch_t mlvl = CHAR_MATCH_NOT;
|
915 |
|
_strccmp(fill, cmd->name, shell->config & AOS_SHELL_CONFIG_MATCH_CASE, (n == 0) ? NULL : &n, &mlvl);
|
916 |
|
const int cmp = (n < cmatch) ?
|
917 |
|
((int)n - (int)cmatch) :
|
918 |
|
(cmd->name[n] != '\0') ?
|
919 |
|
(int)strlen(cmd->name) - (int)n :
|
920 |
|
0;
|
921 |
|
// if an exact match was found
|
922 |
|
if ((size_t)((int)cmatch + cmp) == shell->inputdata.cursorpos) {
|
923 |
|
cmatch = shell->inputdata.cursorpos;
|
924 |
|
fill = cmd->name;
|
925 |
|
// break the loop only if there are no case mismatches with the input
|
926 |
|
n = shell->inputdata.cursorpos;
|
927 |
|
_strccmp(fill, shell->input.line, false, &n, &mlvl);
|
928 |
|
if (mlvl == CHAR_MATCH_CASE) {
|
929 |
|
break;
|
930 |
|
}
|
931 |
|
}
|
932 |
|
// if a not exact match was found
|
933 |
|
else if ((size_t)((int)cmatch + cmp) > shell->inputdata.cursorpos) {
|
934 |
|
// if this is the first one
|
935 |
|
if (fill == shell->input.line) {
|
936 |
|
cmatch = (size_t)((int)cmatch + cmp);
|
|
1412 |
|
|
1413 |
// only execute autofill if the line is valid
|
|
1414 |
if (line) {
|
|
1415 |
_prepare4Modification(shell, rdata);
|
|
1416 |
|
|
1417 |
// iterate through command list
|
|
1418 |
for (aos_shellcommand_t* cmd = shell->commands; cmd != NULL; cmd = cmd->next) {
|
|
1419 |
// compare current match with command
|
|
1420 |
n = cmatch;
|
|
1421 |
charmatch_t mlvl = CHAR_MATCH_NOT;
|
|
1422 |
_strccmp(fill, cmd->name, shell->config & AOS_SHELL_CONFIG_MATCH_CASE, (n == 0) ? NULL : &n, &mlvl);
|
|
1423 |
const int cmp = (n < cmatch) ?
|
|
1424 |
((int)n - (int)cmatch) :
|
|
1425 |
(cmd->name[n] != '\0') ?
|
|
1426 |
(int)strlen(cmd->name) - (int)n :
|
|
1427 |
0;
|
|
1428 |
// if an exact match was found
|
|
1429 |
if ((size_t)((int)cmatch + cmp) == rdata->input.cursorpos) {
|
|
1430 |
cmatch = rdata->input.cursorpos;
|
937 |
1431 |
fill = cmd->name;
|
|
1432 |
// break the loop only if there are no case mismatches with the input
|
|
1433 |
n = rdata->input.cursorpos;
|
|
1434 |
_strccmp(fill, line, false, &n, &mlvl);
|
|
1435 |
if (mlvl == CHAR_MATCH_CASE) {
|
|
1436 |
break;
|
|
1437 |
}
|
938 |
1438 |
}
|
939 |
|
// if this is a worse one
|
940 |
|
else if ((cmp < 0) || (cmp == 0 && mlvl == CHAR_MATCH_CASE)) {
|
941 |
|
cmatch = (size_t)((int)cmatch + cmp);
|
|
1439 |
// if a not exact match was found
|
|
1440 |
else if ((size_t)((int)cmatch + cmp) > rdata->input.cursorpos) {
|
|
1441 |
// if this is the first one
|
|
1442 |
if (fill == line) {
|
|
1443 |
cmatch = (size_t)((int)cmatch + cmp);
|
|
1444 |
fill = cmd->name;
|
|
1445 |
}
|