readline.c

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