Statistics
| Branch: | Tag: | Revision:

amiro-os / core / src / aos_shell.c @ 96621a83

History | View | Annotate | Download (72.9 KB)

1 e545e620 Thomas Schöpping
/*
2
AMiRo-OS is an operating system designed for the Autonomous Mini Robot (AMiRo) platform.
3 96621a83 Thomas Schöpping
Copyright (C) 2016..2020  Thomas Schöpping et al.
4 e545e620 Thomas Schöpping

5
This program is free software: you can redistribute it and/or modify
6
it under the terms of the GNU General Public License as published by
7
the Free Software Foundation, either version 3 of the License, or
8
(at your option) any later version.
9

10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
GNU General Public License for more details.
14

15
You should have received a copy of the GNU General Public License
16
along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
*/
18
19 53710ca3 Marc Rothmann
/**
20
 * @file    aos_shell.c
21
 * @brief   Shell code.
22
 * @details Shell code as well as shell related channels and streams.
23
 *
24
 * @addtogroup aos_shell
25
 * @{
26
 */
27
28 3940ba8a Thomas Schöpping
#include <amiroos.h>
29
#include <string.h>
30 e545e620 Thomas Schöpping
31 cda14729 Thomas Schöpping
#if (AMIROOS_CFG_SHELL_ENABLE == true) || defined(__DOXYGEN__)
32 3940ba8a Thomas Schöpping
33 f3ac1c96 Thomas Schöpping
/******************************************************************************/
34
/* LOCAL DEFINITIONS                                                          */
35
/******************************************************************************/
36 ba516b61 Thomas Schöpping
37
/**
38 697dba3c Thomas Schöpping
 * @brief   The character the input buffer is initialized with.
39
 */
40
#define INBUF_INIT_CHAR                         '\x07'
41
42
/**
43 ba516b61 Thomas Schöpping
 * @brief   Event mask to be set on OS related events.
44
 */
45 697dba3c Thomas Schöpping
#define EVENTMASK_OS                            EVENT_MASK(0)
46 ba516b61 Thomas Schöpping
47
/**
48
 * @brief   Event mask to be set on a input event.
49
 */
50 697dba3c Thomas Schöpping
#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"
121 ba516b61 Thomas Schöpping
122 f3ac1c96 Thomas Schöpping
/******************************************************************************/
123
/* EXPORTED VARIABLES                                                         */
124
/******************************************************************************/
125
126
/******************************************************************************/
127
/* LOCAL TYPES                                                                */
128
/******************************************************************************/
129
130
/*
131
 * forward declarations
132
 */
133
static size_t _channelwrite(void *instance, const uint8_t *bp, size_t n);
134
static size_t _channelread(void *instance, uint8_t *bp, size_t n);
135
static msg_t _channelput(void *instance, uint8_t b);
136
static msg_t _channelget(void *instance);
137
static msg_t _channelputt(void *instance, uint8_t b, sysinterval_t time);
138
static msg_t _channelgett(void *instance, sysinterval_t time);
139
static size_t _channelwritet(void *instance, const uint8_t *bp, size_t n, sysinterval_t time);
140
static size_t _channelreadt(void *instance, uint8_t *bp, size_t n, sysinterval_t time);
141
static msg_t _channelctl(void *instance, unsigned int operation, void *arg);
142
static size_t _streamwrite(void *instance, const uint8_t *bp, size_t n);
143
static size_t _stremread(void *instance, uint8_t *bp, size_t n);
144
static msg_t _streamput(void *instance, uint8_t b);
145
static msg_t _streamget(void *instance);
146
147
static const struct AosShellChannelVMT _channelvmt = {
148
  (size_t) 0,
149
  _channelwrite,
150
  _channelread,
151
  _channelput,
152
  _channelget,
153
  _channelputt,
154
  _channelgett,
155
  _channelwritet,
156
  _channelreadt,
157
  _channelctl,
158
};
159
160
static const struct AosShellStreamVMT _streamvmt = {
161
  (size_t) 0,
162
  _streamwrite,
163
  _stremread,
164
  _streamput,
165
  _streamget,
166
};
167
168
/**
169
 * @brief   Enumerator of special keyboard keys.
170
 */
171
typedef enum special_key {
172 cc33217b Thomas Schöpping
  KEY_UNKNOWN,          /**< any/unknow key */
173
  KEY_AMBIGUOUS,        /**< key is ambiguous */
174
  KEY_TAB,              /**< tabulator key */
175
  KEY_BACKSPACE,        /**< backspace key */
176
  KEY_INSERT,           /**< insert key */
177
  KEY_DELETE,           /**< delete key */
178 697dba3c Thomas Schöpping
  KEY_ESCAPE,           /**< escape key */
179 cc33217b Thomas Schöpping
  KEY_HOME,             /**< home key */
180
  KEY_END,              /**< end key */
181 697dba3c Thomas Schöpping
  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 */
192 f3ac1c96 Thomas Schöpping
} special_key_t;
193
194
/**
195
 * @brief   Enumerator for case (in)sensitive character matching.
196
 */
197
typedef enum charmatch {
198
  CHAR_MATCH_NOT    = 0,  /**< Characters do not match at all. */
199
  CHAR_MATCH_NCASE  = 1,  /**< Characters would match case insensitive. */
200
  CHAR_MATCH_CASE   = 2,  /**< Characters do match with case. */
201
} charmatch_t;
202
203 697dba3c Thomas Schöpping
/**
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
289 f3ac1c96 Thomas Schöpping
/******************************************************************************/
290
/* LOCAL VARIABLES                                                            */
291
/******************************************************************************/
292
293
/******************************************************************************/
294
/* LOCAL FUNCTIONS                                                            */
295
/******************************************************************************/
296
297 ba516b61 Thomas Schöpping
/**
298
 * @brief   Implementation of the BaseAsynchronous write() method (inherited from BaseSequentialStream).
299
 */
300
static size_t _channelwrite(void *instance, const uint8_t *bp, size_t n)
301
{
302
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
303 dd8738ea Thomas Schöpping
    return streamWrite(((AosShellChannel*)instance)->asyncchannel, bp, n);
304 ba516b61 Thomas Schöpping
  } else {
305
    return 0;
306
  }
307
}
308
309
/**
310
 * @brief   Implementation of the BaseAsynchronous read() method (inherited from BaseSequentialStream).
311
 */
312
static size_t _channelread(void *instance, uint8_t *bp, size_t n)
313
{
314 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
315
    return streamRead(((AosShellChannel*)instance)->asyncchannel, bp, n);
316
  } else {
317
    return 0;
318
  }
319 ba516b61 Thomas Schöpping
}
320
321
/**
322
 * @brief   Implementation of the BaseAsynchronous put() method (inherited from BaseSequentialStream).
323
 */
324
static msg_t _channelput(void *instance, uint8_t b)
325
{
326
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
327 dd8738ea Thomas Schöpping
    return streamPut(((AosShellChannel*)instance)->asyncchannel, b);
328 ba516b61 Thomas Schöpping
  } else {
329
    return MSG_RESET;
330
  }
331
}
332
333
/**
334
 * @brief   Implementation of the BaseAsynchronous get() method (inherited from BaseSequentialStream).
335
 */
336
static msg_t _channelget(void *instance)
337
{
338 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
339
    return streamGet(((AosShellChannel*)instance)->asyncchannel);
340
  } else {
341
    return MSG_RESET;
342
  }
343 ba516b61 Thomas Schöpping
}
344
345
/**
346
 * @brief   Implementation of the BaseAsynchronous putt() method.
347
 */
348 2c99037f Marc Rothmann
static msg_t _channelputt(void *instance, uint8_t b, sysinterval_t time)
349 ba516b61 Thomas Schöpping
{
350
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
351 dd8738ea Thomas Schöpping
    return chnPutTimeout(((AosShellChannel*)instance)->asyncchannel, b, time);
352 ba516b61 Thomas Schöpping
  } else {
353
    return MSG_RESET;
354
  }
355
}
356
357
/**
358
 * @brief   Implementation of the BaseAsynchronous gett() method.
359
 */
360 2c99037f Marc Rothmann
static msg_t _channelgett(void *instance, sysinterval_t time)
361 ba516b61 Thomas Schöpping
{
362 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
363
    return chnGetTimeout(((AosShellChannel*)instance)->asyncchannel, time);
364
  } else {
365
    return MSG_RESET;
366
  }
367 ba516b61 Thomas Schöpping
}
368
369
/**
370
 * @brief   Implementation of the BaseAsynchronous writet() method.
371
 */
372 2c99037f Marc Rothmann
static size_t _channelwritet(void *instance, const uint8_t *bp, size_t n, sysinterval_t time)
373 ba516b61 Thomas Schöpping
{
374
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
375 dd8738ea Thomas Schöpping
    return chnWriteTimeout(((AosShellChannel*)instance)->asyncchannel, bp, n, time);
376 ba516b61 Thomas Schöpping
  } else {
377
    return 0;
378
  }
379
}
380
381
/**
382
 * @brief   Implementation of the BaseAsynchronous readt() method.
383
 */
384 2c99037f Marc Rothmann
static size_t _channelreadt(void *instance, uint8_t *bp, size_t n, sysinterval_t time)
385 ba516b61 Thomas Schöpping
{
386 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
387
    return chnReadTimeout(((AosShellChannel*)instance)->asyncchannel, bp, n, time);
388
  } else {
389
    return 0;
390
  }
391 ba516b61 Thomas Schöpping
}
392
393 2c99037f Marc Rothmann
/**
394
 * @brief   Implementation of the BaseAsynchronousChannel ctl() method.
395
 */
396 f3ac1c96 Thomas Schöpping
static msg_t _channelctl(void *instance, unsigned int operation, void *arg)
397
{
398 2c99037f Marc Rothmann
  (void) instance;
399
400
  switch (operation) {
401
  case CHN_CTL_NOP:
402
    osalDbgCheck(arg == NULL);
403
    break;
404
  case CHN_CTL_INVALID:
405
    osalDbgAssert(false, "invalid CTL operation");
406
    break;
407
  default:
408
    break;
409
  }
410
  return MSG_OK;
411
}
412
413 697dba3c Thomas Schöpping
/**
414
 * @brief   Implementation of the BaseSequentialStream write() method.
415
 */
416 ba516b61 Thomas Schöpping
static size_t _streamwrite(void *instance, const uint8_t *bp, size_t n)
417
{
418
  aosDbgCheck(instance != NULL);
419
420
  // local variables
421
  AosShellChannel* channel = ((AosShellStream*)instance)->channel;
422
  size_t bytes;
423
  size_t maxbytes = 0;
424
425
  // iterate through the list of channels
426
  while (channel != NULL) {
427
    bytes = streamWrite(channel, bp, n);
428
    maxbytes = (bytes > maxbytes) ? bytes : maxbytes;
429
    channel = channel->next;
430
  }
431
432
  return maxbytes;
433
}
434
435 697dba3c Thomas Schöpping
/**
436
 * @brief   Implementation of the BaseSequentialStream read() method.
437
 */
438 ba516b61 Thomas Schöpping
static size_t _stremread(void *instance, uint8_t *bp, size_t n)
439
{
440
  (void)instance;
441
  (void)bp;
442
  (void)n;
443
444
  return 0;
445
}
446
447 697dba3c Thomas Schöpping
/**
448
 * @brief   Implementation of the BaseSequentialStream put() method.
449
 */
450 ba516b61 Thomas Schöpping
static msg_t _streamput(void *instance, uint8_t b)
451
{
452
  aosDbgCheck(instance != NULL);
453
454
  // local variables
455
  AosShellChannel* channel = ((AosShellStream*)instance)->channel;
456 dd8738ea Thomas Schöpping
  msg_t ret = MSG_OK;
457 ba516b61 Thomas Schöpping
458
  // iterate through the list of channels
459
  while (channel != NULL) {
460 dd8738ea Thomas Schöpping
    msg_t ret_ = streamPut(channel, b);
461
    ret = (ret_ < ret) ? ret_ : ret;
462 ba516b61 Thomas Schöpping
    channel = channel->next;
463
  }
464
465 dd8738ea Thomas Schöpping
  return ret;
466 ba516b61 Thomas Schöpping
}
467
468 697dba3c Thomas Schöpping
/**
469
 * @brief   Implementation of the BaseSequentialStream get() method.
470
 */
471 ba516b61 Thomas Schöpping
static msg_t _streamget(void *instance)
472
{
473
  (void)instance;
474
475
  return 0;
476
}
477
478 e545e620 Thomas Schöpping
/**
479 697dba3c Thomas Schöpping
 * @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
/**
594 e545e620 Thomas Schöpping
 * @brief   Print the shell prompt
595
 * @details Depending on the configuration flags, the system uptime is printed before the prompt string.
596
 *
597
 * @param[in] shell   Pointer to the shell object.
598
 */
599
static void _printPrompt(aos_shell_t* shell)
600
{
601
  aosDbgCheck(shell != NULL);
602
603 8399aeae Thomas Schöpping
  // print some time informattion before prompt if configured
604
  if (shell->config & (AOS_SHELL_CONFIG_PROMPT_UPTIME | AOS_SHELL_CONFIG_PROMPT_DATETIME)) {
605
    // printf the system uptime
606
    if ((shell->config & (AOS_SHELL_CONFIG_PROMPT_UPTIME | AOS_SHELL_CONFIG_PROMPT_DATETIME)) == AOS_SHELL_CONFIG_PROMPT_UPTIME) {
607
      // get current system uptime
608
      aos_timestamp_t uptime;
609
      aosSysGetUptime(&uptime);
610
611
      chprintf((BaseSequentialStream*)&shell->stream, "[%01u:%02u:%02u:%02u:%03u:%03u] ",
612
               (uint32_t)(uptime / MICROSECONDS_PER_DAY),
613
               (uint8_t)(uptime % MICROSECONDS_PER_DAY / MICROSECONDS_PER_HOUR),
614
               (uint8_t)(uptime % MICROSECONDS_PER_HOUR / MICROSECONDS_PER_MINUTE),
615
               (uint8_t)(uptime % MICROSECONDS_PER_MINUTE / MICROSECONDS_PER_SECOND),
616
               (uint16_t)(uptime % MICROSECONDS_PER_SECOND / MICROSECONDS_PER_MILLISECOND),
617
               (uint16_t)(uptime % MICROSECONDS_PER_MILLISECOND / MICROSECONDS_PER_MICROSECOND));
618
    }
619 23437e98 Thomas Schöpping
#if (HAL_USE_RTC == TRUE)
620 8399aeae Thomas Schöpping
    else if ((shell->config & (AOS_SHELL_CONFIG_PROMPT_UPTIME | AOS_SHELL_CONFIG_PROMPT_DATETIME)) == AOS_SHELL_CONFIG_PROMPT_DATETIME) {
621
      // get current RTC time
622
      struct tm dt;
623
      aosSysGetDateTime(&dt);
624
      chprintf((BaseSequentialStream*)&shell->stream, "[%02u-%02u-%04u|%02u:%02u:%02u] ",
625
               dt.tm_mday,
626
               dt.tm_mon + 1,
627
               dt.tm_year + 1900,
628
               dt.tm_hour,
629
               dt.tm_min,
630
               dt.tm_sec);
631
    }
632 7de0cc90 Thomas Schöpping
#endif /* (HAL_USE_RTC == TRUE) */
633 8399aeae Thomas Schöpping
    else {
634
      aosDbgAssert(false);
635
    }
636 e545e620 Thomas Schöpping
  }
637
638
  // print the actual prompt string
639
  if (shell->prompt && !(shell->config & AOS_SHELL_CONFIG_PROMPT_MINIMAL)) {
640 ba516b61 Thomas Schöpping
    chprintf((BaseSequentialStream*)&shell->stream, "%s$ ", shell->prompt);
641 e545e620 Thomas Schöpping
  } else {
642 ba516b61 Thomas Schöpping
    chprintf((BaseSequentialStream*)&shell->stream, "%>$ ");
643 e545e620 Thomas Schöpping
  }
644
645
  return;
646
}
647
648
/**
649
 * @brief   Interprete a escape sequence
650 d96ce104 Thomas Schöpping
 * @details This function interpretes escape sequences (starting with ASCII
651
 *          "Escape" character 0x1B) according to the VT100 / VT52 ANSI escape
652
 *          sequence definitions.
653
 * @note    Only the most important escape sequences are implemented yet.
654 e545e620 Thomas Schöpping
 *
655
 * @param[in] seq   Character sequence to interprete.
656
 *                  Must be terminated by NUL byte.
657
 *
658
 * @return          A @p special_key value.
659
 */
660
static special_key_t _interpreteEscapeSequence(const char seq[])
661
{
662
  // local variables
663 cc33217b Thomas Schöpping
  unsigned long strl = 0;
664
  const unsigned long seql = strlen(seq);
665 e545e620 Thomas Schöpping
  bool ambiguous = false;
666
667
  // TAB
668 697dba3c Thomas Schöpping
  /* not supported yet; use '\x09' instead */
669 e545e620 Thomas Schöpping
670
  // BACKSPACE
671 697dba3c Thomas Schöpping
  /* not supported yet; use '\x08' instead */
672 e545e620 Thomas Schöpping
673
  // ESCAPE
674 697dba3c Thomas Schöpping
  /* not supported yes; use '\x1B' instead */
675
676
  // CTRL + C
677
  /* not defined yet; use '\x03' instead */
678 e545e620 Thomas Schöpping
679
  // INSERT
680 697dba3c Thomas Schöpping
  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
    }
687 e545e620 Thomas Schöpping
  }
688
689
  // DELETE
690 697dba3c Thomas Schöpping
  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
    }
697 e545e620 Thomas Schöpping
  }
698
699
  // HOME
700 697dba3c Thomas Schöpping
  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
    }
707 e545e620 Thomas Schöpping
  }
708
709
  // END
710 697dba3c Thomas Schöpping
  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
    }
717 e545e620 Thomas Schöpping
  }
718
719
  // PAGE UP
720 697dba3c Thomas Schöpping
  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
    }
727 e545e620 Thomas Schöpping
  }
728
729
  // PAGE DOWN
730 697dba3c Thomas Schöpping
  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
    }
737 e545e620 Thomas Schöpping
  }
738
739
  // ARROW UP
740 697dba3c Thomas Schöpping
  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
    }
747 e545e620 Thomas Schöpping
  }
748
749
  // ARROW DOWN
750 697dba3c Thomas Schöpping
  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
    }
757 e545e620 Thomas Schöpping
  }
758
759
  // ARROW LEFT
760 697dba3c Thomas Schöpping
  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
    }
767 e545e620 Thomas Schöpping
  }
768
769
  // ARROW RIGHT
770 697dba3c Thomas Schöpping
  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
    }
777 cc33217b Thomas Schöpping
  }
778
779
  // CTRL + ARROW UP
780 697dba3c Thomas Schöpping
  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
    }
787 cc33217b Thomas Schöpping
  }
788
789
  // CTRL + ARROW DOWN
790 697dba3c Thomas Schöpping
  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
    }
797 cc33217b Thomas Schöpping
  }
798
799
  // CTRL + ARROW LEFT
800 697dba3c Thomas Schöpping
  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
    }
807 cc33217b Thomas Schöpping
  }
808
809
  // CTRL + ARROW RIGHT
810 697dba3c Thomas Schöpping
  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
    }
817 e545e620 Thomas Schöpping
  }
818
819
  return ambiguous ? KEY_AMBIGUOUS : KEY_UNKNOWN;
820
}
821
822
/**
823 697dba3c Thomas Schöpping
 * @brief   Move the cursor in the terminal.
824 e545e620 Thomas Schöpping
 *
825
 * @param[in] shell   Pointer to the shell object.
826 697dba3c Thomas Schöpping
 * @param[in] line    Pointer to the current content of the line.
827 e545e620 Thomas Schöpping
 * @param[in] from    Starting position of the cursor.
828
 * @param[in] to      Target position to move the cursor to.
829
 *
830
 * @return            The number of positions moved.
831
 */
832 697dba3c Thomas Schöpping
static int _moveCursor(aos_shell_t* shell, const char* line, size_t from, size_t to)
833 e545e620 Thomas Schöpping
{
834
  aosDbgCheck(shell != NULL);
835 697dba3c Thomas Schöpping
  aosDbgCheck(line !=  NULL || from >= to);
836
  aosDbgCheck(from <= shell->input.linewidth);
837
  aosDbgCheck(to <= shell->input.linewidth);
838 e545e620 Thomas Schöpping
839
  // local variables
840
  size_t pos = from;
841
842
  // move cursor left by printing backspaces
843
  while (pos > to) {
844 ba516b61 Thomas Schöpping
    streamPut(&shell->stream, '\b');
845 e545e620 Thomas Schöpping
    --pos;
846
  }
847
848
  // move cursor right by printing line content
849
  while (pos < to) {
850 697dba3c Thomas Schöpping
    streamPut(&shell->stream, (uint8_t)line[pos]);
851 e545e620 Thomas Schöpping
    ++pos;
852
  }
853
854
  return (int)pos - (int)from;
855
}
856
857
/**
858 697dba3c Thomas Schöpping
 * @brief   Print content of a given string to the shell output stream.
859 e545e620 Thomas Schöpping
 *
860
 * @param[in] shell   Pointer to the shell object.
861 697dba3c Thomas Schöpping
 * @param[in] line    Pointer to the line to be printed.
862 e545e620 Thomas Schöpping
 * @param[in] from    First position to start printing from.
863
 * @param[in] to      Position after the last character to print.
864
 *
865
 * @return            Number of characters printed.
866
 */
867 697dba3c Thomas Schöpping
static size_t _printString(aos_shell_t* shell, const char* line, size_t from, size_t to)
868 e545e620 Thomas Schöpping
{
869
  aosDbgCheck(shell != NULL);
870 697dba3c Thomas Schöpping
  aosDbgCheck(line != NULL || from >= to);
871
  aosDbgCheck(from < shell->input.linewidth);
872
  aosDbgCheck(to <= shell->input.linewidth);
873 e545e620 Thomas Schöpping
874
  // local variables
875
  size_t cnt;
876
877
  for (cnt = 0; from + cnt < to; ++cnt) {
878 697dba3c Thomas Schöpping
    streamPut(&shell->stream, (uint8_t)line[from + cnt]);
879 e545e620 Thomas Schöpping
  }
880
881
  return cnt;
882
}
883
884 697dba3c Thomas Schöpping
/**
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
{
896 cc33217b Thomas Schöpping
  aosDbgCheck(shell != NULL);
897 697dba3c Thomas Schöpping
  aosDbgCheck(rdata != NULL);
898 cc33217b Thomas Schöpping
899
  // check whether input line is already full
900 697dba3c Thomas Schöpping
  if (rdata->input.length + 1 >= shell->input.linewidth) {
901 cc33217b Thomas Schöpping
    return 0;
902 697dba3c Thomas Schöpping
  }
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);
923 cc33217b Thomas Schöpping
    return 1;
924
  }
925
}
926
927 e545e620 Thomas Schöpping
/**
928 697dba3c Thomas Schöpping
 * @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
/**
963 e545e620 Thomas Schöpping
 * @brief   Compare two characters.
964
 *
965
 * @param[in] lhs       First character to compare.
966
 * @param[in] rhs       Second character to compare.
967
 *
968
 * @return              How well the characters match.
969
 */
970
static inline charmatch_t _charcmp(char lhs, char rhs)
971
{
972
  // if lhs is a upper case letter and rhs is a lower case letter
973
  if (lhs >= 'A' && lhs <= 'Z' && rhs >= 'a' && rhs <= 'z') {
974
    return (lhs == (rhs - 'a' + 'A')) ? CHAR_MATCH_NCASE : CHAR_MATCH_NOT;
975
  }
976
  // if lhs is a lower case letter and rhs is a upper case letter
977
  else if (lhs >= 'a' && lhs <= 'z' && rhs >= 'A' && rhs <= 'Z') {
978
    return ((lhs - 'a' + 'A') == rhs) ? CHAR_MATCH_NCASE : CHAR_MATCH_NOT;
979
  }
980
  // default
981
  else {
982
    return (lhs == rhs) ? CHAR_MATCH_CASE : CHAR_MATCH_NOT;
983
  }
984
}
985
986
/**
987
 * @brief   Maps an character from ASCII to a modified custom encoding.
988
 * @details The custom character encoding is very similar to ASCII and has the following structure:
989
 *          0x00=NULL ... 0x40='@' (identically to ASCII)
990
 *          0x4A='a'; 0x4B='A'; 0x4C='b'; 0x4D='B' ... 0x73='z'; 0x74='Z' (custom letter order)
991
 *          0x75='[' ... 0x7A='`' (0x5B..0x60 is ASCII)
992
 *          0x7B='{' ... 0x7F=DEL (identically to ASCII)
993
 *
994
 * @param[in] c   Character to map to the custom encoding.
995
 *
996
 * @return    The customly encoded character.
997
 */
998 697dba3c Thomas Schöpping
static inline char _mapAscii2Custom(char c)
999 e545e620 Thomas Schöpping
{
1000
  if (c >= 'A' && c <= 'Z') {
1001
    return ((c - 'A') * 2) + 'A' + 1;
1002
  } else if (c > 'Z' && c < 'a') {
1003
    return c + ('z' - 'a') + 1;
1004
  } else if (c >= 'a' && c <= 'z') {
1005
    return ((c - 'a') * 2) + 'A';
1006
  } else {
1007
    return c;
1008
  }
1009
}
1010
1011
/**
1012
 * @brief   Compares two strings wrt letter case.
1013
 * @details Comparisson uses a custom character encoding or mapping.
1014
 *          See @p _mapAscii2Custom for details.
1015
 *
1016
 * @param[in] str1    First string to compare.
1017
 * @param[in] str2    Second string to compare.
1018
 * @param[in] cs      Flag indicating whether comparison shall be case sensitive.
1019
 * @param[in,out] n   Maximum number of character to compare (in) and number of matching characters (out).
1020
 *                    If a null pointer is specified, this parameter is ignored.
1021
 *                    If the value pointed to is zero, comarison will not be limited.
1022
 * @param[out] m      Optional indicator whether there was at least one case mismatch.
1023
 *
1024
 * @return      Integer value indicating the relationship between the strings.
1025
 * @retval <0   The first character that does not match has a lower value in str1 than in str2.
1026
 * @retval  0   The contents of both strings are equal.
1027
 * @retval >0   The first character that does not match has a greater value in str1 than in str2.
1028
 */
1029
static int _strccmp(const char *str1, const char *str2, bool cs, size_t* n, charmatch_t* m)
1030
{
1031
  aosDbgCheck(str1 != NULL);
1032
  aosDbgCheck(str2 != NULL);
1033
1034
  // initialize variables
1035
  if (m) {
1036
    *m = CHAR_MATCH_NOT;
1037
  }
1038
  size_t i = 0;
1039
1040
  // iterate through the strings
1041
  while ((n == NULL) || (*n == 0) || (*n > 0 && i < *n)) {
1042
    // break on NUL
1043
    if (str1[i] == '\0' || str2[i] == '\0') {
1044
      if (n) {
1045
        *n = i;
1046
      }
1047
      break;
1048
    }
1049
    // compare character
1050
    const charmatch_t match = _charcmp(str1[i], str2[i]);
1051
    if ((match == CHAR_MATCH_CASE) || (!cs && match == CHAR_MATCH_NCASE)) {
1052
      if (m != NULL && *m != CHAR_MATCH_NCASE) {
1053
        *m = match;
1054
      }
1055
      ++i;
1056
    } else {
1057
      if (n) {
1058
        *n = i;
1059
      }
1060
      break;
1061
    }
1062
  }
1063
1064
  return _mapAscii2Custom(str1[i]) - _mapAscii2Custom(str2[i]);
1065
}
1066
1067 27286ba5 Thomas Schöpping
/**
1068 697dba3c Thomas Schöpping
 * @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
/**
1149 27286ba5 Thomas Schöpping
 * @brief   Read input from a channel as long as there is data available.
1150
 *
1151 697dba3c Thomas Schöpping
 * @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.
1154 27286ba5 Thomas Schöpping
 *
1155 697dba3c Thomas Schöpping
 * @return  Number of characters read.
1156 27286ba5 Thomas Schöpping
 */
1157 697dba3c Thomas Schöpping
static size_t _readChannel(aos_shell_t* shell, runtimedata_t* rdata, AosShellChannel* channel)
1158 e545e620 Thomas Schöpping
{
1159
  aosDbgCheck(shell != NULL);
1160 697dba3c Thomas Schöpping
  aosDbgCheck(rdata != NULL);
1161 ba516b61 Thomas Schöpping
  aosDbgCheck(channel != NULL);
1162 e545e620 Thomas Schöpping
1163
  // local variables
1164 697dba3c Thomas Schöpping
  size_t bytes = 0;
1165 e545e620 Thomas Schöpping
  char c;
1166 dd8738ea Thomas Schöpping
  special_key_t key;
1167 697dba3c Thomas Schöpping
  action_t action;
1168 ba516b61 Thomas Schöpping
1169
  // read character by character from the channel
1170
  while (chnReadTimeout(channel, (uint8_t*)&c, 1, TIME_IMMEDIATE)) {
1171 697dba3c Thomas Schöpping
    // increment byte counter
1172
    ++bytes;
1173 e545e620 Thomas Schöpping
1174 697dba3c Thomas Schöpping
    // drop any further input after an execution request was detected
1175
    if (rdata->lastaction == ACTION_EXECUTE && bytes > 1) {
1176 c18a848e Thomas Schöpping
      continue;
1177
    }
1178
1179 697dba3c Thomas Schöpping
    // 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
        }
1206 e545e620 Thomas Schöpping
      }
1207
    }
1208
1209 697dba3c Thomas Schöpping
    /*
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') {
1217 ba516b61 Thomas Schöpping
1218
      // printable character
1219 697dba3c Thomas Schöpping
      if (c >= '\x20' && c <= '\x7E') {
1220
        action = ACTION_READCHAR;
1221 ba516b61 Thomas Schöpping
      }
1222
1223
      // tab key or character
1224 697dba3c Thomas Schöpping
      else if (c == '\x09' || key == KEY_TAB) {
1225 ba516b61 Thomas Schöpping
        /*
1226
         * pressing tab once applies auto fill
1227 697dba3c Thomas Schöpping
         * pressing tab a second time (or more) prints suggestions
1228 ba516b61 Thomas Schöpping
         */
1229 697dba3c Thomas Schöpping
        if (rdata->lastaction == ACTION_AUTOCOMPLETE || rdata->lastaction == ACTION_SUGGEST) {
1230
          action = ACTION_SUGGEST;
1231 e545e620 Thomas Schöpping
        } else {
1232 697dba3c Thomas Schöpping
          action = ACTION_AUTOCOMPLETE;
1233 e545e620 Thomas Schöpping
        }
1234 ba516b61 Thomas Schöpping
      }
1235
1236 697dba3c Thomas Schöpping
      // carriage return ('\r') or line feed ('\n') character
1237
      else if (c == '\x0D' || c == '\x0A') {
1238
        action = ACTION_EXECUTE;
1239 ba516b61 Thomas Schöpping
      }
1240
1241
      // backspace key or character
1242 697dba3c Thomas Schöpping
      else if (c == '\x08' || key == KEY_BACKSPACE) {
1243 ba516b61 Thomas Schöpping
        // ignore if cursor is at very left
1244 697dba3c Thomas Schöpping
        if (rdata->input.cursorpos > 0) {
1245
          action = ACTION_DELETEBACKWARD;
1246 e545e620 Thomas Schöpping
        }
1247 ba516b61 Thomas Schöpping
      }
1248
1249 697dba3c Thomas Schöpping
      // 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;
1254 e545e620 Thomas Schöpping
        }
1255 ba516b61 Thomas Schöpping
      }
1256
1257 697dba3c Thomas Schöpping
      // '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;
1265 e545e620 Thomas Schöpping
        }
1266 ba516b61 Thomas Schöpping
      }
1267
1268 697dba3c Thomas Schöpping
      // '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;
1276 e545e620 Thomas Schöpping
        }
1277 697dba3c Thomas Schöpping
        // if a historic entry is selected, recall the next input from history
1278
        else if (rdata->buffer.selected > 1) {
1279
          action = ACTION_RECALLNEXT;
1280 e545e620 Thomas Schöpping
        }
1281 ba516b61 Thomas Schöpping
      }
1282
1283
      // 'arrow left' key
1284 697dba3c Thomas Schöpping
      else if (key == KEY_ARROWLEFT) {
1285 e545e620 Thomas Schöpping
        // ignore if cursor is very left
1286 697dba3c Thomas Schöpping
        if (rdata->input.cursorpos > 0) {
1287
          action = ACTION_CURSORLEFT;
1288 e545e620 Thomas Schöpping
        }
1289 ba516b61 Thomas Schöpping
      }
1290
1291
      // 'arrow right' key
1292 697dba3c Thomas Schöpping
      else if (key == KEY_ARROWRIGHT) {
1293 cc33217b Thomas Schöpping
        // ignore if cursor is very right
1294 697dba3c Thomas Schöpping
        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;
1322 e545e620 Thomas Schöpping
        }
1323 ba516b61 Thomas Schöpping
      }
1324
1325 cc33217b Thomas Schöpping
      // CTRL + 'arrow left' key combination
1326 697dba3c Thomas Schöpping
      else if (key == KEY_CTRL_ARROWLEFT) {
1327 cc33217b Thomas Schöpping
        // ignore if cursor is very left
1328 697dba3c Thomas Schöpping
        if (rdata->input.cursorpos > 0) {
1329
          action = ACTION_CURSORWORDLEFT;
1330 cc33217b Thomas Schöpping
        }
1331
      }
1332
1333
      // CTRL + 'arrow right' key combination
1334 697dba3c Thomas Schöpping
      else if (key == KEY_CTRL_ARROWRIGHT) {
1335 cc33217b Thomas Schöpping
        // ignore if cursor is very right
1336 697dba3c Thomas Schöpping
        if (rdata->input.cursorpos < rdata->input.length) {
1337
          action = ACTION_CURSORWORDRIGHT;
1338 cc33217b Thomas Schöpping
        }
1339
      }
1340
1341 697dba3c Thomas Schöpping
      // '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
        }
1347 ba516b61 Thomas Schöpping
      }
1348
1349 697dba3c Thomas Schöpping
      // '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
        }
1355 e545e620 Thomas Schöpping
      }
1356 cc33217b Thomas Schöpping
1357 697dba3c Thomas Schöpping
      // 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;
1370 cc33217b Thomas Schöpping
      }
1371 e545e620 Thomas Schöpping
    }
1372 697dba3c Thomas Schöpping
    // 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 */
1379 e545e620 Thomas Schöpping
1380 697dba3c Thomas Schöpping
    /*
1381
     * execute action
1382
     */
1383 ba516b61 Thomas Schöpping
    switch (action) {
1384 697dba3c Thomas Schöpping
      case ACTION_NONE:
1385
      {
1386
        // do nothing (ignore input) and read next byte
1387
        break;
1388
      }
1389
1390
      case ACTION_READCHAR:
1391 ba516b61 Thomas Schöpping
      {
1392 697dba3c Thomas Schöpping
        char* line = _prepare4Modification(shell, rdata);
1393
        if (_printChar(shell, rdata, c) == 0) {
1394 cc33217b Thomas Schöpping
          // line is full
1395 697dba3c Thomas Schöpping
          _moveCursor(shell, line, rdata->input.cursorpos, rdata->input.length);
1396 ba516b61 Thomas Schöpping
          chprintf((BaseSequentialStream*)&shell->stream, "\n\tmaximum line width reached\n");
1397 e545e620 Thomas Schöpping
          _printPrompt(shell);
1398 697dba3c Thomas Schöpping
          _printString(shell, line, 0, rdata->input.length);
1399
          _moveCursor(shell, line, rdata->input.length, rdata->input.cursorpos);
1400 e545e620 Thomas Schöpping
        }
1401
        break;
1402 ba516b61 Thomas Schöpping
      }
1403 e545e620 Thomas Schöpping
1404 697dba3c Thomas Schöpping
      case ACTION_AUTOCOMPLETE:
1405 e545e620 Thomas Schöpping
      {
1406 697dba3c Thomas Schöpping
        // local variables
1407
        char* line = _getVisualisedEntry(shell, rdata);
1408
        const char* fill = line;
1409
        size_t cmatch = rdata->input.cursorpos;
1410 e545e620 Thomas Schöpping
        charmatch_t matchlevel = CHAR_MATCH_NOT;
1411
        size_t n;
1412 697dba3c Thomas Schöpping
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;
1431 e545e620 Thomas Schöpping
              fill = cmd->name;
1432 697dba3c Thomas Schöpping
              // 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
              }
1438 e545e620 Thomas Schöpping
            }
1439 697dba3c Thomas Schöpping
            // 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
              }
1446
              // if this is a worse one
1447
              else if ((cmp < 0) || (cmp == 0 && mlvl == CHAR_MATCH_CASE)) {
1448
                cmatch = (size_t)((int)cmatch + cmp);
1449
              }
1450 e545e620 Thomas Schöpping
            }
1451 697dba3c Thomas Schöpping
            // non matching commands are ignored
1452
            else {}
1453 e545e620 Thomas Schöpping
          }
1454 697dba3c Thomas Schöpping
1455
          // evaluate if there are case mismatches
1456
          n = cmatch;
1457
          _strccmp(line, fill, shell->config & AOS_SHELL_CONFIG_MATCH_CASE, &n, &matchlevel);
1458
          // print the auto fill if any
1459
          if (cmatch > rdata->input.cursorpos || (cmatch == rdata->input.cursorpos && matchlevel == CHAR_MATCH_NCASE)) {
1460
            // limit auto fill so it will not overflow the line width
1461
            if (rdata->input.length + (cmatch - rdata->input.cursorpos) > shell->input.linewidth) {
1462
              cmatch = shell->input.linewidth - rdata->input.length + rdata->input.cursorpos;
1463
            }
1464
            // move trailing memory further in the line
1465
            memmove(&line[cmatch], &line[rdata->input.cursorpos], (rdata->input.length - rdata->input.cursorpos) * sizeof(char));
1466
            rdata->input.length += cmatch - rdata->input.cursorpos;
1467
            // if there was no incorrect case when matching
1468
            if (matchlevel == CHAR_MATCH_CASE) {
1469
              // insert fill command name to line
1470
              memcpy(&line[rdata->input.cursorpos], &fill[rdata->input.cursorpos], (cmatch - rdata->input.cursorpos) * sizeof(char));
1471
              // print the output
1472
              _printString(shell, line, rdata->input.cursorpos, rdata->input.length);
1473
            } else {
1474
              // overwrite line with fill command name
1475
              memcpy(line, fill, cmatch * sizeof(char));
1476
              // reprint the whole line
1477
              _moveCursor(shell, line, rdata->input.cursorpos, 0);
1478
              _printString(shell, line, 0, rdata->input.length);
1479
            }
1480
            // move cursor to the end of the matching sequence
1481
            rdata->input.cursorpos = cmatch;
1482
            _moveCursor(shell, line, rdata->input.length, rdata->input.cursorpos);
1483 e545e620 Thomas Schöpping
          }
1484
        }
1485
        break;
1486
      }
1487
1488 697dba3c Thomas Schöpping
      case ACTION_SUGGEST:
1489 e545e620 Thomas Schöpping
      {
1490 697dba3c Thomas Schöpping
        // local variables
1491
        const char* line = _getVisualisedEntry(shell, rdata);
1492 e545e620 Thomas Schöpping
        unsigned int matches = 0;
1493 697dba3c Thomas Schöpping
1494 e545e620 Thomas Schöpping
        // iterate through command list
1495
        for (aos_shellcommand_t* cmd = shell->commands; cmd != NULL; cmd = cmd->next) {
1496 697dba3c Thomas Schöpping
          // compare line content with command, except if cursorpos is 0
1497
          size_t i = rdata->input.cursorpos;
1498
          if (rdata->input.cursorpos > 0) {
1499
            _strccmp(line, cmd->name, true, &i, NULL);
1500 e545e620 Thomas Schöpping
          }
1501 697dba3c Thomas Schöpping
          const int cmp = (i < rdata->input.cursorpos) ?
1502
                ((int)i - (int)rdata->input.cursorpos) :
1503
                (cmd->name[i] != '\0') ?
1504
                  (int)strlen(cmd->name) - (int)i :
1505
                  0;
1506 e545e620 Thomas Schöpping
          // if a match was found
1507
          if (cmp > 0) {
1508
            // if this is the first one
1509
            if (matches == 0) {
1510 697dba3c Thomas Schöpping
              _moveCursor(shell, line, rdata->input.cursorpos, rdata->input.length);
1511 ba516b61 Thomas Schöpping
              streamPut(&shell->stream, '\n');
1512 e545e620 Thomas Schöpping
            }
1513
            // print the command
1514 ba516b61 Thomas Schöpping
            chprintf((BaseSequentialStream*)&shell->stream, "\t%s\n", cmd->name);
1515 e545e620 Thomas Schöpping
            ++matches;
1516
          }
1517
        }
1518
        // reprint the prompt and line if any matches have been found
1519
        if (matches > 0) {
1520
          _printPrompt(shell);
1521 697dba3c Thomas Schöpping
          _printString(shell, line, 0, rdata->input.length);
1522
          _moveCursor(shell, line, rdata->input.length, rdata->input.cursorpos);
1523 e545e620 Thomas Schöpping
        }
1524
        break;
1525
      }
1526
1527 697dba3c Thomas Schöpping
      case ACTION_EXECUTE:
1528 ba516b61 Thomas Schöpping
      {
1529 697dba3c Thomas Schöpping
        // if the input buffer can hold historic entries
1530
        if (shell->input.nentries > 1) {
1531
          _prepare4Modification(shell, rdata);
1532 e545e620 Thomas Schöpping
        }
1533
        break;
1534 ba516b61 Thomas Schöpping
      }
1535 e545e620 Thomas Schöpping
1536 697dba3c Thomas Schöpping
      case ACTION_DELETEBACKWARD:
1537 ba516b61 Thomas Schöpping
      {
1538 697dba3c Thomas Schöpping
        char* line = _prepare4Modification(shell, rdata);
1539
        --rdata->input.cursorpos;
1540
        memmove(&line[rdata->input.cursorpos], &line[rdata->input.cursorpos + 1], (rdata->input.length - rdata->input.cursorpos) * sizeof(char));
1541
        --rdata->input.length;
1542
        line[rdata->input.length] = '\0';
1543
        _moveCursor(shell, line, rdata->input.cursorpos + 1, rdata->input.cursorpos);
1544
        _printString(shell, line, rdata->input.cursorpos, rdata->input.length);
1545 ba516b61 Thomas Schöpping
        streamPut(&shell->stream, ' ');
1546 697dba3c Thomas Schöpping
        _moveCursor(shell, line, rdata->input.length + 1, rdata->input.cursorpos);
1547 e545e620 Thomas Schöpping
        break;
1548 ba516b61 Thomas Schöpping
      }
1549 e545e620 Thomas Schöpping
1550 697dba3c Thomas Schöpping
      case ACTION_DELETEFORWARD:
1551 ba516b61 Thomas Schöpping
      {
1552 697dba3c Thomas Schöpping
        char* line = _prepare4Modification(shell, rdata);
1553
        --rdata->input.length;
1554
        memmove(&line[rdata->input.cursorpos], &line[rdata->input.cursorpos + 1], (rdata->input.length - rdata->input.cursorpos) * sizeof(char));
1555
        _printString(shell, line, rdata->input.cursorpos, rdata->input.length);
1556 ba516b61 Thomas Schöpping
        streamPut(&shell->stream, ' ');
1557 697dba3c Thomas Schöpping
        _moveCursor(shell, line, rdata->input.length + 1, rdata->input.cursorpos);
1558 e545e620 Thomas Schöpping
        break;
1559 ba516b61 Thomas Schöpping
      }
1560 e545e620 Thomas Schöpping
1561 697dba3c Thomas Schöpping
      case ACTION_CLEAR:
1562 e545e620 Thomas Schöpping
      {
1563 697dba3c Thomas Schöpping
        // clear visualization
1564
        _moveCursor(shell, NULL, rdata->input.cursorpos, 0);
1565
        for (size_t cpos = 0; cpos < rdata->input.length; ++cpos) {
1566
          streamPut(&shell->stream, ' ');
1567 e545e620 Thomas Schöpping
        }
1568 697dba3c Thomas Schöpping
        _moveCursor(shell, NULL, rdata->input.length, 0);
1569
1570
        // update metadata
1571
        rdata->input.cursorpos = 0;
1572
        rdata->input.length = 0;
1573
        rdata->buffer.selected = 0;
1574
1575 e545e620 Thomas Schöpping
        break;
1576
      }
1577
1578 697dba3c Thomas Schöpping
      case ACTION_RECALLPREVIOUS:
1579 ba516b61 Thomas Schöpping
      {
1580 697dba3c Thomas Schöpping
        // if the input was cleared but the current entry is occupied by a modified copy of a history entry, skip the current entry
1581
        if (rdata->buffer.selected == 0 && rdata->buffer.edited > 1) {
1582
          rdata->buffer.selected += 2;
1583
        } else {
1584
          ++rdata->buffer.selected;
1585 e545e620 Thomas Schöpping
        }
1586 697dba3c Thomas Schöpping
        _overwriteOutput(shell, rdata, _getVisualisedEntry(shell, rdata));
1587
        break;
1588
      }
1589
1590
      case ACTION_RECALLNEXT:
1591
      {
1592
        --rdata->buffer.selected;
1593
        _overwriteOutput(shell, rdata, _getVisualisedEntry(shell, rdata));
1594 e545e620 Thomas Schöpping
        break;
1595 ba516b61 Thomas Schöpping
      }
1596 e545e620 Thomas Schöpping
1597 697dba3c Thomas Schöpping
      case ACTION_RECALLOLDEST:
1598 ba516b61 Thomas Schöpping
      {
1599 697dba3c Thomas Schöpping
        // find oldest valid entry
1600
        rdata->buffer.selected = shell->input.nentries;
1601
        while (_getSelectedEntry(shell, rdata)[0] == INBUF_INIT_CHAR) {
1602
          --rdata->buffer.selected;
1603
        }
1604
1605
        _overwriteOutput(shell, rdata, _getVisualisedEntry(shell, rdata));
1606 e545e620 Thomas Schöpping
        break;
1607 ba516b61 Thomas Schöpping
      }
1608 e545e620 Thomas Schöpping
1609 697dba3c Thomas Schöpping
      case ACTION_RECALLCURRENT:
1610 ba516b61 Thomas Schöpping
      {
1611 697dba3c Thomas Schöpping
        rdata->buffer.selected = 1;
1612
        _overwriteOutput(shell, rdata, _getVisualisedEntry(shell, rdata));
1613 e545e620 Thomas Schöpping
        break;
1614 ba516b61 Thomas Schöpping
      }
1615 e545e620 Thomas Schöpping
1616 697dba3c Thomas Schöpping
      case ACTION_CURSORLEFT:
1617 ba516b61 Thomas Schöpping
      {
1618 697dba3c Thomas Schöpping
        _moveCursor(shell, NULL, rdata->input.cursorpos, rdata->input.cursorpos - 1);
1619
        --rdata->input.cursorpos;
1620 e545e620 Thomas Schöpping
        break;
1621 ba516b61 Thomas Schöpping
      }
1622 e545e620 Thomas Schöpping
1623 697dba3c Thomas Schöpping
      case ACTION_CURSORRIGHT:
1624 ba516b61 Thomas Schöpping
      {
1625 697dba3c Thomas Schöpping
        _moveCursor(shell, _getVisualisedEntry(shell, rdata), rdata->input.cursorpos, rdata->input.cursorpos + 1);
1626
        ++rdata->input.cursorpos;
1627 e545e620 Thomas Schöpping
        break;
1628 ba516b61 Thomas Schöpping
      }
1629 e545e620 Thomas Schöpping
1630 697dba3c Thomas Schöpping
      case ACTION_CURSORWORDLEFT:
1631 cc33217b Thomas Schöpping
      {
1632 697dba3c Thomas Schöpping
        // local variables
1633
        const char* line = _getVisualisedEntry(shell, rdata);
1634
        size_t cpos = rdata->input.cursorpos;
1635
1636
        // skip spaces
1637
        while (cpos > 0) {
1638 cc33217b Thomas Schöpping
          --cpos;
1639 697dba3c Thomas Schöpping
          if (line[cpos] != ' ') {
1640
            break;
1641
          }
1642 cc33217b Thomas Schöpping
        }
1643 697dba3c Thomas Schöpping
1644
        // search for beginning of the word
1645
        while (cpos > 0) {
1646 cc33217b Thomas Schöpping
          --cpos;
1647 697dba3c Thomas Schöpping
          if (line[cpos] == ' ') {
1648
            // go back to first character of the word
1649
            ++cpos;
1650
            break;
1651
          }
1652 cc33217b Thomas Schöpping
        }
1653 697dba3c Thomas Schöpping
1654
        // move the cursor and set metadata
1655
        _moveCursor(shell, line, rdata->input.cursorpos, cpos);
1656
        rdata->input.cursorpos = cpos;
1657
1658 cc33217b Thomas Schöpping
        break;
1659
      }
1660
1661 697dba3c Thomas Schöpping
      case ACTION_CURSORWORDRIGHT:
1662 cc33217b Thomas Schöpping
      {
1663 697dba3c Thomas Schöpping
        // local variables
1664
        const char* line = _getVisualisedEntry(shell, rdata);
1665
        size_t cpos = rdata->input.cursorpos;
1666
1667
        // skip spaces
1668
        while (cpos < rdata->input.length && line[cpos] == ' ') {
1669 cc33217b Thomas Schöpping
          ++cpos;
1670
        }
1671 697dba3c Thomas Schöpping
1672
        // search for end of the word
1673
        while (cpos < rdata->input.length && line[cpos] != ' ') {
1674 cc33217b Thomas Schöpping
          ++cpos;
1675
        }
1676 697dba3c Thomas Schöpping
1677
        // move cursor and set metadata
1678
        _moveCursor(shell, line, rdata->input.cursorpos, cpos);
1679
        rdata->input.cursorpos = cpos;
1680
1681 cc33217b Thomas Schöpping
        break;
1682
      }
1683
1684 697dba3c Thomas Schöpping
      case ACTION_CURSOR2END:
1685 ba516b61 Thomas Schöpping
      {
1686 697dba3c Thomas Schöpping
        _moveCursor(shell, _getVisualisedEntry(shell, rdata), rdata->input.cursorpos, rdata->input.length);
1687
        rdata->input.cursorpos = rdata->input.length;
1688 c18a848e Thomas Schöpping
        break;
1689 ba516b61 Thomas Schöpping
      }
1690 e545e620 Thomas Schöpping
1691 697dba3c Thomas Schöpping
      case ACTION_CURSOR2START:
1692 ba516b61 Thomas Schöpping
      {
1693 697dba3c Thomas Schöpping
        _moveCursor(shell, _getVisualisedEntry(shell, rdata), rdata->input.cursorpos, 0);
1694
        rdata->input.cursorpos = 0;
1695 cc33217b Thomas Schöpping
        break;
1696
      }
1697
1698 697dba3c Thomas Schöpping
      case ACTION_RESET:
1699 cc33217b Thomas Schöpping
      {
1700 697dba3c Thomas Schöpping
        // print reset indicator
1701
        chprintf((BaseSequentialStream*)&shell->stream, "^C");
1702
        if (!(shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE)) {
1703
          _printString(shell, _getVisualisedEntry(shell, rdata), rdata->input.cursorpos, rdata->input.length);
1704 cc33217b Thomas Schöpping
        }
1705 697dba3c Thomas Schöpping
        chprintf((BaseSequentialStream*)&shell->stream, "\n");
1706
1707
        // reset buffers and metadata
1708
        if (rdata->buffer.edited != 0) {
1709
          memset(_getCurrentEntry(shell, rdata), '\0', shell->input.linewidth * sizeof(char));
1710
        }
1711
        rdata->input.length = 0;
1712
        rdata->input.cursorpos = 0;
1713
        rdata->buffer.selected = (shell->input.nentries > 1) ? 1 : 0;
1714
        rdata->buffer.edited = 0;
1715
1716
        // print a new prompt
1717
        _printPrompt(shell);
1718
1719 e545e620 Thomas Schöpping
        break;
1720 ba516b61 Thomas Schöpping
      }
1721 e545e620 Thomas Schöpping
1722 697dba3c Thomas Schöpping
      case ACTION_INSERTTOGGLE:
1723 ba516b61 Thomas Schöpping
      {
1724 697dba3c Thomas Schöpping
        if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
1725
          shell->config &= ~AOS_SHELL_CONFIG_INPUT_OVERWRITE;
1726
        } else {
1727
          shell->config |= AOS_SHELL_CONFIG_INPUT_OVERWRITE;
1728
        }
1729
        break;
1730 ba516b61 Thomas Schöpping
      }
1731 e545e620 Thomas Schöpping
1732 697dba3c Thomas Schöpping
      case ACTION_ESCSTART:
1733
      {
1734
        rdata->input.escseq[0] = c;
1735
        break;
1736
      }
1737 e545e620 Thomas Schöpping
1738 697dba3c Thomas Schöpping
      case ACTION_PRINTUNKNOWNSEQUENCE:
1739
      {
1740
        _prepare4Modification(shell, rdata);
1741
        for (size_t seqc = 1; rdata->input.escseq[seqc] != '\0'; ++seqc) {
1742
          // element 0 would be unprintable ESC character
1743
          _printChar(shell, rdata, rdata->input.escseq[seqc]);
1744
        }
1745
        memset(rdata->input.escseq, '\0', AOS_SHELL_ESCSEQUENCE_LENGTH * sizeof(char));
1746
        break;
1747
      }
1748
    }
1749 c18a848e Thomas Schöpping
1750 697dba3c Thomas Schöpping
    // update runtime data
1751
    rdata->lastaction = (action != ACTION_NONE) ? action : rdata->lastaction;
1752
  } /* end of while */
1753
1754
  return bytes;
1755 e545e620 Thomas Schöpping
}
1756
1757
/**
1758 697dba3c Thomas Schöpping
 * @brief   Parses the content of the given string to separate arguments.
1759 e545e620 Thomas Schöpping
 *
1760 10fd7ac9 Thomas Schöpping
 * @param[in]   shell   Pointer to the shell object.
1761 697dba3c Thomas Schöpping
 * @param[in]   str     String to be parsed.
1762 10fd7ac9 Thomas Schöpping
 * @param[out]  argbuf  Buffer to store argument pointers to.
1763 e545e620 Thomas Schöpping
 *
1764
 * @return            Number of arguments found.
1765
 */
1766 697dba3c Thomas Schöpping
static size_t _parseArguments(aos_shell_t* shell, char* str, char** argbuf)
1767 e545e620 Thomas Schöpping
{
1768
  aosDbgCheck(shell != NULL);
1769 697dba3c Thomas Schöpping
  aosDbgCheck(str != NULL);
1770 10fd7ac9 Thomas Schöpping
  aosDbgCheck(argbuf != NULL);
1771 e545e620 Thomas Schöpping
1772
  /*
1773
   * States for a very small FSM.
1774
   */
1775
  typedef enum {
1776
    START,
1777
    SPACE,
1778
    TEXT,
1779
    END,
1780
  } state_t;
1781
1782
  // local variables
1783
  state_t state = START;
1784 10fd7ac9 Thomas Schöpping
  size_t nargs = 0;
1785 e545e620 Thomas Schöpping
1786
  // iterate through the line
1787 697dba3c Thomas Schöpping
  for (size_t c = 0; c < shell->input.linewidth; ++c) {
1788 e545e620 Thomas Schöpping
    // terminate at first NUL byte
1789 697dba3c Thomas Schöpping
    if (str[c] == '\0') {
1790 e545e620 Thomas Schöpping
      state = END;
1791
      break;
1792
    }
1793
    // spaces become NUL bytes
1794 697dba3c Thomas Schöpping
    else if (str[c] == ' ') {
1795
      str[c] = '\0';
1796 e545e620 Thomas Schöpping
      state = SPACE;
1797
    }
1798
    // handle non-NUL bytes
1799
    else {
1800
      switch (state) {
1801
        case START:
1802
        case SPACE:
1803
          // ignore too many arguments
1804 10fd7ac9 Thomas Schöpping
          if (nargs < shell->input.nargs) {
1805 697dba3c Thomas Schöpping
            argbuf[nargs] = &str[c];
1806 e545e620 Thomas Schöpping
          }
1807 10fd7ac9 Thomas Schöpping
          ++nargs;
1808 e545e620 Thomas Schöpping
          break;
1809
        case TEXT:
1810
        case END:
1811
          break;
1812
      }
1813
      state = TEXT;
1814
    }
1815
  }
1816
1817
  // set all remaining argument pointers to NULL
1818 10fd7ac9 Thomas Schöpping
  for (size_t a = nargs; a < shell->input.nargs; ++a) {
1819
    argbuf[a] = NULL;
1820 e545e620 Thomas Schöpping
  }
1821
1822 10fd7ac9 Thomas Schöpping
  return nargs;
1823 e545e620 Thomas Schöpping
}
1824
1825 f3ac1c96 Thomas Schöpping
/******************************************************************************/
1826
/* EXPORTED FUNCTIONS                                                         */
1827
/******************************************************************************/
1828
1829 e545e620 Thomas Schöpping
/**
1830
 * @brief   Initializes a shell object with the specified parameters.
1831
 *
1832 697dba3c Thomas Schöpping
 * @param[in,out] shell           Pointer to the shell object to be initialized.
1833
 * @param[in]     prompt          Prompt line to print (NULL = use default prompt).
1834
 * @param[in]     inbuf           Two dimensional input buffer.
1835
 * @param[in]     entries         Number of entries in the input buffer (1st dimension).
1836
 * @param[in]     linewidth       Length of each entry in the input buffer (2nd dimension).
1837
 * @param[in]     numargs         Maximum number of arguments (defines size of internal buffer).
1838
 */
1839
void aosShellInit(aos_shell_t* shell, const char* prompt, char inbuf[], size_t entries, size_t linewidth, size_t numargs)
1840 e545e620 Thomas Schöpping
{
1841
  aosDbgCheck(shell != NULL);
1842 697dba3c Thomas Schöpping
  aosDbgCheck(inbuf != NULL);
1843
  aosDbgCheck(entries > 0);
1844
  aosDbgCheck(linewidth > 0);
1845
  aosDbgCheck(numargs > 0);
1846 e545e620 Thomas Schöpping
1847
  // set parameters
1848
  shell->thread = NULL;
1849
  chEvtObjectInit(&shell->eventSource);
1850 ba516b61 Thomas Schöpping
  aosShellStreamInit(&shell->stream);
1851 e545e620 Thomas Schöpping
  shell->prompt = prompt;
1852
  shell->commands = NULL;
1853
  shell->execstatus.command = NULL;
1854
  shell->execstatus.retval = 0;
1855 697dba3c Thomas Schöpping
  shell->input.buffer = inbuf;
1856
  shell->input.nentries = entries;
1857
  shell->input.linewidth = linewidth;
1858
  shell->input.nargs = numargs;
1859 e545e620 Thomas Schöpping
  shell->config = 0x00;
1860
1861 cc33217b Thomas Schöpping
  // initialize buffers
1862 697dba3c Thomas Schöpping
  memset(shell->input.buffer, INBUF_INIT_CHAR, shell->input.nentries * shell->input.linewidth * sizeof(char));
1863 e545e620 Thomas Schöpping
1864
  return;
1865
}
1866
1867
/**
1868 ba516b61 Thomas Schöpping
 * @brief   Initialize an AosShellStream object.
1869
 *
1870
 * @param[in] stream  The AosShellStrem to initialize.
1871
 */
1872
void aosShellStreamInit(AosShellStream* stream)
1873
{
1874
  aosDbgCheck(stream != NULL);
1875
1876
  stream->vmt = &_streamvmt;
1877
  stream->channel = NULL;
1878
1879
  return;
1880
}
1881
1882
/**
1883
 * @brief   Initialize an AosShellChannel object with the specified parameters.
1884
 *
1885 dd8738ea Thomas Schöpping
 * @param[in] channel       The AosShellChannel to initialize.
1886
 * @param[in] asyncchannel  An BaseAsynchronousChannel this AosShellChannel is associated with.
1887 ba516b61 Thomas Schöpping
 */
1888 dd8738ea Thomas Schöpping
void aosShellChannelInit(AosShellChannel* channel, BaseAsynchronousChannel* asyncchannel)
1889 ba516b61 Thomas Schöpping
{
1890
  aosDbgCheck(channel != NULL);
1891 dd8738ea Thomas Schöpping
  aosDbgCheck(asyncchannel != NULL);
1892 ba516b61 Thomas Schöpping
1893
  channel->vmt = &_channelvmt;
1894 dd8738ea Thomas Schöpping
  channel->asyncchannel = asyncchannel;
1895
  channel->listener.wflags = 0;
1896 ba516b61 Thomas Schöpping
  channel->next = NULL;
1897
  channel->flags = 0;
1898
1899
  return;
1900
}
1901
1902
/**
1903 e545e620 Thomas Schöpping
 * @brief   Inserts a command to the shells list of commands.
1904
 *
1905
 * @param[in] shell   Pointer to the shell object.
1906
 * @param[in] cmd     Pointer to the command to add.
1907
 *
1908
 * @return            A status value.
1909
 * @retval AOS_SUCCESS  The command was added successfully.
1910
 * @retval AOS_ERROR    Another command with identical name already exists.
1911
 */
1912
aos_status_t aosShellAddCommand(aos_shell_t *shell, aos_shellcommand_t *cmd)
1913
{
1914
  aosDbgCheck(shell != NULL);
1915
  aosDbgCheck(cmd != NULL);
1916
  aosDbgCheck(cmd->name != NULL && strlen(cmd->name) > 0 && strchr(cmd->name, ' ') == NULL && strchr(cmd->name, '\t') == NULL);
1917
  aosDbgCheck(cmd->callback != NULL);
1918
  aosDbgCheck(cmd->next == NULL);
1919
1920
  aos_shellcommand_t* prev = NULL;
1921
  aos_shellcommand_t** curr = &(shell->commands);
1922
1923
  // insert the command to the list wrt lexographical order (exception: lower case characters preceed upper their uppercase counterparts)
1924 ba516b61 Thomas Schöpping
  while (*curr != NULL) {
1925
    // iterate through the list as long as the command names are 'smaller'
1926
    const int cmp = _strccmp((*curr)->name, cmd->name, true, NULL, NULL);
1927
    if (cmp < 0) {
1928
      prev = *curr;
1929
      curr = &((*curr)->next);
1930
      continue;
1931
    }
1932
    // error if the command already exists
1933
    else if (cmp == 0) {
1934
      return AOS_ERROR;
1935
    }
1936
    // insert the command as soon as a 'larger' name was found
1937
    else /* if (cmpval > 0) */ {
1938
      cmd->next = *curr;
1939
      // special case: the first command is larger
1940
      if (prev == NULL) {
1941
        shell->commands = cmd;
1942
      } else {
1943
        prev->next = cmd;
1944 e545e620 Thomas Schöpping
      }
1945 ba516b61 Thomas Schöpping
      return AOS_SUCCESS;
1946 e545e620 Thomas Schöpping
    }
1947
  }
1948 ba516b61 Thomas Schöpping
  // the end of the list has been reached
1949
1950
  // append the command
1951
  *curr = cmd;
1952
  return AOS_SUCCESS;
1953 e545e620 Thomas Schöpping
}
1954
1955
/**
1956
 * @brief   Removes a command from the shells list of commands.
1957
 *
1958
 * @param[in] shell     Pointer to the shell object.
1959
 * @param[in] cmd       Name of the command to removde.
1960
 * @param[out] removed  Optional pointer to the command that was removed.
1961
 *
1962
 * @return              A status value.
1963
 * @retval AOS_SUCCESS  The command was removed successfully.
1964
 * @retval AOS_ERROR    The command name was not found.
1965
 */
1966
aos_status_t aosShellRemoveCommand(aos_shell_t *shell, char *cmd, aos_shellcommand_t **removed)
1967
{
1968
  aosDbgCheck(shell != NULL);
1969
  aosDbgCheck(cmd != NULL && strlen(cmd) > 0);
1970
1971
  aos_shellcommand_t* prev = NULL;
1972
  aos_shellcommand_t** curr = &(shell->commands);
1973
1974
  // iterate through the list and seach for the specified command name
1975
  while (curr != NULL) {
1976
    const int cmpval = strcmp((*curr)->name, cmd);
1977
    // iterate through the list as long as the command names are 'smaller'
1978
    if (cmpval < 0) {
1979
      prev = *curr;
1980
      curr = &((*curr)->next);
1981
      continue;
1982
    }
1983
    // remove the command when found
1984
    else if (cmpval == 0) {
1985
      // special case: the first command matches
1986
      if (prev == NULL) {
1987
        shell->commands = (*curr)->next;
1988
      } else {
1989
        prev->next = (*curr)->next;
1990
      }
1991
      (*curr)->next = NULL;
1992
      // set the optional output argument
1993
      if (removed != NULL) {
1994
        *removed = *curr;
1995
      }
1996
      return AOS_SUCCESS;
1997
    }
1998
    // break the loop if the command names are 'larger'
1999
    else /* if (cmpval > 0) */ {
2000
      break;
2001
    }
2002
  }
2003
2004
  // if the command was not found, return an error
2005
  return AOS_ERROR;
2006
}
2007
2008
/**
2009 aed3754b Thomas Schöpping
 * @brief   Count the number of commands assigned to the shell.
2010
 *
2011
 * @param[in] shell   The shell to count the commands for.
2012
 *
2013
 * @return  The number of commands associated to the shell.
2014
 */
2015
unsigned int aosShellCountCommands(aos_shell_t* shell)
2016
{
2017
  aosDbgCheck(shell != NULL);
2018
2019
  unsigned int count = 0;
2020
  aos_shellcommand_t* cmd = shell->commands;
2021
  while (cmd != NULL) {
2022
    ++count;
2023
    cmd = cmd->next;
2024
  }
2025
2026
  return count;
2027
}
2028
2029
/**
2030 ba516b61 Thomas Schöpping
 * @brief   Add a channel to a AosShellStream.
2031
 *
2032
 * @param[in] stream    The AosShellStream to extend.
2033
 * @param[in] channel   The channel to be added to the stream.
2034
 */
2035
void aosShellStreamAddChannel(AosShellStream* stream, AosShellChannel* channel)
2036
{
2037
  aosDbgCheck(stream != NULL);
2038 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL && channel->next == NULL && (channel->flags & AOS_SHELLCHANNEL_ATTACHED) == 0);
2039 ba516b61 Thomas Schöpping
2040
  // prepend the new channel
2041
  chSysLock();
2042
  channel->flags |= AOS_SHELLCHANNEL_ATTACHED;
2043
  channel->next = stream->channel;
2044
  stream->channel = channel;
2045
  chSysUnlock();
2046
2047
  return;
2048
}
2049
2050
/**
2051
 * @brief   Remove a channel from an AosShellStream.
2052
 *
2053
 * @param[in] stream    The AosShellStream to modify.
2054
 * @param[in] channel   The channel to remove.
2055 aed3754b Thomas Schöpping
 *
2056
 * @return              A status value.
2057
 * @retval AOS_SUCCESS  The channel was removed successfully.
2058
 * @retval AOS_ERROR    The specified channel was not found to be associated with the shell.
2059 ba516b61 Thomas Schöpping
 */
2060
aos_status_t aosShellStreamRemoveChannel(AosShellStream* stream, AosShellChannel* channel)
2061
{
2062
  aosDbgCheck(stream != NULL);
2063 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL && channel->flags & AOS_SHELLCHANNEL_ATTACHED);
2064 ba516b61 Thomas Schöpping
2065
  // local varibales
2066
  AosShellChannel* prev = NULL;
2067
  AosShellChannel* curr = stream->channel;
2068
2069
  // iterate through the list and search for the specified channel
2070
  while (curr != NULL) {
2071
    // if the channel was found
2072
    if (curr == channel) {
2073
      chSysLock();
2074
      // special case: the first channel matches (prev is NULL)
2075
      if (prev == NULL) {
2076
        stream->channel = curr->next;
2077
      } else {
2078
        prev->next = channel->next;
2079
      }
2080
      curr->next = NULL;
2081
      curr->flags &= ~AOS_SHELLCHANNEL_ATTACHED;
2082
      chSysUnlock();
2083
      return AOS_SUCCESS;
2084
    }
2085
  }
2086
2087
  // if the channel was not found, return an error
2088
  return AOS_ERROR;
2089
}
2090
2091
/**
2092 243fb4e2 Thomas Schöpping
 * @brief   Enable a AosShellChannel as input.
2093 ba516b61 Thomas Schöpping
 *
2094
 * @param[in] channel   The channel to enable as input.
2095
 */
2096
void aosShellChannelInputEnable(AosShellChannel* channel)
2097
{
2098 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
2099 ba516b61 Thomas Schöpping
2100
  chSysLock();
2101
  channel->listener.wflags |= CHN_INPUT_AVAILABLE;
2102
  channel->flags |= AOS_SHELLCHANNEL_INPUT_ENABLED;
2103
  chSysUnlock();
2104
2105
  return;
2106
}
2107
2108
/**
2109 243fb4e2 Thomas Schöpping
 * @brief   Disable a AosShellChannel as input.
2110 ba516b61 Thomas Schöpping
 *
2111
 * @param[in] channel   The channel to disable as input.
2112
 */
2113
void aosShellChannelInputDisable( AosShellChannel* channel)
2114
{
2115 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
2116 ba516b61 Thomas Schöpping
2117
  chSysLock();
2118
  channel->listener.wflags &= ~CHN_INPUT_AVAILABLE;
2119
  channel->flags &= ~AOS_SHELLCHANNEL_INPUT_ENABLED;
2120
  chSysUnlock();
2121
2122
  return;
2123
}
2124
2125
/**
2126 243fb4e2 Thomas Schöpping
 * @brief   Enable a AosShellChannel as output.
2127 ba516b61 Thomas Schöpping
 *
2128
 * @param[in] channel   The channel to enable as output.
2129
 */
2130
void aosShellChannelOutputEnable(AosShellChannel* channel)
2131
{
2132 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
2133 ba516b61 Thomas Schöpping
2134
  channel->flags |= AOS_SHELLCHANNEL_OUTPUT_ENABLED;
2135
2136
  return;
2137
}
2138
2139
/**
2140 243fb4e2 Thomas Schöpping
 * @brief   Disable a AosShellChannel as output.
2141 ba516b61 Thomas Schöpping
 *
2142
 * @param[in] channel   The channel to disable as output.
2143
 */
2144
void aosShellChannelOutputDisable(AosShellChannel* channel)
2145
{
2146 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
2147 ba516b61 Thomas Schöpping
2148
  channel->flags &= ~AOS_SHELLCHANNEL_OUTPUT_ENABLED;
2149
2150
  return;
2151
}
2152
2153
/**
2154 e545e620 Thomas Schöpping
 * @brief   Thread main function.
2155
 *
2156
 * @param[in] aosShellThread    Name of the function;
2157
 * @param[in] shell             Pointer to the shell object.
2158
 */
2159 af4fd4a2 Thomas Schöpping
void aosShellThread(void* shell)
2160 e545e620 Thomas Schöpping
{
2161
  aosDbgCheck(shell != NULL);
2162 697dba3c Thomas Schöpping
  aosDbgCheck(((aos_shell_t*)shell)->input.nentries > 0);
2163 e545e620 Thomas Schöpping
2164
  // local variables
2165 ba516b61 Thomas Schöpping
  eventmask_t eventmask;
2166
  eventflags_t eventflags;
2167
  AosShellChannel* channel;
2168 697dba3c Thomas Schöpping
  runtimedata_t rdata;
2169 10fd7ac9 Thomas Schöpping
  char* args[((aos_shell_t*)shell)->input.nargs];
2170 e545e620 Thomas Schöpping
  size_t nargs = 0;
2171 ba516b61 Thomas Schöpping
  aos_shellcommand_t* cmd;
2172
2173 697dba3c Thomas Schöpping
  // initialize variables and buffers
2174
  rdata.input.length = 0;
2175
  rdata.input.cursorpos = 0;
2176
  memset(rdata.input.escseq, '\0', AOS_SHELL_ESCSEQUENCE_LENGTH * sizeof(char));
2177
  rdata.buffer.current = 0;
2178
  rdata.buffer.selected = (((aos_shell_t*)shell)->input.nentries > 1) ? 1 : 0;
2179
  rdata.buffer.edited = 0;
2180
  rdata.lastaction = ACTION_NONE;
2181 10fd7ac9 Thomas Schöpping
  for (size_t arg = 0; arg < ((aos_shell_t*)shell)->input.nargs; ++arg) {
2182
    args[arg] = NULL;
2183
  }
2184 697dba3c Thomas Schöpping
  memset(_getCurrentEntry((aos_shell_t*)shell, &rdata), '\0', ((aos_shell_t*)shell)->input.linewidth * sizeof(char));
2185 ba516b61 Thomas Schöpping
2186
  // register OS related events
2187 697dba3c Thomas Schöpping
  chEvtRegisterMask(&aos.events.os, &(((aos_shell_t*)shell)->osEventListener), EVENTMASK_OS);
2188 ba516b61 Thomas Schöpping
  // register events to all input channels
2189
  for (channel = ((aos_shell_t*)shell)->stream.channel; channel != NULL; channel = channel->next) {
2190 697dba3c Thomas Schöpping
    chEvtRegisterMaskWithFlags(&(channel->asyncchannel->event), &(channel->listener), EVENTMASK_INPUT, channel->listener.wflags);
2191 ba516b61 Thomas Schöpping
  }
2192 e545e620 Thomas Schöpping
2193
  // fire start event
2194
  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_START);
2195
2196 ba516b61 Thomas Schöpping
  // print the prompt for the first time
2197
  _printPrompt((aos_shell_t*)shell);
2198
2199 e545e620 Thomas Schöpping
  // enter thread loop
2200
  while (!chThdShouldTerminateX()) {
2201 ba516b61 Thomas Schöpping
    // wait for event and handle it accordingly
2202
    eventmask = chEvtWaitOne(ALL_EVENTS);
2203
2204
    // handle event
2205
    switch (eventmask) {
2206
2207
      // OS related events
2208 697dba3c Thomas Schöpping
      case EVENTMASK_OS:
2209 ba516b61 Thomas Schöpping
      {
2210 697dba3c Thomas Schöpping
        eventflags = chEvtGetAndClearFlags(&((aos_shell_t*)shell)->osEventListener);
2211 ba516b61 Thomas Schöpping
        // handle shutdown/restart events
2212 cda14729 Thomas Schöpping
        if (eventflags & AOS_SYSTEM_EVENTFLAGS_SHUTDOWN_MASK) {
2213 ba516b61 Thomas Schöpping
          chThdTerminate(((aos_shell_t*)shell)->thread);
2214
        } else {
2215
          // print an error message
2216
          chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\nERROR: unknown OS event received (0x%08X)\n", eventflags);
2217
        }
2218 e545e620 Thomas Schöpping
        break;
2219
      }
2220
2221 ba516b61 Thomas Schöpping
      // input events
2222 697dba3c Thomas Schöpping
      case EVENTMASK_INPUT:
2223 ba516b61 Thomas Schöpping
      {
2224
        // check and handle all channels
2225
        channel = ((aos_shell_t*)shell)->stream.channel;
2226
        while (channel != NULL) {
2227
          eventflags = chEvtGetAndClearFlags(&channel->listener);
2228 ae8211ea Thomas Schöpping
          // if there is new input and a command shall be executed
2229 c18a848e Thomas Schöpping
          if (eventflags & CHN_INPUT_AVAILABLE) {
2230 697dba3c Thomas Schöpping
            _readChannel(shell, &rdata, channel);
2231
2232
            // if an execution request was detected
2233
            if (rdata.lastaction == ACTION_EXECUTE) {
2234
              streamPut(&((aos_shell_t*)shell)->stream, '\n');
2235
              char* line = _getVisualisedEntry((aos_shell_t*)shell, &rdata);
2236
2237
              // skip, if there is nothing to be executed
2238
              if (line) {
2239
                // parse arguments
2240
                nargs = _parseArguments(shell, line, args);
2241
                // check number of arguments found
2242
                if (nargs > ((aos_shell_t*)shell)->input.nargs) {
2243
                  chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\ttoo many arguments\n");
2244
                } else if (nargs > 0) {
2245
                  // search command list for arg[0] and execute callback
2246
                  cmd = ((aos_shell_t*)shell)->commands;
2247
                  while (cmd != NULL) {
2248
                    // if the requested command has been found
2249
                    if (strcmp(args[0], cmd->name) == 0) {
2250
                      ((aos_shell_t*)shell)->execstatus.command = cmd;
2251
                      chEvtBroadcastFlags(&((aos_shell_t*)shell)->eventSource, AOS_SHELL_EVTFLAG_EXECUTE);
2252
                      ((aos_shell_t*)shell)->execstatus.retval = cmd->callback((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, (int)nargs, args);
2253
                      chEvtBroadcastFlags(&((aos_shell_t*)shell)->eventSource, AOS_SHELL_EVTFLAG_DONE);
2254
                      // notify user if execution of the command was not successful
2255
                      if (((aos_shell_t*)shell)->execstatus.retval != 0) {
2256
                        chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "command returned exit status %d\n", ((aos_shell_t*)shell)->execstatus.retval);
2257
                      }
2258
                      break;
2259
                    }
2260
                    // keep searching
2261
                    else {
2262
                      cmd = cmd->next;
2263 c18a848e Thomas Schöpping
                    }
2264 697dba3c Thomas Schöpping
                  } /* end of while */
2265
2266
                  // if no matching command was found, print a message
2267
                  if (cmd == NULL) {
2268
                    chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\tcommand '%s' not found\n", args[0]);
2269 ba516b61 Thomas Schöpping
                  }
2270 697dba3c Thomas Schöpping
                }
2271 e545e620 Thomas Schöpping
2272 697dba3c Thomas Schöpping
                // restore spaces in the current entry, which have been modified to NUL bytes due to argument parsing
2273
                _restoreWhitespace(line, rdata.input.length);
2274
2275
                // update internat variables
2276
                {
2277
                  rdata.input.length = 0;
2278
                  rdata.input.cursorpos = 0;
2279
                  rdata.buffer.edited = 0;
2280
2281
                  // if the input buffer can hold historic entries
2282
                  if (((aos_shell_t*)shell)->input.nentries > 1) {
2283
                    // iterate in the history only if
2284
                    // there was some valid input and
2285
                    // the user did not execute the exact previous command again
2286
                    if (nargs > 0 &&
2287
                        strcmp(line, _getRelativeEntry((aos_shell_t*)shell, &rdata, 1)) != 0) {
2288
                      rdata.buffer.current = (rdata.buffer.current + 1) % ((aos_shell_t*)shell)->input.nentries;
2289
                    }
2290
                    // clear and select next (now current) entry
2291
                    memset(_getCurrentEntry((aos_shell_t*)shell, &rdata), '\0', ((aos_shell_t*)shell)->input.linewidth * sizeof(char));
2292
                    rdata.buffer.selected = 1;
2293
                  }
2294
                  // if there is only a single entry in the input buffer
2295
                  else {
2296
                    // do not clear/reset the entry, but start off with a cleared preview
2297
                    rdata.buffer.selected = 0;
2298
                  }
2299 c18a848e Thomas Schöpping
                }
2300 ba516b61 Thomas Schöpping
              }
2301 dd8738ea Thomas Schöpping
2302 697dba3c Thomas Schöpping
              // print a new prompt
2303
              if (!chThdShouldTerminateX()) {
2304
                _printPrompt((aos_shell_t*)shell);
2305
              }
2306 ba516b61 Thomas Schöpping
            }
2307 e545e620 Thomas Schöpping
          }
2308 ba516b61 Thomas Schöpping
2309
          // iterate to next channel
2310
          channel = channel->next;
2311 e545e620 Thomas Schöpping
        }
2312 ba516b61 Thomas Schöpping
        break;
2313 e545e620 Thomas Schöpping
      }
2314 ba516b61 Thomas Schöpping
2315
      // other events
2316
      default:
2317
      {
2318
        // print an error message
2319 1e5f7648 Thomas Schöpping
        chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\nSHELL: ERROR: unknown event received (0x%08X)\n", eventmask);
2320 ba516b61 Thomas Schöpping
        break;
2321 e545e620 Thomas Schöpping
      }
2322
2323 ba516b61 Thomas Schöpping
    } /* end of switch */
2324
2325
  } /* end of while */
2326 e545e620 Thomas Schöpping
2327
  // fire event and exit the thread
2328
  chSysLock();
2329
  chEvtBroadcastFlagsI(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_EXIT);
2330
  chThdExitS(MSG_OK);
2331
  // no chSysUnlock() required since the thread has been terminated an all waiting threads have been woken up
2332
}
2333 ba516b61 Thomas Schöpping
2334 cda14729 Thomas Schöpping
#endif /* (AMIROOS_CFG_SHELL_ENABLE == true) */
2335 53710ca3 Marc Rothmann
2336
/** @} */