X-Git-Url: https://codewiz.org/gitweb?a=blobdiff_plain;f=mware%2Freadline.c;fp=mware%2Freadline.c;h=0000000000000000000000000000000000000000;hb=791e167e053bdd9250d34a9a5ccae6ccde4d6679;hp=4c3b3a81e5eb26fb266672a42f152a09b7dd9aca;hpb=faf2f6bfd5933ff75e6cc01e3d48f9277f731d8f;p=bertos.git diff --git a/mware/readline.c b/mware/readline.c deleted file mode 100644 index 4c3b3a81..00000000 --- a/mware/readline.c +++ /dev/null @@ -1,507 +0,0 @@ -/** - * \file - * - * - * \brief Line editing support with history - * - * Rationale for basic implementation choices: - * - * \li The history is implemented storing consecutive ASCIIZ strings within an array of memory. When - * the history is full, the first (oldest) line is cancelled and the whole buffer is memmoved to - * overwrite it and make room. while this is is obviously not the fastest algorithm (which would - * require the use of a circular buffer) it is surely good enough for this module, which does not - * aim at fast performances (line editing does not require to be blazingly fast). - * - * \li The first character in the history is always \c \\0, and it is used as a guard. By 'wasting' it - * in this way, the code actually gets much simpler in that we remove many checks when moving - * backward (\c i>0 and similar). - * - * \li While editing, the current index points to the position of the buffer which contains the - * last character typed in (exactly like a stack pointer). This also allows to simplify calculations - * and to make easier using the last byte of history. - * - * \li While editing, the current line is always kept null-terminated. This is important because - * if the user press ENTER, we must have room to add a \c \\0 to terminate the line. If the line - * is as long as the whole history buffer, there would not be space for it. By always keeping the - * \c \\0 at the end, we properly ensure this without making index checks harder. - * - * \li When removing a line from the history (see \c pop_history()), instead of updating all the - * indices we have around, we move backward the pointer to the history we use. This way, we don't - * have to update anything. This means that we keep two pointers to the history: \c real_history - * always points to the physical start, while \c history is the adjusted pointer (that is - * dereference to read/write to it). - * - * \todo Use up/down to move through history The history line will be copied to the current line, - * making sure there is room for it. - * - * \version $Id$ - * - * \author Giovanni Bajo - */ - - -#include "readline.h" - -#include -#include - -#include -#include - - -/// Enable compilation of the unit test code -#define DEBUG_UNIT_TEST 0 - -/// Enable dump of the history after each line -#define DEBUG_DUMP_HISTORY 0 - - -/** Special keys (escape sequences converted to a single code) */ -enum RL_KEYS { - SPECIAL_KEYS = 0x1000, - - /* - * Three byte keys: - * ################# - * UpArrow: 0x1B 0x5B 0X41 - * DownArrow: 0x1B 0x5B 0X42 - * RightArrow: 0x1B 0x5B 0x43 - * LeftArrow: 0x1b 0x5B 0x44 - * Beak(Pause): 0x1b 0x5B 0x50 - */ - KEY_UP_ARROW, - KEY_DOWN_ARROW, - KEY_LEFT_ARROW, - KEY_RIGHT_ARROW, - KEY_PAUSE, - - /* - * Four byte keys: - * ################ - * F1: 0x1b 0x5B 0x5B 0x41 - * F2: 0x1b 0x5B 0x5B 0x42 - * F3: 0x1b 0x5B 0x5B 0x43 - * F4: 0x1b 0x5B 0x5B 0x44 - * F5: 0x1b 0x5B 0x5B 0x45 - * Ins: 0x1b 0x5B 0x32 0x7E - * Home: 0x1b 0x5B 0x31 0x7E - * PgUp: 0x1b 0x5B 0x35 0x7E - * Del: 0x1b 0x5B 0x33 0x7E - * End: 0x1b 0x5B 0x34 0x7E - * PgDn: 0x1b 0x5B 0x36 0x7E - */ - KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, - KEY_INS, KEY_HOME, KEY_PGUP, KEY_DEL, KEY_END, KEY_PGDN, - - /* - * Five byte keys: - * ################ - * F6: 0x1b 0x5B 0x31 0x37 0x7E - * F7: 0x1b 0x5B 0x31 0x38 0x7E - * F8: 0x1b 0x5B 0x31 0x39 0x7E - * F9: 0x1b 0x5B 0x32 0x30 0x7E - * F10: 0x1b 0x5B 0x32 0x31 0x7E - * F11: 0x1b 0x5B 0x32 0x33 0x7E - * F12: 0x1b 0x5B 0x32 0x34 0x7E - */ - KEY_F6, KEY_F7, KEY_F8, KEY_F9, - KEY_F10, KEY_F11, KEY_F12, -}; - -/** Check if \a c is a separator between words. - * \note Parameter \a c is evaluated multiple times - */ -#define IS_WORD_SEPARATOR(c) ((c) == ' ' || (c) == '\0') - -/// Write the string \a txt to the IO output (without any kind of termination) -INLINE void rl_puts(const struct RLContext* ctx, const char* txt) -{ - if (!ctx->put) - return; - - while (*txt) - ctx->put(*txt++, ctx->put_param); -} - -/// Write character \a ch to the IO output. -INLINE void rl_putc(const struct RLContext* ctx, char ch) -{ - if (ctx->put) - ctx->put(ch, ctx->put_param); -} - -/** Read a character from the IO into \a ch. This function also takes - * care of converting the ANSI escape sequences into one of the codes - * defined in \c RL_KEYS. - */ -static bool rl_getc(const struct RLContext* ctx, int* ch) -{ - int c = ctx->get(ctx->get_param); - - if (c == EOF) - { - if (ctx->clear) - ctx->clear(ctx->clear_param); - - return false; - } - - if (c == 0x1B) - { - // Unknown ESC sequence. Ignore it and read - // return next character. - if (ctx->get(ctx->get_param) != 0x5B) - return rl_getc(ctx, ch); - - /* To be added: - * Home: 0x1b 0x5B 0x31 0x7E - * F6: 0x1b 0x5B 0x31 0x37 0x7E - * F7: 0x1b 0x5B 0x31 0x38 0x7E - * F8: 0x1b 0x5B 0x31 0x39 0x7E - * Ins: 0x1b 0x5B 0x32 0x7E - * F9: 0x1b 0x5B 0x32 0x30 0x7E - * F10: 0x1b 0x5B 0x32 0x31 0x7E - * F11: 0x1b 0x5B 0x32 0x33 0x7E - * F12: 0x1b 0x5B 0x32 0x34 0x7E - * Del: 0x1b 0x5B 0x33 0x7E - * End: 0x1b 0x5B 0x34 0x7E - * PgUp: 0x1b 0x5B 0x35 0x7E - * PgDn: 0x1b 0x5B 0x36 0x7E - */ - - c = ctx->get(ctx->get_param); - switch (c) - { - case 0x41: c = KEY_UP_ARROW; break; - case 0x42: c = KEY_DOWN_ARROW; break; - case 0x43: c = KEY_RIGHT_ARROW; break; - case 0x44: c = KEY_LEFT_ARROW; break; - case 0x50: c = KEY_PAUSE; break; - case 0x5B: - c = ctx->get(ctx->get_param); - switch (c) - { - case 0x41: c = KEY_F1; break; - case 0x42: c = KEY_F2; break; - case 0x43: c = KEY_F3; break; - case 0x44: c = KEY_F4; break; - case 0x45: c = KEY_F5; break; - default: return rl_getc(ctx, ch); - } - break; - default: return rl_getc(ctx, ch); - } - } - - *ch = c; - return true; -} - -INLINE void beep(struct RLContext* ctx) -{ - rl_putc(ctx, '\a'); -} - -static bool pop_history(struct RLContext* ctx, int total_len) -{ - // Compute the length of the first command (including terminator). - int len = strlen(ctx->real_history+1)+1; - - // (the first byte of the history should always be 0) - ASSERT(ctx->real_history[0] == '\0'); - - // If it is the only one in the history, do nothing - if (len == total_len) - return false; - - // Overwrite the first command with the second one - memmove(ctx->real_history, ctx->real_history+len, HISTORY_SIZE-len); - - // Move back the ctx->buffer pointer so that all the indices are still valid - ctx->history -= len; - - return true; -} - -/// Check if index \a i points to the begin of the history. -INLINE bool is_history_begin(struct RLContext* ctx, int i) -{ return ctx->history + i == ctx->real_history; } - -/// Check if index \a i points to the (exclusive) end of history -INLINE bool is_history_end(struct RLContext* ctx, int i) -{ return ctx->history + i == ctx->real_history + HISTORY_SIZE; } - -/// Check if index \a i points to the (exclusive) end of history, or somewhere past the end. -INLINE bool is_history_past_end(struct RLContext* ctx, int i) -{ return ctx->history + i >= ctx->real_history + HISTORY_SIZE; } - -/** Insert \a num_chars characters from \a ch into the history buffer at the - * position indicated by \a curpos. If needed, remove old history to make room. - * Returns true if everything was successful, false if there was no room to - * add the characters. - * \note \a num_chars can be 0, in which case we just make sure the line is - * correctly zero-terminated (ASCIIZ format). - */ -static bool insert_chars(struct RLContext* ctx, size_t *curpos, const char* ch, int num_chars) -{ - ASSERT(!is_history_past_end(ctx, *curpos)); - - while (is_history_past_end(ctx, *curpos+num_chars+1)) - { - if (!pop_history(ctx, *curpos)) - return false; - } - - while (num_chars--) - ctx->history[++(*curpos)] = *ch++; - - ASSERT(!is_history_past_end(ctx, *curpos + 1)); - ctx->history[*curpos+1] = '\0'; - return true; -} - -/// Insert a single character \a ch into the buffer (with the same semantic of \c insert_chars()) -static bool insert_char(struct RLContext* ctx, size_t *curpos, char ch) -{ - return insert_chars(ctx, curpos, &ch, 1); -} - -#if DEBUG_DUMP_HISTORY -/// Dump the internal history of a context (used only for debug purposes) -static void dump_history(struct RLContext* ctx) -{ - int k; - char buf[8]; - ASSERT(ctx->real_history[0] == '\0'); - rl_puts(ctx, "History dump:"); - rl_puts(ctx, "\r\n"); - for (k = 1; - ctx->real_history + k != ctx->history + ctx->history_pos + 1; - k += strlen(&ctx->real_history[k]) + 1) - { - rl_puts(ctx, &ctx->real_history[k]); - rl_puts(ctx, "\r\n"); - } - - sprintf(buf, "%d\r\n", ctx->history_pos + (ctx->history - ctx->real_history)); - rl_puts(ctx, buf); -} -#endif /* DEBUG_DUMP_HISTORY */ - -/// Complete the current word. Return false if no unambiguous completion was found -static bool complete_word(struct RLContext *ctx, size_t *curpos) -{ - const char* completed_word; - size_t wstart; - - // If the current character is a separator, - // there is nothing to complete - wstart = *curpos; - if (IS_WORD_SEPARATOR(ctx->history[wstart])) - { - beep(ctx); - return false; - } - - // Find the separator before the current word - do - --wstart; - while (!IS_WORD_SEPARATOR(ctx->history[wstart])); - - // Complete the word through the hook - completed_word = ctx->match(ctx->match_param, ctx->history + wstart + 1, *curpos - wstart); - if (!completed_word) - return false; - - // Move back the terminal cursor to the separator - while (*curpos != wstart) - { - rl_putc(ctx, '\b'); - --*curpos; - } - - // Insert the completed command - insert_chars(ctx, curpos, completed_word, strlen(completed_word)); - rl_puts(ctx, completed_word); - insert_char(ctx, curpos, ' '); - rl_putc(ctx, ' '); - - return true; -} - -void rl_refresh(struct RLContext* ctx) -{ - rl_puts(ctx, "\r\n"); - if (ctx->prompt) - rl_puts(ctx, ctx->prompt); - rl_puts(ctx, ctx->history + ctx->history_pos + 1); -} - -const char* rl_readline(struct RLContext* ctx) -{ - size_t i = ctx->history_pos; - - if (ctx->prompt) - rl_puts(ctx, ctx->prompt); - - insert_chars(ctx, &i, NULL, 0); - - while (1) - { - char ch; - int c; - - ASSERT(ctx->history - ctx->real_history + ctx->line_pos < HISTORY_SIZE); - - if (!rl_getc(ctx, &c)) - return NULL; - - // Just ignore special keys for now - if (c > SPECIAL_KEYS) - continue; - - if (c == '\t') - { - // Ask the match hook if available - if (!ctx->match) - return false; - - complete_word(ctx, &ctx->line_pos); - continue; - } - - if (c == '\r' || c == '\n') - { - if (ctx->prompt) - rl_puts(ctx, ctx->prompt); - - // Terminate line - insert_chars(ctx, &ctx->line_pos, NULL, 0); - rl_puts(ctx, "\r\n"); - break; - } - - // Backspace cancels a character, or it is ignored if at - // the start of the line - if (c == '\b') - { - if (ctx->history[ctx->line_pos] != '\0') - { - --ctx->line_pos; - rl_puts(ctx, "\b \b"); - } - continue; - } - - // Add a character to the buffer, if possible - ch = (char)c; - ASSERT2(ch == c, "a special key was not properly handled"); - if (insert_chars(ctx, &ctx->line_pos, &ch, 1)) - { - rl_putc(ctx, ch); - } - else - { - beep(ctx); - } - } - - ctx->history_pos = ctx->line_pos + 1; - while (ctx->history[ctx->line_pos] != '\0') - --ctx->line_pos; - - // Do not store empty lines in the history - if (ctx->line_pos == ctx->history_pos - 1) - ctx->history_pos -= 1; - -#if DEBUG_DUMP_HISTORY - dump_history(ctx); -#endif - - // Since the current pointer now points to the separator, we need - // to return the first character - return &ctx->history[ctx->line_pos + 1]; -} - - -#if DEBUG_UNIT_TEST - -/** Perform the unit test for the readline library */ -void rl_test(void); - -#if HISTORY_SIZE != 32 - #error This test needs HISTORY_SIZE to be set at 32 -#endif - -static struct RLContext test_ctx; - -static char* test_getc_ptr; -static int test_getc(void* data) -{ - return *test_getc_ptr++; -} - -/** Perform a readline test. The function pipes the characters from \a input_buffer - * through the I/O to \c rl_readline(). After the whole string is sent, \c do_test() - * checks if the current history within the context match \a expected_history. - */ -static bool do_test(char* input_buffer, char* expected_history) -{ - rl_init_ctx(&test_ctx); - rl_sethook_get(&test_ctx, test_getc, NULL); - - test_getc_ptr = input_buffer; - while (*test_getc_ptr) - rl_readline(&test_ctx); - - if (memcmp(test_ctx.real_history, expected_history, HISTORY_SIZE) != 0) - { - ASSERT2(0, "history compare failed"); - return false; - } - - return true; -} - -void rl_test(void) -{ - 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"; - char test1_hist[HISTORY_SIZE] = "\0l\0m\0n\0o\0p\0q\0r\0s\0t\0u\0v\0w\0x\0y\0z"; - - if (!do_test(test1_in, test1_hist)) - return; - - kprintf("rl_test successful\n"); -} - -#endif /* DEBUG_UNIT_TEST */ -