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