readline.c
Go to the documentation of this file.
00001
00039 #include "readline.h"
00040
00041 #include <cfg/compiler.h>
00042 #include <cfg/debug.h>
00043
00044 #include <stdio.h>
00045
00047 #define DEBUG_DUMP_HISTORY    0
00048 
00050 enum RL_KEYS {
00051     SPECIAL_KEYS = 0x1000,
00052
00053     /*
00054      * Three byte keys:
00055      * #################
00056      * UpArrow:     0x1B 0x5B 0X41
00057      * DownArrow:   0x1B 0x5B 0X42
00058      * RightArrow:  0x1B 0x5B 0x43
00059      * LeftArrow:   0x1b 0x5B 0x44
00060      * Beak(Pause): 0x1b 0x5B 0x50
00061     */
00062     KEY_UP_ARROW,
00063     KEY_DOWN_ARROW,
00064     KEY_LEFT_ARROW,
00065     KEY_RIGHT_ARROW,
00066     KEY_PAUSE,
00067
00068     /*
00069      * Four byte keys:
00070      * ################
00071      * F1:          0x1b 0x5B 0x5B 0x41
00072      * F2:          0x1b 0x5B 0x5B 0x42
00073      * F3:          0x1b 0x5B 0x5B 0x43
00074      * F4:          0x1b 0x5B 0x5B 0x44
00075      * F5:          0x1b 0x5B 0x5B 0x45
00076      * Ins:         0x1b 0x5B 0x32 0x7E
00077      * Home:        0x1b 0x5B 0x31 0x7E
00078      * PgUp:        0x1b 0x5B 0x35 0x7E
00079      * Del:         0x1b 0x5B 0x33 0x7E
00080      * End:         0x1b 0x5B 0x34 0x7E
00081      * PgDn:        0x1b 0x5B 0x36 0x7E
00082      */
00083     KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5,
00084     KEY_INS, KEY_HOME, KEY_PGUP, KEY_DEL, KEY_END, KEY_PGDN,
00085
00086     /*
00087      * Five byte keys:
00088      * ################
00089      * F6:          0x1b 0x5B 0x31 0x37 0x7E
00090      * F7:          0x1b 0x5B 0x31 0x38 0x7E
00091      * F8:          0x1b 0x5B 0x31 0x39 0x7E
00092      * F9:          0x1b 0x5B 0x32 0x30 0x7E
00093      * F10:         0x1b 0x5B 0x32 0x31 0x7E
00094      * F11:         0x1b 0x5B 0x32 0x33 0x7E
00095      * F12:         0x1b 0x5B 0x32 0x34 0x7E
00096      */
00097     KEY_F6, KEY_F7, KEY_F8, KEY_F9,
00098     KEY_F10, KEY_F11, KEY_F12,
00099 };
00100
00104 #define IS_WORD_SEPARATOR(c) ((c) == ' ' || (c) == '\0')
00105 
00107 INLINE void rl_puts(const struct RLContext* ctx, const char* txt)
00108 {
00109     if (!ctx->put)
00110         return;
00111
00112     while (*txt)
00113         ctx->put(*txt++, ctx->put_param);
00114 }
00115
00117 INLINE void rl_putc(const struct RLContext* ctx, char ch)
00118 {
00119     if (ctx->put)
00120         ctx->put(ch, ctx->put_param);
00121 }
00122
00128 static bool rl_getc(const struct RLContext* ctx, int* ch)
00129 {
00130     int c = ctx->get(ctx->get_param);
00131
00132     if (c == EOF)
00133     {
00134         if (ctx->clear)
00135             ctx->clear(ctx->clear_param);
00136
00137         return false;
00138     }
00139
00140     if (c == 0x1B)
00141     {
00142         // Unknown ESC sequence. Ignore it and read
00143         //  return next character.
00144         if (ctx->get(ctx->get_param) != 0x5B)
00145             return rl_getc(ctx, ch);
00146
00147         /*
00148          * To be added:
00149          * Home:        0x1b 0x5B 0x31 0x7E
00150          * F6:          0x1b 0x5B 0x31 0x37 0x7E
00151          * F7:          0x1b 0x5B 0x31 0x38 0x7E
00152          * F8:          0x1b 0x5B 0x31 0x39 0x7E
00153          * Ins:         0x1b 0x5B 0x32 0x7E
00154          * F9:          0x1b 0x5B 0x32 0x30 0x7E
00155          * F10:         0x1b 0x5B 0x32 0x31 0x7E
00156          * F11:         0x1b 0x5B 0x32 0x33 0x7E
00157          * F12:         0x1b 0x5B 0x32 0x34 0x7E
00158          * Del:         0x1b 0x5B 0x33 0x7E
00159          * End:         0x1b 0x5B 0x34 0x7E
00160          * PgUp:        0x1b 0x5B 0x35 0x7E
00161          * PgDn:        0x1b 0x5B 0x36 0x7E
00162          */
00163
00164         c = ctx->get(ctx->get_param);
00165         switch (c)
00166         {
00167         case 0x41: c = KEY_UP_ARROW; break;
00168         case 0x42: c = KEY_DOWN_ARROW; break;
00169         case 0x43: c = KEY_RIGHT_ARROW; break;
00170         case 0x44: c = KEY_LEFT_ARROW; break;
00171         case 0x50: c = KEY_PAUSE; break;
00172         case 0x5B:
00173             c = ctx->get(ctx->get_param);
00174             switch (c)
00175             {
00176             case 0x41: c = KEY_F1; break;
00177             case 0x42: c = KEY_F2; break;
00178             case 0x43: c = KEY_F3; break;
00179             case 0x44: c = KEY_F4; break;
00180             case 0x45: c = KEY_F5; break;
00181             default: return rl_getc(ctx, ch);
00182             }
00183             break;
00184         default: return rl_getc(ctx, ch);
00185         }
00186     }
00187
00188     *ch = c;
00189     return true;
00190 }
00191
00192 INLINE void beep(struct RLContext* ctx)
00193 {
00194     rl_putc(ctx, '\a');
00195 }
00196
00197 static bool pop_history(struct RLContext* ctx, int total_len)
00198 {
00199     // Compute the length of the first command (including terminator).
00200     int len = strlen(ctx->real_history+1)+1;
00201
00202     // (the first byte of the history should always be 0)
00203     ASSERT(ctx->real_history[0] == '\0');
00204
00205     // If it is the only one in the history, do nothing
00206     if (len == total_len)
00207         return false;
00208
00209     // Overwrite the first command with the second one
00210     memmove(ctx->real_history, ctx->real_history+len, HISTORY_SIZE-len);
00211
00212     // Move back the ctx->buffer pointer so that all the indices are still valid
00213     ctx->history -= len;
00214
00215     return true;
00216 }
00217
00219 INLINE bool is_history_begin(struct RLContext* ctx, int i)
00220 { return ctx->history + i == ctx->real_history; }
00221
00223 INLINE bool is_history_end(struct RLContext* ctx, int i)
00224 { return ctx->history + i == ctx->real_history + HISTORY_SIZE; }
00225
00227 INLINE bool is_history_past_end(struct RLContext* ctx, int i)
00228 { return ctx->history + i >= ctx->real_history + HISTORY_SIZE; }
00229
00239 static bool insert_chars(struct RLContext* ctx, size_t *curpos, const char* ch, int num_chars)
00240 {
00241     ASSERT(!is_history_past_end(ctx, *curpos));
00242
00243     while (is_history_past_end(ctx, *curpos+num_chars+1))
00244     {
00245         if (!pop_history(ctx, *curpos))
00246             return false;
00247     }
00248
00249     while (num_chars--)
00250         ctx->history[++(*curpos)] = *ch++;
00251
00252     ASSERT(!is_history_past_end(ctx, *curpos + 1));
00253     ctx->history[*curpos+1] = '\0';
00254     return true;
00255 }
00256
00258 static bool insert_char(struct RLContext* ctx, size_t *curpos, char ch)
00259 {
00260     return insert_chars(ctx, curpos, &ch, 1);
00261 }
00262
00263 #if DEBUG_DUMP_HISTORY
00264 
00265 static void dump_history(struct RLContext* ctx)
00266 {
00267     int k;
00268     char buf[8];
00269     ASSERT(ctx->real_history[0] == '\0');
00270     rl_puts(ctx, "History dump:");
00271     rl_puts(ctx, "\r\n");
00272     for (k = 1;
00273          ctx->real_history + k != ctx->history + ctx->history_pos + 1;
00274          k += strlen(&ctx->real_history[k]) + 1)
00275     {
00276         rl_puts(ctx, &ctx->real_history[k]);
00277         rl_puts(ctx, "\r\n");
00278     }
00279
00280     sprintf(buf, "%d\r\n", ctx->history_pos + (ctx->history - ctx->real_history));
00281     rl_puts(ctx, buf);
00282 }
00283 #endif /* DEBUG_DUMP_HISTORY */
00284
00286 static bool complete_word(struct RLContext *ctx, size_t *curpos)
00287 {
00288     const char* completed_word;
00289     size_t wstart;
00290
00291     // If the current character is a separator,
00292     //  there is nothing to complete
00293     wstart = *curpos;
00294     if (IS_WORD_SEPARATOR(ctx->history[wstart]))
00295     {
00296         beep(ctx);
00297         return false;
00298     }
00299
00300     // Find the separator before the current word
00301     do
00302         --wstart;
00303     while (!IS_WORD_SEPARATOR(ctx->history[wstart]));
00304
00305     // Complete the word through the hook
00306     completed_word = ctx->match(ctx->match_param, ctx->history + wstart + 1, *curpos - wstart);
00307     if (!completed_word)
00308         return false;
00309
00310     // Move back the terminal cursor to the separator
00311     while (*curpos != wstart)
00312     {
00313         rl_putc(ctx, '\b');
00314         --*curpos;
00315     }
00316
00317     // Insert the completed command
00318     insert_chars(ctx, curpos, completed_word, strlen(completed_word));
00319     rl_puts(ctx, completed_word);
00320     insert_char(ctx, curpos, ' ');
00321     rl_putc(ctx, ' ');
00322
00323     return true;
00324 }
00325
00326 void rl_refresh(struct RLContext* ctx)
00327 {
00328     rl_puts(ctx, "\r\n");
00329     if (ctx->prompt)
00330         rl_puts(ctx, ctx->prompt);
00331     rl_puts(ctx, ctx->history + ctx->history_pos + 1);
00332 }
00333
00334 const char* rl_readline(struct RLContext* ctx)
00335 {
00336     while (1)
00337     {
00338         char ch;
00339         int c;
00340
00341         ASSERT(ctx->history - ctx->real_history + ctx->line_pos < HISTORY_SIZE);
00342
00343         if (!rl_getc(ctx, &c))
00344             return NULL;
00345
00346         // Just ignore special keys for now
00347         if (c > SPECIAL_KEYS)
00348             continue;
00349
00350         if (c == '\t')
00351         {
00352             // Ask the match hook if available
00353             if (!ctx->match)
00354                 return NULL;
00355
00356             complete_word(ctx, &ctx->line_pos);
00357             continue;
00358         }
00359
00360         // Backspace cancels a character, or it is ignored if at
00361         // the start of the line
00362         if (c == '\b')
00363         {
00364             if (ctx->history[ctx->line_pos] != '\0')
00365             {
00366                 --ctx->line_pos;
00367                 rl_puts(ctx, "\b \b");
00368             }
00369             continue;
00370         }
00371
00372         if (c == '\r' || c == '\n')
00373         {
00374             rl_puts(ctx, "\r\n");
00375             break;
00376         }
00377
00378
00379         // Add a character to the buffer, if possible
00380         ch = (char)c;
00381         ASSERT2(ch == c, "a special key was not properly handled");
00382         if (insert_chars(ctx, &ctx->line_pos, &ch, 1))
00383             rl_putc(ctx, ch);
00384         else
00385             beep(ctx);
00386     }
00387
00388     ctx->history_pos = ctx->line_pos + 1;
00389     while (ctx->history[ctx->line_pos] != '\0')
00390         --ctx->line_pos;
00391
00392     // Do not store empty lines in the history
00393     if (ctx->line_pos == ctx->history_pos - 1)
00394         ctx->history_pos -= 1;
00395
00396 #if DEBUG_DUMP_HISTORY
00397     dump_history(ctx);
00398 #endif
00399 
00400     const char *buf = &ctx->history[ctx->line_pos + 1];
00401
00402     ctx->line_pos = ctx->history_pos;
00403
00404     insert_chars(ctx, &ctx->line_pos, NULL, 0);
00405
00406     // Since the current pointer now points to the separator, we need
00407     //  to return the first character
00408     return buf;
00409 }
00410