diff --git a/lib/mp-readline/readline.c b/lib/mp-readline/readline.c index ee2e8ebdae..78380e3170 100644 --- a/lib/mp-readline/readline.c +++ b/lib/mp-readline/readline.c @@ -57,190 +57,220 @@ STATIC char *str_dup_maybe(const char *str) { return s2; } -int readline(vstr_t *line, const char *prompt) { - stdout_tx_str(prompt); - int orig_line_len = line->len; - int escape_seq = ESEQ_NONE; - char escape_seq_buf[1] = {0}; - int hist_cur = -1; - int cursor_pos = orig_line_len; - for (;;) { - int c = stdin_rx_chr(); - int last_line_len = line->len; - int redraw_step_back = 0; - bool redraw_from_cursor = false; - int redraw_step_forward = 0; - if (escape_seq == ESEQ_NONE) { - if (CHAR_CTRL_A <= c && c <= CHAR_CTRL_D && vstr_len(line) == orig_line_len) { - // control character with empty line - return c; - } else if (c == CHAR_CTRL_A) { - // CTRL-A with non-empty line is go-to-start-of-line - goto home_key; - } else if (c == CHAR_CTRL_C) { - // CTRL-C with non-empty line is cancel - return c; - } else if (c == CHAR_CTRL_E) { - // CTRL-E is go-to-end-of-line - goto end_key; - } else if (c == '\r') { - // newline - stdout_tx_str("\r\n"); - if (line->len > orig_line_len && (MP_STATE_PORT(readline_hist)[0] == NULL || strcmp(MP_STATE_PORT(readline_hist)[0], line->buf + orig_line_len) != 0)) { - // a line which is not empty and different from the last one - // so update the history - char *most_recent_hist = str_dup_maybe(line->buf + orig_line_len); - if (most_recent_hist != NULL) { - for (int i = READLINE_HIST_SIZE - 1; i > 0; i--) { - MP_STATE_PORT(readline_hist)[i] = MP_STATE_PORT(readline_hist)[i - 1]; - } - MP_STATE_PORT(readline_hist)[0] = most_recent_hist; +typedef struct _readline_t { + vstr_t *line; + int orig_line_len; + int escape_seq; + int hist_cur; + int cursor_pos; + char escape_seq_buf[1]; +} readline_t; + +readline_t rl; + +int readline_process_char(int c) { + int last_line_len = rl.line->len; + int redraw_step_back = 0; + bool redraw_from_cursor = false; + int redraw_step_forward = 0; + if (rl.escape_seq == ESEQ_NONE) { + if (CHAR_CTRL_A <= c && c <= CHAR_CTRL_D && vstr_len(rl.line) == rl.orig_line_len) { + // control character with empty line + return c; + } else if (c == CHAR_CTRL_A) { + // CTRL-A with non-empty line is go-to-start-of-line + goto home_key; + } else if (c == CHAR_CTRL_C) { + // CTRL-C with non-empty line is cancel + return c; + } else if (c == CHAR_CTRL_E) { + // CTRL-E is go-to-end-of-line + goto end_key; + } else if (c == '\r') { + // newline + stdout_tx_str("\r\n"); + if (rl.line->len > rl.orig_line_len && (MP_STATE_PORT(readline_hist)[0] == NULL || strcmp(MP_STATE_PORT(readline_hist)[0], rl.line->buf + rl.orig_line_len) != 0)) { + // a line which is not empty and different from the last one + // so update the history + char *most_recent_hist = str_dup_maybe(rl.line->buf + rl.orig_line_len); + if (most_recent_hist != NULL) { + for (int i = READLINE_HIST_SIZE - 1; i > 0; i--) { + MP_STATE_PORT(readline_hist)[i] = MP_STATE_PORT(readline_hist)[i - 1]; } + MP_STATE_PORT(readline_hist)[0] = most_recent_hist; } - return 0; - } else if (c == 27) { - // escape sequence - escape_seq = ESEQ_ESC; - } else if (c == 8 || c == 127) { - // backspace/delete - if (cursor_pos > orig_line_len) { - vstr_cut_out_bytes(line, cursor_pos - 1, 1); - // set redraw parameters - redraw_step_back = 1; - redraw_from_cursor = true; - } - } else if (32 <= c && c <= 126) { - // printable character - vstr_ins_char(line, cursor_pos, c); + } + return 0; + } else if (c == 27) { + // escape sequence + rl.escape_seq = ESEQ_ESC; + } else if (c == 8 || c == 127) { + // backspace/delete + if (rl.cursor_pos > rl.orig_line_len) { + vstr_cut_out_bytes(rl.line, rl.cursor_pos - 1, 1); // set redraw parameters + redraw_step_back = 1; redraw_from_cursor = true; - redraw_step_forward = 1; } - } else if (escape_seq == ESEQ_ESC) { - switch (c) { - case '[': - escape_seq = ESEQ_ESC_BRACKET; - break; - case 'O': - escape_seq = ESEQ_ESC_O; - break; - default: - DEBUG_printf("(ESC %d)", c); - escape_seq = ESEQ_NONE; - } - } else if (escape_seq == ESEQ_ESC_BRACKET) { - if ('0' <= c && c <= '9') { - escape_seq = ESEQ_ESC_BRACKET_DIGIT; - escape_seq_buf[0] = c; - } else { - escape_seq = ESEQ_NONE; - if (c == 'A') { - // up arrow - if (hist_cur + 1 < READLINE_HIST_SIZE && MP_STATE_PORT(readline_hist)[hist_cur + 1] != NULL) { - // increase hist num - hist_cur += 1; - // set line to history - line->len = orig_line_len; - vstr_add_str(line, MP_STATE_PORT(readline_hist)[hist_cur]); - // set redraw parameters - redraw_step_back = cursor_pos - orig_line_len; - redraw_from_cursor = true; - redraw_step_forward = line->len - orig_line_len; - } - } else if (c == 'B') { - // down arrow - if (hist_cur >= 0) { - // decrease hist num - hist_cur -= 1; - // set line to history - vstr_cut_tail_bytes(line, line->len - orig_line_len); - if (hist_cur >= 0) { - vstr_add_str(line, MP_STATE_PORT(readline_hist)[hist_cur]); - } - // set redraw parameters - redraw_step_back = cursor_pos - orig_line_len; - redraw_from_cursor = true; - redraw_step_forward = line->len - orig_line_len; - } - } else if (c == 'C') { - // right arrow - if (cursor_pos < line->len) { - redraw_step_forward = 1; - } - } else if (c == 'D') { - // left arrow - if (cursor_pos > orig_line_len) { - redraw_step_back = 1; - } - } else if (c == 'H') { - // home - goto home_key; - } else if (c == 'F') { - // end - goto end_key; - } else { - DEBUG_printf("(ESC [ %d)", c); + } else if (32 <= c && c <= 126) { + // printable character + vstr_ins_char(rl.line, rl.cursor_pos, c); + // set redraw parameters + redraw_from_cursor = true; + redraw_step_forward = 1; + } + } else if (rl.escape_seq == ESEQ_ESC) { + switch (c) { + case '[': + rl.escape_seq = ESEQ_ESC_BRACKET; + break; + case 'O': + rl.escape_seq = ESEQ_ESC_O; + break; + default: + DEBUG_printf("(ESC %d)", c); + rl.escape_seq = ESEQ_NONE; + } + } else if (rl.escape_seq == ESEQ_ESC_BRACKET) { + if ('0' <= c && c <= '9') { + rl.escape_seq = ESEQ_ESC_BRACKET_DIGIT; + rl.escape_seq_buf[0] = c; + } else { + rl.escape_seq = ESEQ_NONE; + if (c == 'A') { + // up arrow + if (rl.hist_cur + 1 < READLINE_HIST_SIZE && MP_STATE_PORT(readline_hist)[rl.hist_cur + 1] != NULL) { + // increase hist num + rl.hist_cur += 1; + // set line to history + rl.line->len = rl.orig_line_len; + vstr_add_str(rl.line, MP_STATE_PORT(readline_hist)[rl.hist_cur]); + // set redraw parameters + redraw_step_back = rl.cursor_pos - rl.orig_line_len; + redraw_from_cursor = true; + redraw_step_forward = rl.line->len - rl.orig_line_len; } + } else if (c == 'B') { + // down arrow + if (rl.hist_cur >= 0) { + // decrease hist num + rl.hist_cur -= 1; + // set line to history + vstr_cut_tail_bytes(rl.line, rl.line->len - rl.orig_line_len); + if (rl.hist_cur >= 0) { + vstr_add_str(rl.line, MP_STATE_PORT(readline_hist)[rl.hist_cur]); + } + // set redraw parameters + redraw_step_back = rl.cursor_pos - rl.orig_line_len; + redraw_from_cursor = true; + redraw_step_forward = rl.line->len - rl.orig_line_len; + } + } else if (c == 'C') { + // right arrow + if (rl.cursor_pos < rl.line->len) { + redraw_step_forward = 1; + } + } else if (c == 'D') { + // left arrow + if (rl.cursor_pos > rl.orig_line_len) { + redraw_step_back = 1; + } + } else if (c == 'H') { + // home + goto home_key; + } else if (c == 'F') { + // end + goto end_key; + } else { + DEBUG_printf("(ESC [ %d)", c); } - } else if (escape_seq == ESEQ_ESC_BRACKET_DIGIT) { - if (c == '~') { - if (escape_seq_buf[0] == '1' || escape_seq_buf[0] == '7') { + } + } else if (rl.escape_seq == ESEQ_ESC_BRACKET_DIGIT) { + if (c == '~') { + if (rl.escape_seq_buf[0] == '1' || rl.escape_seq_buf[0] == '7') { home_key: - redraw_step_back = cursor_pos - orig_line_len; - } else if (escape_seq_buf[0] == '4' || escape_seq_buf[0] == '8') { + redraw_step_back = rl.cursor_pos - rl.orig_line_len; + } else if (rl.escape_seq_buf[0] == '4' || rl.escape_seq_buf[0] == '8') { end_key: - redraw_step_forward = line->len - cursor_pos; - } else { - DEBUG_printf("(ESC [ %c %d)", escape_seq_buf[0], c); - } + redraw_step_forward = rl.line->len - rl.cursor_pos; } else { - DEBUG_printf("(ESC [ %c %d)", escape_seq_buf[0], c); - } - escape_seq = ESEQ_NONE; - } else if (escape_seq == ESEQ_ESC_O) { - switch (c) { - case 'H': - goto home_key; - case 'F': - goto end_key; - default: - DEBUG_printf("(ESC O %d)", c); - escape_seq = ESEQ_NONE; + DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c); } } else { - escape_seq = ESEQ_NONE; + DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c); } + rl.escape_seq = ESEQ_NONE; + } else if (rl.escape_seq == ESEQ_ESC_O) { + switch (c) { + case 'H': + goto home_key; + case 'F': + goto end_key; + default: + DEBUG_printf("(ESC O %d)", c); + rl.escape_seq = ESEQ_NONE; + } + } else { + rl.escape_seq = ESEQ_NONE; + } - // redraw command prompt, efficiently - // TODO we can probably use some more sophisticated VT100 commands here - if (redraw_step_back > 0) { - for (int i = 0; i < redraw_step_back; i++) { - stdout_tx_str("\b"); - } - cursor_pos -= redraw_step_back; + // redraw command prompt, efficiently + // TODO we can probably use some more sophisticated VT100 commands here + if (redraw_step_back > 0) { + for (int i = 0; i < redraw_step_back; i++) { + stdout_tx_str("\b"); } - if (redraw_from_cursor) { - if (line->len < last_line_len) { - // erase old chars - for (int i = cursor_pos; i < last_line_len; i++) { - stdout_tx_str(" "); - } - // step back - for (int i = cursor_pos; i < last_line_len; i++) { - stdout_tx_str("\b"); - } + rl.cursor_pos -= redraw_step_back; + } + if (redraw_from_cursor) { + if (rl.line->len < last_line_len) { + // erase old chars + for (int i = rl.cursor_pos; i < last_line_len; i++) { + stdout_tx_str(" "); } - // draw new chars - stdout_tx_strn(line->buf + cursor_pos, line->len - cursor_pos); - // move cursor forward if needed (already moved forward by length of line, so move it back) - for (int i = cursor_pos + redraw_step_forward; i < line->len; i++) { + // step back + for (int i = rl.cursor_pos; i < last_line_len; i++) { stdout_tx_str("\b"); } - cursor_pos += redraw_step_forward; - } else if (redraw_step_forward > 0) { - // draw over old chars to move cursor forwards - stdout_tx_strn(line->buf + cursor_pos, redraw_step_forward); - cursor_pos += redraw_step_forward; + } + // draw new chars + stdout_tx_strn(rl.line->buf + rl.cursor_pos, rl.line->len - rl.cursor_pos); + // move cursor forward if needed (already moved forward by length of line, so move it back) + for (int i = rl.cursor_pos + redraw_step_forward; i < rl.line->len; i++) { + stdout_tx_str("\b"); + } + rl.cursor_pos += redraw_step_forward; + } else if (redraw_step_forward > 0) { + // draw over old chars to move cursor forwards + stdout_tx_strn(rl.line->buf + rl.cursor_pos, redraw_step_forward); + rl.cursor_pos += redraw_step_forward; + } + + return -1; +} + +void readline_note_newline() { + rl.orig_line_len = rl.line->len; + rl.cursor_pos = rl.orig_line_len; +} + +void readline_init(vstr_t *line) { + rl.line = line; + rl.orig_line_len = line->len; + rl.escape_seq = ESEQ_NONE; + rl.escape_seq_buf[0] = 0; + rl.hist_cur = -1; + rl.cursor_pos = rl.orig_line_len; +} + +int readline(vstr_t *line, const char *prompt) { + stdout_tx_str(prompt); + readline_init(line); + for (;;) { + int c = stdin_rx_chr(); + int r = readline_process_char(c); + if (r >= 0) { + return r; } } } diff --git a/lib/mp-readline/readline.h b/lib/mp-readline/readline.h index d5ce4704ea..51ea4a56d4 100644 --- a/lib/mp-readline/readline.h +++ b/lib/mp-readline/readline.h @@ -32,3 +32,7 @@ void readline_init0(void); int readline(vstr_t *line, const char *prompt); + +void readline_init(vstr_t *line); +void readline_note_newline(); +int readline_process_char(int c);