commit cc33217bf0fddaa2b1f2d7d6409f88ecc2ca45e7
Author: Thomas Schöpping <tschoepp@cit-ec.uni-bielefeld.de>
Date:   Fri Apr 26 10:35:05 2019 +0200

    Enhanced the shell so that CTRL + arrow key will jump wordwise through the current input

diff --git a/core/src/aos_shell.c b/core/src/aos_shell.c
index 111fad5..30e21e5 100644
--- a/core/src/aos_shell.c
+++ b/core/src/aos_shell.c
@@ -94,21 +94,25 @@ static const struct AosShellStreamVMT _streamvmt = {
  * @brief   Enumerator of special keyboard keys.
  */
 typedef enum special_key {
-  KEY_UNKNOWN,      /**< any/unknow key */
-  KEY_AMBIGUOUS,    /**< key is ambiguous */
-  KEY_TAB,          /**< tabulator key */
-  KEY_ESCAPE,       /**< escape key */
-  KEY_BACKSPACE,    /**< backspace key */
-  KEY_INSERT,       /**< insert key */
-  KEY_DELETE,       /**< delete key */
-  KEY_HOME,         /**< home key */
-  KEY_END,          /**< end key */
-  KEY_PAGE_UP,      /**< page up key */
-  KEY_PAGE_DOWN,    /**< page down key */
-  KEY_ARROW_UP,     /**< arrow up key */
-  KEY_ARROW_DOWN,   /**< arrow down key */
-  KEY_ARROW_LEFT,   /**< arrow left key */
-  KEY_ARROW_RIGHT,  /**< arrow right key */
+  KEY_UNKNOWN,          /**< any/unknow key */
+  KEY_AMBIGUOUS,        /**< key is ambiguous */
+  KEY_TAB,              /**< tabulator key */
+  KEY_ESCAPE,           /**< escape key */
+  KEY_BACKSPACE,        /**< backspace key */
+  KEY_INSERT,           /**< insert key */
+  KEY_DELETE,           /**< delete key */
+  KEY_HOME,             /**< home key */
+  KEY_END,              /**< end key */
+  KEY_PAGE_UP,          /**< page up key */
+  KEY_PAGE_DOWN,        /**< page down key */
+  KEY_ARROW_UP,         /**< arrow up key */
+  KEY_ARROW_DOWN,       /**< arrow down key */
+  KEY_ARROW_LEFT,       /**< arrow left key */
+  KEY_ARROW_RIGHT,      /**< arrow right key */
+  KEY_CTRL_ARROW_UP,    /**< CTRL + arrow up key */
+  KEY_CTRL_ARROW_DOWN,  /**< CTRL + arrow down key */
+  KEY_CTRL_ARROW_LEFT,  /**< CTRL + arrow left key */
+  KEY_CTRL_ARROW_RIGHT, /**< CTRL + arrow right key */
 } special_key_t;
 
 /**
@@ -367,8 +371,10 @@ static void _printPrompt(aos_shell_t* shell)
 static special_key_t _interpreteEscapeSequence(const char seq[])
 {
   // local variables
+  char str[AOS_SHELL_ESCSEQUENCE_LENGTH];
+  unsigned long strl = 0;
+  const unsigned long seql = strlen(seq);
   bool ambiguous = false;
-  int cmp = 0;
 
   // TAB
   /* not supported yet; use "\x09" instead */
@@ -377,91 +383,138 @@ static special_key_t _interpreteEscapeSequence(const char seq[])
   /* not supported yet; use "\x08" instead */
 
   // ESCAPE
-  cmp = strcmp(seq, "\x1B");
-  if (cmp == 0) {
+  strncpy(str, "\x1B", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
     return KEY_ESCAPE;
-  } else {
-    ambiguous |= (cmp < 0);
+  } else if(seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
   }
 
   // INSERT
-  cmp = strcmp(seq, "\x1B\x5B\x32\x7E");
-  if (cmp == 0) {
+  strncpy(str, "\x1B\x5B\x32\x7E", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
     return KEY_INSERT;
-  } else {
-    ambiguous |= (cmp < 0);
+  } else if(seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
   }
 
   // DELETE
-  cmp = strcmp(seq, "\x1B\x5B\x33\x7E");
-  if (cmp == 0) {
+  strncpy(str, "\x1B\x5B\x33\x7E", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
     return KEY_DELETE;
-  } else {
-    ambiguous |= (cmp < 0);
+  } else if(seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
   }
 
   // HOME
-  cmp = strcmp(seq, "\x1B\x5B\x48");
-  if (cmp == 0) {
+  strncpy(str, "\x1B\x5B\x48", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
     return KEY_HOME;
-  } else {
-    ambiguous |= (cmp < 0);
+  } else if(seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
   }
 
   // END
-  cmp = strcmp(seq, "\x1B\x5B\x46");
-  if (cmp == 0) {
+  strncpy(str, "\x1B\x5B\x46", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
     return KEY_END;
-  } else {
-    ambiguous |= (cmp < 0);
+  } else if(seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
   }
 
   // PAGE UP
-  cmp = strcmp(seq, "\x1B\x5B\x35\x7E");
-  if (cmp == 0) {
+  strncpy(str, "\x1B\x5B\x35\x7E", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
     return KEY_PAGE_UP;
-  } else {
-    ambiguous |= (cmp < 0);
+  } else if(seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
   }
 
   // PAGE DOWN
-  cmp = strcmp(seq, "\x1B\x5B\x36\x7E");
-  if (cmp == 0) {
+  strncpy(str, "\x1B\x5B\x36\x7E", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
     return KEY_PAGE_DOWN;
-  } else {
-    ambiguous |= (cmp < 0);
+  } else if(seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
   }
 
   // ARROW UP
-  cmp = strcmp(seq, "\x1B\x5B\x41");
-  if (cmp == 0) {
+  strncpy(str, "\x1B\x5B\x41", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
     return KEY_ARROW_UP;
-  } else {
-    ambiguous |= (cmp < 0);
+  } else if(seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
   }
 
   // ARROW DOWN
-  cmp = strcmp(seq, "\x1B\x5B\x42");
-  if (cmp == 0) {
+  strncpy(str, "\x1B\x5B\x42", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
     return KEY_ARROW_DOWN;
-  } else {
-    ambiguous |= (cmp < 0);
+  } else if (seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
   }
 
   // ARROW LEFT
-  cmp = strcmp(seq, "\x1B\x5B\x44");
-  if (cmp == 0) {
+  strncpy(str, "\x1B\x5B\x44", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
     return KEY_ARROW_LEFT;
-  } else {
-    ambiguous |= (cmp < 0);
+  } else if (seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
   }
 
   // ARROW RIGHT
-  cmp = strcmp(seq, "\x1B\x5B\x43");
-  if (cmp == 0) {
+  strncpy(str, "\x1B\x5B\x43", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
     return KEY_ARROW_RIGHT;
-  } else {
-    ambiguous |= (cmp < 0);
+  } else if (seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
+  }
+
+  // CTRL + ARROW UP
+  strncpy(str, "\x1B\x5B\x31\x3B\x35\x41", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
+    return KEY_CTRL_ARROW_UP;
+  } else if(seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
+  }
+
+  // CTRL + ARROW DOWN
+  strncpy(str, "\x1B\x5B\x31\x3B\x35\x42", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
+    return KEY_CTRL_ARROW_DOWN;
+  } else if(seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
+  }
+
+  // CTRL + ARROW LEFT
+  strncpy(str, "\x1B\x5B\x31\x3B\x35\x44", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
+    return KEY_CTRL_ARROW_LEFT;
+  } else if(seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
+  }
+
+  // CTRL + ARROW RIGHT
+  strncpy(str, "\x1B\x5B\x31\x3B\x35\x43", AOS_SHELL_ESCSEQUENCE_LENGTH);
+  strl = strlen(str);
+  if (seql == strl && strncmp(seq, str, seql) == 0) {
+    return KEY_CTRL_ARROW_RIGHT;
+  } else if(seql < strl && strncmp(seq, str, seql) == 0) {
+    ambiguous = true;
   }
 
   return ambiguous ? KEY_AMBIGUOUS : KEY_UNKNOWN;
@@ -491,7 +544,7 @@ static int _moveCursor(aos_shell_t* shell, const size_t from, const size_t to)
 
   // move cursor right by printing line content
   while (pos < to) {
-    streamPut(&shell->stream, shell->line[pos]);
+    streamPut(&shell->stream, shell->input.line[pos]);
     ++pos;
   }
 
@@ -515,12 +568,44 @@ static inline size_t _printLine(aos_shell_t* shell, const size_t from, const siz
   size_t cnt;
 
   for (cnt = 0; from + cnt < to; ++cnt) {
-    streamPut(&shell->stream, shell->line[from + cnt]);
+    streamPut(&shell->stream, shell->input.line[from + cnt]);
   }
 
   return cnt;
 }
 
+static int _readChar(aos_shell_t* shell, const char c) {
+  aosDbgCheck(shell != NULL);
+
+  // check whether input line is already full
+  if (shell->inputdata.lineend + 1 >= shell->input.width) {
+    return 0;
+  } else {
+    // clear old line content on first input
+    if (shell->inputdata.noinput) {
+      memset(shell->input.line, '\0', shell->input.width);
+      shell->inputdata.noinput = false;
+    }
+    // overwrite content
+    if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
+      shell->input.line[shell->inputdata.cursorpos] = c;
+      ++shell->inputdata.cursorpos;
+      shell->inputdata.lineend = (shell->inputdata.cursorpos > shell->inputdata.lineend) ? shell->inputdata.cursorpos : shell->inputdata.lineend;
+      streamPut(&shell->stream, (uint8_t)c);
+    }
+    // insert character
+    else {
+      memmove(&(shell->input.line[shell->inputdata.cursorpos+1]), &(shell->input.line[shell->inputdata.cursorpos]), shell->inputdata.lineend - shell->inputdata.cursorpos);
+      shell->input.line[shell->inputdata.cursorpos] = c;
+      ++shell->inputdata.lineend;
+      _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
+      ++shell->inputdata.cursorpos;
+      _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
+    }
+    return 1;
+  }
+}
+
 /**
  * @brief   Compare two characters.
  *
@@ -654,21 +739,20 @@ static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, s
     key = KEY_UNKNOWN;
 
     // parse escape sequence
-    if (shell->inputdata.escp > 0) {
-      shell->inputdata.escseq[shell->inputdata.escp] = c;
-      ++shell->inputdata.escp;
+    if (strlen(shell->inputdata.escseq) > 0) {
+      shell->inputdata.escseq[strlen(shell->inputdata.escseq)] = c;
       key = _interpreteEscapeSequence(shell->inputdata.escseq);
-      if (key == KEY_AMBIGUOUS) {
-        // read next byte to resolve ambiguity
-        continue;
-      } else {
-        /*
-         * If the escape sequence could either be parsed sucessfully
-         * or there is no match (KEY_UNKNOWN),
-         * reset the sequence variable and interprete key/character
-         */
-        shell->inputdata.escp = 0;
-        memset(shell->inputdata.escseq, '\0', sizeof(shell->inputdata.escseq)*sizeof(shell->inputdata.escseq[0]));
+      switch (key) {
+        case KEY_AMBIGUOUS:
+          // read next byte to resolve ambiguity
+          continue;
+        case KEY_UNKNOWN:
+          // do nothing here, but handle the unknown sequence below
+          break;
+        default:
+          // reset the sequence variable and buffer
+          memset(shell->inputdata.escseq, '\0', sizeof(shell->inputdata.escseq)*sizeof(shell->inputdata.escseq[0]));
+          break;
       }
     }
 
@@ -678,7 +762,7 @@ static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, s
       action = AOS_SHELL_ACTION_NONE;
 
       // printable character
-      if (key == KEY_UNKNOWN && c >= '\x20' && c <= '\x7E') {
+      if (key == KEY_UNKNOWN && strlen(shell->inputdata.escseq) == 0 && c >= '\x20' && c <= '\x7E') {
         action = AOS_SHELL_ACTION_READCHAR;
       }
 
@@ -716,16 +800,16 @@ static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, s
         }
       }
 
-      // 'page up' of 'arrow up' key
-      else if (key == KEY_PAGE_UP || key == KEY_ARROW_UP) {
+      // 'page up' key or CTRL + 'arrow up' key combination
+      else if (key == KEY_PAGE_UP || key == KEY_CTRL_ARROW_UP) {
         // ignore if there was some input
         if (shell->inputdata.noinput) {
           action = AOS_SHELL_ACTION_RECALLLAST;
         }
       }
 
-      // 'page down' key, 'arrow done' key, 'end of test' character or 'end of transmission' character
-      else if (key == KEY_PAGE_DOWN || key == KEY_ARROW_DOWN || c == '\x03' || c == '\x03') {
+      // 'page down' key, 'arrow down' key, 'end of test' character or 'end of transmission' character, or CTRL + 'arrow down' key combination
+      else if (key == KEY_PAGE_DOWN || key == KEY_ARROW_DOWN || c == '\x03' || c == '\x03' || key == KEY_CTRL_ARROW_DOWN) {
         // ignore if line is empty
         if (shell->inputdata.lineend > 0) {
           action = AOS_SHELL_ACTION_CLEAR;
@@ -758,12 +842,28 @@ static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, s
 
       // 'arrow right' key
       else if (key == KEY_ARROW_RIGHT) {
-        // irgnore if cursor is very right
+        // ignore if cursor is very right
         if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
           action = AOS_SHELL_ACTION_CURSORRIGHT;
         }
       }
 
+      // CTRL + 'arrow left' key combination
+      else if (key == KEY_CTRL_ARROW_LEFT) {
+        // ignore if cursor is very left
+        if (shell->inputdata.cursorpos > 0) {
+          action = AOS_SHELL_ACTION_CURSORWORDLEFT;
+        }
+      }
+
+      // CTRL + 'arrow right' key combination
+      else if (key == KEY_CTRL_ARROW_RIGHT) {
+        // ignore if cursor is very right
+        if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
+          action = AOS_SHELL_ACTION_CURSORWORDRIGHT;
+        }
+      }
+
       // carriage return ('\r') or line feed ('\n') character
       else if (c == '\x0D' || c == '\x0A') {
         action = AOS_SHELL_ACTION_EXECUTE;
@@ -773,50 +873,31 @@ static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, s
       else if (key == KEY_ESCAPE || c == '\x1B') {
         action = AOS_SHELL_ACTION_ESCSTART;
       }
+
+      // unknown escape sequence
+      else if (key == KEY_UNKNOWN && strlen(shell->inputdata.escseq) > 0) {
+        action = AOS_SHELL_ACTION_PRINTUNKNOWNSEQUENCE;
+      }
     }
 
     /* handle function */
     switch (action) {
       case AOS_SHELL_ACTION_READCHAR:
       {
-        // line is full
-        if (shell->inputdata.lineend + 1 >= shell->linesize) {
+        if (_readChar(shell, c) == 0) {
+          // line is full
           _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
           chprintf((BaseSequentialStream*)&shell->stream, "\n\tmaximum line width reached\n");
           _printPrompt(shell);
           _printLine(shell, 0, shell->inputdata.lineend);
           _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
         }
-        // read character
-        else {
-          // clear old line content on first input
-          if (shell->inputdata.noinput) {
-            memset(shell->line, '\0', shell->linesize);
-            shell->inputdata.noinput = false;
-          }
-          // overwrite content
-          if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
-            shell->line[shell->inputdata.cursorpos] = c;
-            ++shell->inputdata.cursorpos;
-            shell->inputdata.lineend = (shell->inputdata.cursorpos > shell->inputdata.lineend) ? shell->inputdata.cursorpos : shell->inputdata.lineend;
-            streamPut(&shell->stream, (uint8_t)c);
-          }
-          // insert character
-          else {
-            memmove(&(shell->line[shell->inputdata.cursorpos+1]), &(shell->line[shell->inputdata.cursorpos]), shell->inputdata.lineend - shell->inputdata.cursorpos);
-            shell->line[shell->inputdata.cursorpos] = c;
-            ++shell->inputdata.lineend;
-            _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
-            ++shell->inputdata.cursorpos;
-            _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
-          }
-        }
         break;
       }
 
       case AOS_SHELL_ACTION_AUTOFILL:
       {
-        const char* fill = shell->line;
+        const char* fill = shell->input.line;
         size_t cmatch = shell->inputdata.cursorpos;
         charmatch_t matchlevel = CHAR_MATCH_NOT;
         size_t n;
@@ -837,7 +918,7 @@ static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, s
             fill = cmd->name;
             // break the loop only if there are no case mismatches with the input
             n = shell->inputdata.cursorpos;
-            _strccmp(fill, shell->line, false, &n, &mlvl);
+            _strccmp(fill, shell->input.line, false, &n, &mlvl);
             if (mlvl == CHAR_MATCH_CASE) {
               break;
             }
@@ -845,7 +926,7 @@ static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, s
           // if a not exact match was found
           else if (cmatch + cmp > shell->inputdata.cursorpos) {
             // if this is the first one
-            if (fill == shell->line) {
+            if (fill == shell->input.line) {
               cmatch += cmp;
               fill = cmd->name;
             }
@@ -859,26 +940,26 @@ static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, s
         }
         // evaluate if there are case mismatches
         n = cmatch;
-        _strccmp(shell->line, fill, shell->config & AOS_SHELL_CONFIG_MATCH_CASE, &n, &matchlevel);
+        _strccmp(shell->input.line, fill, shell->config & AOS_SHELL_CONFIG_MATCH_CASE, &n, &matchlevel);
         // print the auto fill if any
         if (cmatch > shell->inputdata.cursorpos || (cmatch == shell->inputdata.cursorpos && matchlevel == CHAR_MATCH_NCASE)) {
           shell->inputdata.noinput = false;
           // limit auto fill so it will not overflow the line width
-          if (shell->inputdata.lineend + (cmatch - shell->inputdata.cursorpos) > shell->linesize) {
-            cmatch = shell->linesize - shell->inputdata.lineend + shell->inputdata.cursorpos;
+          if (shell->inputdata.lineend + (cmatch - shell->inputdata.cursorpos) > shell->input.width) {
+            cmatch = shell->input.width - shell->inputdata.lineend + shell->inputdata.cursorpos;
           }
           // move trailing memory further in the line
-          memmove(&(shell->line[cmatch]), &(shell->line[shell->inputdata.cursorpos]), shell->inputdata.lineend - shell->inputdata.cursorpos);
+          memmove(&(shell->input.line[cmatch]), &(shell->input.line[shell->inputdata.cursorpos]), shell->inputdata.lineend - shell->inputdata.cursorpos);
           shell->inputdata.lineend += cmatch - shell->inputdata.cursorpos;
           // if there was no incorrect case when matching
           if (matchlevel == CHAR_MATCH_CASE) {
             // insert fill command name to line
-            memcpy(&(shell->line[shell->inputdata.cursorpos]), &(fill[shell->inputdata.cursorpos]), cmatch - shell->inputdata.cursorpos);
+            memcpy(&(shell->input.line[shell->inputdata.cursorpos]), &(fill[shell->inputdata.cursorpos]), cmatch - shell->inputdata.cursorpos);
             // print the output
             _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
           } else {
             // overwrite line with fill command name
-            memcpy(shell->line, fill, cmatch);
+            memcpy(shell->input.line, fill, cmatch);
             // reprint the whole line
             _moveCursor(shell, shell->inputdata.cursorpos, 0);
             _printLine(shell, 0, shell->inputdata.lineend);
@@ -898,7 +979,7 @@ static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, s
           // compare line content with command, excpet if cursorpos=0
           size_t i = shell->inputdata.cursorpos;
           if (shell->inputdata.cursorpos > 0) {
-            _strccmp(shell->line, cmd->name, true, &i, NULL);
+            _strccmp(shell->input.line, cmd->name, true, &i, NULL);
           }
           const int cmp = (i < shell->inputdata.cursorpos) ?
                             (i - shell->inputdata.cursorpos) :
@@ -940,7 +1021,7 @@ static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, s
       case AOS_SHELL_ACTION_DELETEFORWARD:
       {
         --shell->inputdata.lineend;
-        memmove(&(shell->line[shell->inputdata.cursorpos]), &(shell->line[shell->inputdata.cursorpos+1]), shell->inputdata.lineend - shell->inputdata.cursorpos);
+        memmove(&(shell->input.line[shell->inputdata.cursorpos]), &(shell->input.line[shell->inputdata.cursorpos+1]), shell->inputdata.lineend - shell->inputdata.cursorpos);
         _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
         streamPut(&shell->stream, ' ');
         _moveCursor(shell, shell->inputdata.lineend + 1, shell->inputdata.cursorpos);
@@ -950,9 +1031,9 @@ static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, s
       case AOS_SHELL_ACTION_DELETEBACKWARD:
       {
         --shell->inputdata.cursorpos;
-        memmove(&(shell->line[shell->inputdata.cursorpos]), &(shell->line[shell->inputdata.cursorpos+1]), shell->inputdata.lineend - shell->inputdata.cursorpos);
+        memmove(&(shell->input.line[shell->inputdata.cursorpos]), &(shell->input.line[shell->inputdata.cursorpos+1]), shell->inputdata.lineend - shell->inputdata.cursorpos);
         --shell->inputdata.lineend;
-        shell->line[shell->inputdata.lineend] = '\0';
+        shell->input.line[shell->inputdata.lineend] = '\0';
         _moveCursor(shell, shell->inputdata.cursorpos + 1, shell->inputdata.cursorpos);
         _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
         streamPut(&shell->stream, ' ');
@@ -967,14 +1048,14 @@ static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, s
         size_t nul_start = 0;
         size_t nul_end = 0;
         // search line for a NUL byte
-        while (nul_start < shell->linesize) {
-          if (shell->line[nul_start] == '\0') {
+        while (nul_start < shell->input.width) {
+          if (shell->input.line[nul_start] == '\0') {
             nul_end = nul_start + 1;
             // keep searcjing for a byte that is not NUL
-            while (nul_end < shell->linesize) {
-              if (shell->line[nul_end] != '\0') {
+            while (nul_end < shell->input.width) {
+              if (shell->input.line[nul_end] != '\0') {
                 // an intermediate NUL sequence was found
-                memset(&(shell->line[nul_start]), ' ', nul_end - nul_start);
+                memset(&(shell->input.line[nul_start]), ' ', nul_end - nul_start);
                 shell->inputdata.lineend = nul_end + 1;
                 break;
               } else {
@@ -1035,14 +1116,42 @@ static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, s
         break;
       }
 
+      case AOS_SHELL_ACTION_CURSORWORDLEFT:
+      {
+        size_t cpos = shell->inputdata.cursorpos;
+        while (cpos > 0 && shell->input.line[cpos-1] == ' ') {
+          --cpos;
+        }
+        while (cpos > 0 && shell->input.line[cpos-1] != ' ') {
+          --cpos;
+        }
+        _moveCursor(shell, shell->inputdata.cursorpos, cpos);
+        shell->inputdata.cursorpos = cpos;
+        break;
+      }
+
+      case AOS_SHELL_ACTION_CURSORWORDRIGHT:
+      {
+        size_t cpos = shell->inputdata.cursorpos;
+        while (cpos < shell->inputdata.lineend && shell->input.line[cpos] != ' ') {
+          ++cpos;
+        }
+        while (cpos < shell->inputdata.lineend && shell->input.line[cpos] == ' ') {
+          ++cpos;
+        }
+        _moveCursor(shell, shell->inputdata.cursorpos, cpos);
+        shell->inputdata.cursorpos = cpos;
+        break;
+      }
+
       case AOS_SHELL_ACTION_EXECUTE:
       {
         streamPut(&shell->stream, '\n');
         // set the number of read bytes and return
         if (!shell->inputdata.noinput) {
-          *n = shell->linesize - shell->inputdata.lineend;
+          *n = shell->input.width - shell->inputdata.lineend;
           // fill the remainder of the line with NUL bytes
-          memset(&(shell->line[shell->inputdata.lineend]), '\0', *n);
+          memset(&(shell->input.line[shell->inputdata.lineend]), '\0', *n);
           // reset static variables
           shell->inputdata.noinput = true;
         }
@@ -1052,12 +1161,21 @@ static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, s
       case AOS_SHELL_ACTION_ESCSTART:
       {
         shell->inputdata.escseq[0] = c;
-        ++shell->inputdata.escp;
+        break;
+      }
+
+      case AOS_SHELL_ACTION_PRINTUNKNOWNSEQUENCE:
+      {
+        size_t seqc = 1; // element 0 would be unprintible ESC character
+        while (shell->inputdata.escseq[seqc] != '\0') {
+          _readChar(shell, shell->inputdata.escseq[seqc]);
+          ++seqc;
+        }
+        memset(shell->inputdata.escseq, '\0', sizeof(shell->inputdata.escseq)*sizeof(shell->inputdata.escseq[0]));
         break;
       }
 
       case AOS_SHELL_ACTION_NONE:
-      default:
       {
         // do nothing (ignore input) and read next byte
         continue;
@@ -1097,7 +1215,7 @@ static size_t _parseArguments(aos_shell_t* shell)
   size_t arg = 0;
 
   // iterate through the line
-  for (char* c = shell->line; c < shell->line + shell->linesize; ++c) {
+  for (char* c = shell->input.line; c < shell->input.line + shell->input.width; ++c) {
     // terminate at first NUL byte
     if (*c == '\0') {
       state = END;
@@ -1151,7 +1269,7 @@ static size_t _parseArguments(aos_shell_t* shell)
  * @param[in] arglist       Pointer to the argument buffer.
  * @param[in] arglistsize   Size of te argument buffer.
  */
-void aosShellInit(aos_shell_t* shell, event_source_t* oseventsource,  const char* prompt, char* line, size_t linesize, char** arglist, size_t arglistsize)
+void aosShellInit(aos_shell_t* shell, event_source_t* oseventsource, const char* prompt, char* line, size_t linesize, char** arglist, size_t arglistsize)
 {
   aosDbgCheck(shell != NULL);
   aosDbgCheck(oseventsource != NULL);
@@ -1167,10 +1285,9 @@ void aosShellInit(aos_shell_t* shell, event_source_t* oseventsource,  const char
   shell->commands = NULL;
   shell->execstatus.command = NULL;
   shell->execstatus.retval = 0;
-  shell->line = line;
-  shell->linesize = linesize;
+  shell->input.line = line;
+  shell->input.width = linesize;
   shell->inputdata.lastaction = AOS_SHELL_ACTION_NONE;
-  shell->inputdata.escp = 0;
   memset(shell->inputdata.escseq, '\0', sizeof(shell->inputdata.escseq)*sizeof(shell->inputdata.escseq[0]));
   shell->inputdata.cursorpos = 0;
   shell->inputdata.lineend = 0;
@@ -1179,8 +1296,8 @@ void aosShellInit(aos_shell_t* shell, event_source_t* oseventsource,  const char
   shell->arglistsize = arglistsize;
   shell->config = 0x00;
 
-  // initialize arrays
-  memset(shell->line, '\0', shell->linesize);
+  // initialize buffers
+  memset(shell->input.line, '\0', shell->input.width);
   for (size_t a = 0; a < shell->arglistsize; ++a) {
     shell->arglist[a] = NULL;
   }
@@ -1566,7 +1683,7 @@ void aosShellThread(void* shell)
 
               // if no matching command was found, print an error
               if (cmd == NULL) {
-                chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "%s: command not found\n", ((aos_shell_t*)shell)->arglist[0]);
+                chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\tcommand '%s' not found\n", ((aos_shell_t*)shell)->arglist[0]);
               }
             }
 
