From 0983ce6eebc386fd282e3c32a56ecdac0258ac92 Mon Sep 17 00:00:00 2001 From: yabu76 Date: Sat, 29 Nov 2025 16:41:04 +0900 Subject: [PATCH] Add history/line-edit function to Ctrl-t command string input Ctrl-t r/R/x/y commands require string input. To reduce the hassle of repeatedly entering this information, I apply readline functions (line-editing and history) to the string input. Now tio have two histories: one for the line input-mode and one for the ctrl-t command's string input. And the following changes are made to the line input: - Add prompts. "> ": line input-mode, ">> " Ctrl-t string input. - Omits duplicate lines from the history. To implement the above functionality, we will modify the readline functions to use the multi-instance style. --- src/readline.c | 267 +++++++++++++++++++++++++++++-------------------- src/readline.h | 14 ++- src/tty.c | 74 +++++++------- 3 files changed, 204 insertions(+), 151 deletions(-) diff --git a/src/readline.c b/src/readline.c index 6cfae00..27125c6 100644 --- a/src/readline.c +++ b/src/readline.c @@ -19,85 +19,133 @@ * 02110-1301, USA. */ +#include "readline.h" #include "print.h" #include "misc.h" +#include #define RL_LINE_LENGTH_MAX PATH_MAX -#define RL_HISTORY_MAX 1000 -static char rl_line[RL_LINE_LENGTH_MAX] = {}; -static char *rl_history[RL_HISTORY_MAX]; -static int rl_history_count = 0; -static int rl_history_index = 0; -static int rl_line_length = 0; -static int rl_cursor_pos = 0; -static int rl_escape = 0; +typedef struct readline_s +{ + char line[RL_LINE_LENGTH_MAX]; + char *history[RL_HISTORY_MAX]; + char prompt[RL_PROMPT_LENGTH_MAX]; + int prompt_length; + int history_count; + int history_index; + int line_length; + int cursor_pos; + int escape; +} readline_t; -static void print_line(const char *string, int cursor_pos) +void print_line(readline_t *rl) { clear_line(); - print("%s", string); + print("%s%s", rl->prompt, rl->line); print("\r"); // Move the cursor back to the beginning - for (int i = 0; i < cursor_pos; ++i) + for (int i = 0; i < rl->prompt_length + rl->cursor_pos; ++i) { print("\x1b[C"); // Move the cursor right } } -void readline_init(void) +readline_t *readline_create(void) { - rl_history_count = 0; - rl_history_index = 0; + readline_t *rl = malloc(sizeof(readline_t)); + if (rl == NULL) + return NULL; + + readline_reinit(rl); + return rl; +} + +void readline_reinit(readline_t *rl) +{ + assert(rl != NULL); + + rl->prompt[0] = '\0'; + rl->prompt_length = 0; + + rl->history_count = 0; + rl->history_index = 0; for (int i = 0; i < RL_HISTORY_MAX; ++i) { - rl_history[i] = NULL; + rl->history[i] = NULL; } - rl_line[0] = 0; - rl_line_length = 0; - rl_cursor_pos = 0; - rl_escape = 0; + rl->line[0] = 0; + rl->line_length = 0; + rl->cursor_pos = 0; + rl->escape = 0; } -char * readline_get(void) +void readline_set_prompt(readline_t *rl, const char *prompt) { - return rl_line; + strncpy(rl->prompt, prompt, RL_PROMPT_LENGTH_MAX - 1); + rl->prompt[RL_PROMPT_LENGTH_MAX - 1] = '\0'; + rl->prompt_length = strlen(rl->prompt); } -static void readline_input_char(char input_char) +char * readline_get(readline_t *rl) { - if (rl_line_length < RL_LINE_LENGTH_MAX - 1) + assert(rl != NULL); + return rl->line; +} + +void readline_prompt_for_input(readline_t *rl) +{ + assert(rl != NULL); + + rl->line[0] = 0; + rl->line_length = 0; + rl->cursor_pos = 0; + rl->escape = 0; + print_line(rl); +} + +static void readline_input_char(readline_t *rl, char input_char) +{ + assert(rl != NULL); + + if (rl->line_length < RL_LINE_LENGTH_MAX - 1) { - memmove(&rl_line[rl_cursor_pos + 1], &rl_line[rl_cursor_pos], rl_line_length - rl_cursor_pos); - rl_line[rl_cursor_pos] = input_char; - rl_line_length++; - rl_cursor_pos++; - rl_line[rl_line_length] = '\0'; - print_line(rl_line, rl_cursor_pos); + memmove(&rl->line[rl->cursor_pos + 1], &rl->line[rl->cursor_pos], rl->line_length - rl->cursor_pos); + rl->line[rl->cursor_pos] = input_char; + rl->line_length++; + rl->cursor_pos++; + rl->line[rl->line_length] = '\0'; + print_line(rl); } - rl_escape = 0; + rl->escape = 0; } -static void readline_input_cr(void) +static void readline_input_cr(readline_t *rl) { - if (rl_line_length > 0) + rl->line[rl->line_length] = '\0'; + + if (rl->line_length > 0) { - // Save to history - if (rl_history_count < RL_HISTORY_MAX) + // Different line only + if (rl->history_count == 0 || + (rl->history_count > 0 && strncmp(rl->history[rl->history_count - 1], rl->line, rl->line_length) != 0)) { - rl_history[rl_history_count] = strndup(rl_line, rl_line_length); - rl_history_count++; - } - else - { - free(rl_history[0]); - memmove(&rl_history[0], &rl_history[1], (RL_HISTORY_MAX - 1) * sizeof(char*)); - rl_history[RL_HISTORY_MAX - 1] = strndup(rl_line, rl_line_length); + // Save to history + if (rl->history_count < RL_HISTORY_MAX) + { + rl->history[rl->history_count] = strndup(rl->line, rl->line_length); + rl->history_count++; + } + else + { + free(rl->history[0]); + memmove(&rl->history[0], &rl->history[1], (RL_HISTORY_MAX - 1) * sizeof(char*)); + rl->history[RL_HISTORY_MAX - 1] = strndup(rl->line, rl->line_length); + } } } - rl_line[rl_line_length] = '\0'; if (option.local_echo == false) { clear_line(); @@ -107,170 +155,171 @@ static void readline_input_cr(void) print("\r\n"); } - rl_line_length = 0; - rl_cursor_pos = 0; - rl_history_index = rl_history_count; - rl_escape = 0; + rl->line_length = 0; + rl->cursor_pos = 0; + rl->history_index = rl->history_count; + rl->escape = 0; } -static void readline_input_bs(void) +static void readline_input_bs(readline_t *rl) { - if (rl_cursor_pos > 0) + if (rl->cursor_pos > 0) { - memmove(&rl_line[rl_cursor_pos - 1], &rl_line[rl_cursor_pos], rl_line_length - rl_cursor_pos); - rl_line_length--; - rl_cursor_pos--; - rl_line[rl_line_length] = '\0'; - print_line(rl_line, rl_cursor_pos); + memmove(&rl->line[rl->cursor_pos - 1], &rl->line[rl->cursor_pos], rl->line_length - rl->cursor_pos); + rl->line_length--; + rl->cursor_pos--; + rl->line[rl->line_length] = '\0'; + print_line(rl); } - rl_escape = 0; + rl->escape = 0; } -static void readline_input_escape(void) +static void readline_input_escape(readline_t *rl) { - rl_escape = 1; + rl->escape = 1; } -static void readline_input_left_bracket(void) +static void readline_input_left_bracket(readline_t *rl) { - if (rl_escape == 1) + if (rl->escape == 1) { - rl_escape = 2; + rl->escape = 2; } else { - rl_escape = 0; + readline_input_char(rl, '['); + rl->escape = 0; } } -static void readline_input_A(void) +static void readline_input_A(readline_t *rl) { - if (rl_escape == 2) + if (rl->escape == 2) { // Up arrow - if (rl_history_index > 0) + if (rl->history_index > 0) { - rl_history_index--; - strncpy(rl_line, rl_history[rl_history_index], RL_LINE_LENGTH_MAX-1); - rl_line_length = strlen(rl_line); - rl_cursor_pos = rl_line_length; - print_line(rl_line, rl_cursor_pos); + rl->history_index--; + strncpy(rl->line, rl->history[rl->history_index], RL_LINE_LENGTH_MAX-1); + rl->line_length = strlen(rl->line); + rl->cursor_pos = rl->line_length; + print_line(rl); } } else { - readline_input_char('A'); + readline_input_char(rl, 'A'); } - rl_escape = 0; + rl->escape = 0; } -static void readline_input_B(void) +static void readline_input_B(readline_t *rl) { - if (rl_escape == 2) + if (rl->escape == 2) { // Down arrow - if (rl_history_index < rl_history_count - 1) + if (rl->history_index < rl->history_count - 1) { - rl_history_index++; - strncpy(rl_line, rl_history[rl_history_index], RL_LINE_LENGTH_MAX-1); - rl_line_length = strlen(rl_line); - rl_cursor_pos = rl_line_length; - print_line(rl_line, rl_cursor_pos); + rl->history_index++; + strncpy(rl->line, rl->history[rl->history_index], RL_LINE_LENGTH_MAX-1); + rl->line_length = strlen(rl->line); + rl->cursor_pos = rl->line_length; + print_line(rl); } - else if (rl_history_index == rl_history_count - 1) + else if (rl->history_index == rl->history_count - 1) { - rl_history_index++; - rl_line_length = 0; - rl_cursor_pos = 0; - rl_line[rl_line_length] = '\0'; - print_line(rl_line, rl_cursor_pos); + rl->history_index++; + rl->line_length = 0; + rl->cursor_pos = 0; + rl->line[rl->line_length] = '\0'; + print_line(rl); } } else { - readline_input_char('B'); + readline_input_char(rl, 'B'); } - rl_escape = 0; + rl->escape = 0; } -static void readline_input_C(void) +static void readline_input_C(readline_t *rl) { - if (rl_escape == 2) + if (rl->escape == 2) { // Right arrow - if (rl_cursor_pos < rl_line_length) + if (rl->cursor_pos < rl->line_length) { - rl_cursor_pos++; + rl->cursor_pos++; print("\x1b[C"); } } else { - readline_input_char('C'); + readline_input_char(rl, 'C'); } - rl_escape = 0; + rl->escape = 0; } -static void readline_input_D(void) +static void readline_input_D(readline_t *rl) { - if (rl_escape == 2) + if (rl->escape == 2) { // Left arrow - if (rl_cursor_pos > 0) + if (rl->cursor_pos > 0) { - rl_cursor_pos--; + rl->cursor_pos--; print("\b"); } } else { - readline_input_char('D'); + readline_input_char(rl, 'D'); } - rl_escape = 0; + rl->escape = 0; } -void readline_input(char input_char) +void readline_input(readline_t *rl, char input_char) { switch (input_char) { case '\r': // Carriage return - readline_input_cr(); + readline_input_cr(rl); break; case 127: // Backspace - readline_input_bs(); + readline_input_bs(rl); break; case 27: // Escape - readline_input_escape(); + readline_input_escape(rl); break; case '[': - readline_input_left_bracket(); + readline_input_left_bracket(rl); break; case 'A': - readline_input_A(); + readline_input_A(rl); break; case 'B': - readline_input_B(); + readline_input_B(rl); break; case 'C': - readline_input_C(); + readline_input_C(rl); break; case 'D': - readline_input_D(); + readline_input_D(rl); break; default: - readline_input_char(input_char); + readline_input_char(rl, input_char); break; } } diff --git a/src/readline.h b/src/readline.h index 46c3f10..0c043b7 100644 --- a/src/readline.h +++ b/src/readline.h @@ -21,6 +21,14 @@ #pragma once -void readline_init(void); -void readline_input(char input_char); -char * readline_get(void); +#define RL_HISTORY_MAX 1000 +#define RL_PROMPT_LENGTH_MAX 16 + +typedef struct readline_s readline_t; + +readline_t *readline_create(void); +void readline_reinit(readline_t *rl); +void readline_set_prompt(readline_t *rl, const char *prompt); +void readline_prompt_for_input(readline_t *rl); +void readline_input(readline_t *rl, char input_char); +char *readline_get(readline_t *rl); diff --git a/src/tty.c b/src/tty.c index 08abf41..63f7054 100644 --- a/src/tty.c +++ b/src/tty.c @@ -185,6 +185,8 @@ static int pipefd[2]; static pthread_mutex_t mutex_input_ready = PTHREAD_MUTEX_INITIALIZER; static char line[PATH_MAX]; static size_t listing_device_name_length_max = 0; +static readline_t *readline_ctx = NULL; +static readline_t *subcmd_readline_ctx = NULL; static void optional_local_echo(char c) { @@ -589,31 +591,25 @@ static void tty_line_poke(int fd, int mask, tty_line_mode_t mode, unsigned int d } } -static int tio_readln(void) +static int tio_subcmd_readln(const char *title_prompt) { - char *p = line; + tio_printf_raw("%s\r\n", title_prompt); + readline_prompt_for_input(subcmd_readline_ctx); - /* Read line, accept BS and DEL as rubout characters */ - for (p = line ; p < &line[PATH_MAX-1]; ) + /* Read line with line edit and history. */ + char c; + while (true) { - if (read(pipefd[0], p, 1) > 0) + if (read(pipefd[0], &c, 1) > 0) { - if (*p == 0x08 || *p == 0x7f) - { - if (p > line) - { - write(STDOUT_FILENO, "\b \b", 3); - p--; - } - continue; - } - write(STDOUT_FILENO, p, 1); - if (*p == '\r') break; - p++; + readline_input(subcmd_readline_ctx, c); + if (c == '\r') break; } } - *p = 0; - return (p - line); + strncpy(line, readline_get(subcmd_readline_ctx), PATH_MAX - 1); + line[PATH_MAX - 1] = 0; + + return strlen(line); } void tty_output_mode_set(output_mode_t mode) @@ -727,8 +723,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward) { case KEY_0: tio_printf("Send file with XMODEM-1K"); - tio_printf_raw("Enter file name: "); - if (tio_readln()) + if (tio_subcmd_readln("Enter file name: ")) { int ret; @@ -741,8 +736,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward) case KEY_1: tio_printf("Send file with XMODEM-CRC"); - tio_printf_raw("Enter file name: "); - if (tio_readln()) + if (tio_subcmd_readln("Enter file name: ")) { int ret; @@ -755,8 +749,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward) case KEY_2: tio_printf("Receive file with XMODEM-CRC"); - tio_printf_raw("Enter file name: "); - if (tio_readln()) + if (tio_subcmd_readln("Enter file name: ")) { int ret; @@ -769,8 +762,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward) case KEY_3: tio_printf("Send file with XMODEM-SUM"); - tio_printf_raw("Enter file name: "); - if (tio_readln()) + if (tio_subcmd_readln("Enter file name: ")) { int ret; @@ -783,8 +775,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward) case KEY_4: tio_printf("Receive file with XMODEM-SUM"); - tio_printf_raw("Enter file name: "); - if (tio_readln()) + if (tio_subcmd_readln("Enter file name: ")) { int ret; @@ -1087,8 +1078,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward) case KEY_R: /* Run script */ tio_printf("Run Lua script"); - tio_printf_raw("Enter file name: "); - if (tio_readln()) + if (tio_subcmd_readln("Enter file name: ")) { clear_line(); script_run(device_fd, line); @@ -1103,8 +1093,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward) case KEY_SHIFT_R: /* Execute shell command */ tio_printf("Execute shell command with I/O redirected to device"); - tio_printf_raw("Enter command: "); - if (tio_readln()) + if (tio_subcmd_readln("Enter command: ")) execute_shell_command(device_fd, line); break; @@ -1163,8 +1152,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward) case KEY_Y: tio_printf("Send file with YMODEM"); - tio_printf_raw("Enter file name: "); - if (tio_readln()) + if (tio_subcmd_readln("Enter file name: ")) { int ret; @@ -2718,7 +2706,15 @@ int tty_connect(void) } // Initialize readline like history - readline_init(); + readline_ctx = readline_create(); + subcmd_readline_ctx = readline_create(); + if (readline_ctx == NULL || subcmd_readline_ctx == NULL) + { + tio_error_printf("Could not allocate readline buffer."); + exit(EXIT_FAILURE); + } + readline_set_prompt(readline_ctx, "> "); + readline_set_prompt(subcmd_readline_ctx, ">> "); /* Input loop */ while (true) @@ -2972,15 +2968,15 @@ int tty_connect(void) if (input_char == '\r') { // Carriage return - readline_input(input_char); + readline_input(readline_ctx, input_char); // Write current line to tty device - char *rl_line = readline_get(); + char *rl_line = readline_get(readline_ctx); tty_write(device_fd, rl_line, strlen(rl_line)); } else { - readline_input(input_char); + readline_input(readline_ctx, input_char); forward = false; } break;