BeRTOS
readline.c
Go to the documentation of this file.
00001 
00070 #include "readline.h"
00071 
00072 #include <cfg/compiler.h>
00073 #include <cfg/debug.h>
00074 
00075 #include <stdio.h>
00076 
00078 #define DEBUG_UNIT_TEST       0
00079 
00081 #define DEBUG_DUMP_HISTORY    0
00082 
00083 
00085 enum RL_KEYS {
00086     SPECIAL_KEYS = 0x1000,
00087 
00088     /*
00089      * Three byte keys:
00090      * #################
00091      * UpArrow:     0x1B 0x5B 0X41
00092      * DownArrow:   0x1B 0x5B 0X42
00093      * RightArrow:  0x1B 0x5B 0x43
00094      * LeftArrow:   0x1b 0x5B 0x44
00095      * Beak(Pause): 0x1b 0x5B 0x50
00096     */
00097     KEY_UP_ARROW,
00098     KEY_DOWN_ARROW,
00099     KEY_LEFT_ARROW,
00100     KEY_RIGHT_ARROW,
00101     KEY_PAUSE,
00102 
00103     /*
00104      * Four byte keys:
00105      * ################
00106      * F1:          0x1b 0x5B 0x5B 0x41
00107      * F2:          0x1b 0x5B 0x5B 0x42
00108      * F3:          0x1b 0x5B 0x5B 0x43
00109      * F4:          0x1b 0x5B 0x5B 0x44
00110      * F5:          0x1b 0x5B 0x5B 0x45
00111      * Ins:         0x1b 0x5B 0x32 0x7E
00112      * Home:        0x1b 0x5B 0x31 0x7E
00113      * PgUp:        0x1b 0x5B 0x35 0x7E
00114      * Del:         0x1b 0x5B 0x33 0x7E
00115      * End:         0x1b 0x5B 0x34 0x7E
00116      * PgDn:        0x1b 0x5B 0x36 0x7E
00117      */
00118     KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5,
00119     KEY_INS, KEY_HOME, KEY_PGUP, KEY_DEL, KEY_END, KEY_PGDN,
00120 
00121     /*
00122      * Five byte keys:
00123      * ################
00124      * F6:          0x1b 0x5B 0x31 0x37 0x7E
00125      * F7:          0x1b 0x5B 0x31 0x38 0x7E
00126      * F8:          0x1b 0x5B 0x31 0x39 0x7E
00127      * F9:          0x1b 0x5B 0x32 0x30 0x7E
00128      * F10:         0x1b 0x5B 0x32 0x31 0x7E
00129      * F11:         0x1b 0x5B 0x32 0x33 0x7E
00130      * F12:         0x1b 0x5B 0x32 0x34 0x7E
00131      */
00132     KEY_F6, KEY_F7, KEY_F8, KEY_F9,
00133     KEY_F10, KEY_F11, KEY_F12,
00134 };
00135 
00139 #define IS_WORD_SEPARATOR(c) ((c) == ' ' || (c) == '\0')
00140 
00142 INLINE void rl_puts(const struct RLContext* ctx, const char* txt)
00143 {
00144     if (!ctx->put)
00145         return;
00146 
00147     while (*txt)
00148         ctx->put(*txt++, ctx->put_param);
00149 }
00150 
00152 INLINE void rl_putc(const struct RLContext* ctx, char ch)
00153 {
00154     if (ctx->put)
00155         ctx->put(ch, ctx->put_param);
00156 }
00157 
00162 static bool rl_getc(const struct RLContext* ctx, int* ch)
00163 {
00164     int c = ctx->get(ctx->get_param);
00165 
00166     if (c == EOF)
00167     {
00168         if (ctx->clear)
00169             ctx->clear(ctx->clear_param);
00170 
00171         return false;
00172     }
00173 
00174     if (c == 0x1B)
00175     {
00176         // Unknown ESC sequence. Ignore it and read
00177         //  return next character.
00178         if (ctx->get(ctx->get_param) != 0x5B)
00179             return rl_getc(ctx, ch);
00180 
00181         /* To be added:
00182             * Home:        0x1b 0x5B 0x31 0x7E
00183             * F6:          0x1b 0x5B 0x31 0x37 0x7E
00184             * F7:          0x1b 0x5B 0x31 0x38 0x7E
00185             * F8:          0x1b 0x5B 0x31 0x39 0x7E
00186             * Ins:         0x1b 0x5B 0x32 0x7E
00187             * F9:          0x1b 0x5B 0x32 0x30 0x7E
00188             * F10:         0x1b 0x5B 0x32 0x31 0x7E
00189             * F11:         0x1b 0x5B 0x32 0x33 0x7E
00190             * F12:         0x1b 0x5B 0x32 0x34 0x7E
00191             * Del:         0x1b 0x5B 0x33 0x7E
00192             * End:         0x1b 0x5B 0x34 0x7E
00193             * PgUp:        0x1b 0x5B 0x35 0x7E
00194             * PgDn:        0x1b 0x5B 0x36 0x7E
00195         */
00196 
00197         c = ctx->get(ctx->get_param);
00198         switch (c)
00199         {
00200         case 0x41: c = KEY_UP_ARROW; break;
00201         case 0x42: c = KEY_DOWN_ARROW; break;
00202         case 0x43: c = KEY_RIGHT_ARROW; break;
00203         case 0x44: c = KEY_LEFT_ARROW; break;
00204         case 0x50: c = KEY_PAUSE; break;
00205         case 0x5B:
00206             c = ctx->get(ctx->get_param);
00207             switch (c)
00208             {
00209             case 0x41: c = KEY_F1; break;
00210             case 0x42: c = KEY_F2; break;
00211             case 0x43: c = KEY_F3; break;
00212             case 0x44: c = KEY_F4; break;
00213             case 0x45: c = KEY_F5; break;
00214             default: return rl_getc(ctx, ch);
00215             }
00216             break;
00217         default: return rl_getc(ctx, ch);
00218         }
00219     }
00220 
00221     *ch = c;
00222     return true;
00223 }
00224 
00225 INLINE void beep(struct RLContext* ctx)
00226 {
00227     rl_putc(ctx, '\a');
00228 }
00229 
00230 static bool pop_history(struct RLContext* ctx, int total_len)
00231 {
00232     // Compute the length of the first command (including terminator).
00233     int len = strlen(ctx->real_history+1)+1;
00234 
00235     // (the first byte of the history should always be 0)
00236     ASSERT(ctx->real_history[0] == '\0');
00237 
00238     // If it is the only one in the history, do nothing
00239     if (len == total_len)
00240         return false;
00241 
00242     // Overwrite the first command with the second one
00243     memmove(ctx->real_history, ctx->real_history+len, HISTORY_SIZE-len);
00244 
00245     // Move back the ctx->buffer pointer so that all the indices are still valid
00246     ctx->history -= len;
00247 
00248     return true;
00249 }
00250 
00252 INLINE bool is_history_begin(struct RLContext* ctx, int i)
00253 { return ctx->history + i == ctx->real_history; }
00254 
00256 INLINE bool is_history_end(struct RLContext* ctx, int i)
00257 { return ctx->history + i == ctx->real_history + HISTORY_SIZE; }
00258 
00260 INLINE bool is_history_past_end(struct RLContext* ctx, int i)
00261 { return ctx->history + i >= ctx->real_history + HISTORY_SIZE; }
00262 
00270 static bool insert_chars(struct RLContext* ctx, size_t *curpos, const char* ch, int num_chars)
00271 {
00272     ASSERT(!is_history_past_end(ctx, *curpos));
00273 
00274     while (is_history_past_end(ctx, *curpos+num_chars+1))
00275     {
00276         if (!pop_history(ctx, *curpos))
00277             return false;
00278     }
00279 
00280     while (num_chars--)
00281         ctx->history[++(*curpos)] = *ch++;
00282 
00283     ASSERT(!is_history_past_end(ctx, *curpos + 1));
00284     ctx->history[*curpos+1] = '\0';
00285     return true;
00286 }
00287 
00289 static bool insert_char(struct RLContext* ctx, size_t *curpos, char ch)
00290 {
00291     return insert_chars(ctx, curpos, &ch, 1);
00292 }
00293 
00294 #if DEBUG_DUMP_HISTORY
00295 
00296 static void dump_history(struct RLContext* ctx)
00297 {
00298     int k;
00299     char buf[8];
00300     ASSERT(ctx->real_history[0] == '\0');
00301     rl_puts(ctx, "History dump:");
00302     rl_puts(ctx, "\r\n");
00303     for (k = 1;
00304          ctx->real_history + k != ctx->history + ctx->history_pos + 1;
00305          k += strlen(&ctx->real_history[k]) + 1)
00306     {
00307         rl_puts(ctx, &ctx->real_history[k]);
00308         rl_puts(ctx, "\r\n");
00309     }
00310 
00311     sprintf(buf, "%d\r\n", ctx->history_pos + (ctx->history - ctx->real_history));
00312     rl_puts(ctx, buf);
00313 }
00314 #endif /* DEBUG_DUMP_HISTORY */
00315 
00317 static bool complete_word(struct RLContext *ctx, size_t *curpos)
00318 {
00319     const char* completed_word;
00320     size_t wstart;
00321 
00322     // If the current character is a separator,
00323     //  there is nothing to complete
00324     wstart = *curpos;
00325     if (IS_WORD_SEPARATOR(ctx->history[wstart]))
00326     {
00327         beep(ctx);
00328         return false;
00329     }
00330 
00331     // Find the separator before the current word
00332     do
00333         --wstart;
00334     while (!IS_WORD_SEPARATOR(ctx->history[wstart]));
00335 
00336     // Complete the word through the hook
00337     completed_word = ctx->match(ctx->match_param, ctx->history + wstart + 1, *curpos - wstart);
00338     if (!completed_word)
00339         return false;
00340 
00341     // Move back the terminal cursor to the separator
00342     while (*curpos != wstart)
00343     {
00344         rl_putc(ctx, '\b');
00345         --*curpos;
00346     }
00347 
00348     // Insert the completed command
00349     insert_chars(ctx, curpos, completed_word, strlen(completed_word));
00350     rl_puts(ctx, completed_word);
00351     insert_char(ctx, curpos, ' ');
00352     rl_putc(ctx, ' ');
00353 
00354     return true;
00355 }
00356 
00357 void rl_refresh(struct RLContext* ctx)
00358 {
00359     rl_puts(ctx, "\r\n");
00360     if (ctx->prompt)
00361         rl_puts(ctx, ctx->prompt);
00362     rl_puts(ctx, ctx->history + ctx->history_pos + 1);
00363 }
00364 
00365 const char* rl_readline(struct RLContext* ctx)
00366 {
00367     while (1)
00368     {
00369         char ch;
00370         int c;
00371 
00372         ASSERT(ctx->history - ctx->real_history + ctx->line_pos < HISTORY_SIZE);
00373 
00374         if (!rl_getc(ctx, &c))
00375             return NULL;
00376 
00377         // Just ignore special keys for now
00378         if (c > SPECIAL_KEYS)
00379             continue;
00380 
00381         if (c == '\t')
00382         {
00383             // Ask the match hook if available
00384             if (!ctx->match)
00385                 return NULL;
00386 
00387             complete_word(ctx, &ctx->line_pos);
00388             continue;
00389         }
00390 
00391         // Backspace cancels a character, or it is ignored if at
00392         //  the start of the line
00393         if (c == '\b')
00394         {
00395             if (ctx->history[ctx->line_pos] != '\0')
00396             {
00397                 --ctx->line_pos;
00398                 rl_puts(ctx, "\b \b");
00399             }
00400             continue;
00401         }
00402 
00403         if (c == '\r' || c == '\n')
00404         {
00405             rl_puts(ctx, "\r\n");
00406             break;
00407         }
00408 
00409 
00410         // Add a character to the buffer, if possible
00411         ch = (char)c;
00412         ASSERT2(ch == c, "a special key was not properly handled");
00413         if (insert_chars(ctx, &ctx->line_pos, &ch, 1))
00414             rl_putc(ctx, ch);
00415         else
00416             beep(ctx);
00417     }
00418 
00419     ctx->history_pos = ctx->line_pos + 1;
00420     while (ctx->history[ctx->line_pos] != '\0')
00421         --ctx->line_pos;
00422 
00423     // Do not store empty lines in the history
00424     if (ctx->line_pos == ctx->history_pos - 1)
00425         ctx->history_pos -= 1;
00426 
00427 #if DEBUG_DUMP_HISTORY
00428     dump_history(ctx);
00429 #endif
00430 
00431     const char *buf = &ctx->history[ctx->line_pos + 1];
00432 
00433     ctx->line_pos = ctx->history_pos;
00434 
00435     if (ctx->prompt)
00436         rl_puts(ctx, ctx->prompt);
00437 
00438     insert_chars(ctx, &ctx->line_pos, NULL, 0);
00439 
00440     // Since the current pointer now points to the separator, we need
00441     //  to return the first character
00442     return buf;
00443 }
00444 
00445 
00446 #if DEBUG_UNIT_TEST
00447 
00449 void rl_test(void);
00450 
00451 #if HISTORY_SIZE != 32
00452     #error This test needs HISTORY_SIZE to be set at 32
00453 #endif
00454 
00455 static struct RLContext test_ctx;
00456 
00457 static char* test_getc_ptr;
00458 static int test_getc(void* data)
00459 {
00460     return *test_getc_ptr++;
00461 }
00462 
00467 static bool do_test(char* input_buffer, char* expected_history)
00468 {
00469     rl_init_ctx(&test_ctx);
00470     rl_sethook_get(&test_ctx, test_getc, NULL);
00471 
00472     test_getc_ptr = input_buffer;
00473     while (*test_getc_ptr)
00474         rl_readline(&test_ctx);
00475 
00476     if (memcmp(test_ctx.real_history, expected_history, HISTORY_SIZE) != 0)
00477     {
00478         ASSERT2(0, "history compare failed");
00479         return false;
00480     }
00481 
00482     return true;
00483 }
00484 
00485 void rl_test(void)
00486 {
00487     char* test1_in = "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\n";
00488     char test1_hist[HISTORY_SIZE] = "\0l\0m\0n\0o\0p\0q\0r\0s\0t\0u\0v\0w\0x\0y\0z";
00489 
00490     if (!do_test(test1_in, test1_hist))
00491         return;
00492 
00493     kprintf("rl_test successful\n");
00494 }
00495 
00496 #endif /* DEBUG_UNIT_TEST */
00497