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.
This commit is contained in:
yabu76 2025-11-29 16:41:04 +09:00
parent 8a1b739ae6
commit 0983ce6eeb
3 changed files with 204 additions and 151 deletions

View file

@ -19,85 +19,133 @@
* 02110-1301, USA. * 02110-1301, USA.
*/ */
#include "readline.h"
#include "print.h" #include "print.h"
#include "misc.h" #include "misc.h"
#include <assert.h>
#define RL_LINE_LENGTH_MAX PATH_MAX #define RL_LINE_LENGTH_MAX PATH_MAX
#define RL_HISTORY_MAX 1000
static char rl_line[RL_LINE_LENGTH_MAX] = {}; typedef struct readline_s
static char *rl_history[RL_HISTORY_MAX]; {
static int rl_history_count = 0; char line[RL_LINE_LENGTH_MAX];
static int rl_history_index = 0; char *history[RL_HISTORY_MAX];
static int rl_line_length = 0; char prompt[RL_PROMPT_LENGTH_MAX];
static int rl_cursor_pos = 0; int prompt_length;
static int rl_escape = 0; 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(); clear_line();
print("%s", string); print("%s%s", rl->prompt, rl->line);
print("\r"); // Move the cursor back to the beginning 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 print("\x1b[C"); // Move the cursor right
} }
} }
void readline_init(void) readline_t *readline_create(void)
{ {
rl_history_count = 0; readline_t *rl = malloc(sizeof(readline_t));
rl_history_index = 0; 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) for (int i = 0; i < RL_HISTORY_MAX; ++i)
{ {
rl_history[i] = NULL; rl->history[i] = NULL;
} }
rl_line[0] = 0; rl->line[0] = 0;
rl_line_length = 0; rl->line_length = 0;
rl_cursor_pos = 0; rl->cursor_pos = 0;
rl_escape = 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); 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[rl->cursor_pos] = input_char;
rl_line_length++; rl->line_length++;
rl_cursor_pos++; rl->cursor_pos++;
rl_line[rl_line_length] = '\0'; rl->line[rl->line_length] = '\0';
print_line(rl_line, rl_cursor_pos); 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 // Different line only
if (rl_history_count < RL_HISTORY_MAX) 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); // Save to history
rl_history_count++; if (rl->history_count < RL_HISTORY_MAX)
} {
else rl->history[rl->history_count] = strndup(rl->line, rl->line_length);
{ rl->history_count++;
free(rl_history[0]); }
memmove(&rl_history[0], &rl_history[1], (RL_HISTORY_MAX - 1) * sizeof(char*)); else
rl_history[RL_HISTORY_MAX - 1] = strndup(rl_line, rl_line_length); {
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) if (option.local_echo == false)
{ {
clear_line(); clear_line();
@ -107,170 +155,171 @@ static void readline_input_cr(void)
print("\r\n"); print("\r\n");
} }
rl_line_length = 0; rl->line_length = 0;
rl_cursor_pos = 0; rl->cursor_pos = 0;
rl_history_index = rl_history_count; rl->history_index = rl->history_count;
rl_escape = 0; 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); memmove(&rl->line[rl->cursor_pos - 1], &rl->line[rl->cursor_pos], rl->line_length - rl->cursor_pos);
rl_line_length--; rl->line_length--;
rl_cursor_pos--; rl->cursor_pos--;
rl_line[rl_line_length] = '\0'; rl->line[rl->line_length] = '\0';
print_line(rl_line, rl_cursor_pos); 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 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 // Up arrow
if (rl_history_index > 0) if (rl->history_index > 0)
{ {
rl_history_index--; rl->history_index--;
strncpy(rl_line, rl_history[rl_history_index], RL_LINE_LENGTH_MAX-1); strncpy(rl->line, rl->history[rl->history_index], RL_LINE_LENGTH_MAX-1);
rl_line_length = strlen(rl_line); rl->line_length = strlen(rl->line);
rl_cursor_pos = rl_line_length; rl->cursor_pos = rl->line_length;
print_line(rl_line, rl_cursor_pos); print_line(rl);
} }
} }
else 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 // Down arrow
if (rl_history_index < rl_history_count - 1) if (rl->history_index < rl->history_count - 1)
{ {
rl_history_index++; rl->history_index++;
strncpy(rl_line, rl_history[rl_history_index], RL_LINE_LENGTH_MAX-1); strncpy(rl->line, rl->history[rl->history_index], RL_LINE_LENGTH_MAX-1);
rl_line_length = strlen(rl_line); rl->line_length = strlen(rl->line);
rl_cursor_pos = rl_line_length; rl->cursor_pos = rl->line_length;
print_line(rl_line, rl_cursor_pos); 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->history_index++;
rl_line_length = 0; rl->line_length = 0;
rl_cursor_pos = 0; rl->cursor_pos = 0;
rl_line[rl_line_length] = '\0'; rl->line[rl->line_length] = '\0';
print_line(rl_line, rl_cursor_pos); print_line(rl);
} }
} }
else 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 // 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"); print("\x1b[C");
} }
} }
else 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 // Left arrow
if (rl_cursor_pos > 0) if (rl->cursor_pos > 0)
{ {
rl_cursor_pos--; rl->cursor_pos--;
print("\b"); print("\b");
} }
} }
else 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) switch (input_char)
{ {
case '\r': // Carriage return case '\r': // Carriage return
readline_input_cr(); readline_input_cr(rl);
break; break;
case 127: // Backspace case 127: // Backspace
readline_input_bs(); readline_input_bs(rl);
break; break;
case 27: // Escape case 27: // Escape
readline_input_escape(); readline_input_escape(rl);
break; break;
case '[': case '[':
readline_input_left_bracket(); readline_input_left_bracket(rl);
break; break;
case 'A': case 'A':
readline_input_A(); readline_input_A(rl);
break; break;
case 'B': case 'B':
readline_input_B(); readline_input_B(rl);
break; break;
case 'C': case 'C':
readline_input_C(); readline_input_C(rl);
break; break;
case 'D': case 'D':
readline_input_D(); readline_input_D(rl);
break; break;
default: default:
readline_input_char(input_char); readline_input_char(rl, input_char);
break; break;
} }
} }

View file

@ -21,6 +21,14 @@
#pragma once #pragma once
void readline_init(void); #define RL_HISTORY_MAX 1000
void readline_input(char input_char); #define RL_PROMPT_LENGTH_MAX 16
char * readline_get(void);
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);

View file

@ -185,6 +185,8 @@ static int pipefd[2];
static pthread_mutex_t mutex_input_ready = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t mutex_input_ready = PTHREAD_MUTEX_INITIALIZER;
static char line[PATH_MAX]; static char line[PATH_MAX];
static size_t listing_device_name_length_max = 0; 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) 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 */ /* Read line with line edit and history. */
for (p = line ; p < &line[PATH_MAX-1]; ) char c;
while (true)
{ {
if (read(pipefd[0], p, 1) > 0) if (read(pipefd[0], &c, 1) > 0)
{ {
if (*p == 0x08 || *p == 0x7f) readline_input(subcmd_readline_ctx, c);
{ if (c == '\r') break;
if (p > line)
{
write(STDOUT_FILENO, "\b \b", 3);
p--;
}
continue;
}
write(STDOUT_FILENO, p, 1);
if (*p == '\r') break;
p++;
} }
} }
*p = 0; strncpy(line, readline_get(subcmd_readline_ctx), PATH_MAX - 1);
return (p - line); line[PATH_MAX - 1] = 0;
return strlen(line);
} }
void tty_output_mode_set(output_mode_t mode) 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: case KEY_0:
tio_printf("Send file with XMODEM-1K"); tio_printf("Send file with XMODEM-1K");
tio_printf_raw("Enter file name: "); if (tio_subcmd_readln("Enter file name: "))
if (tio_readln())
{ {
int ret; int ret;
@ -741,8 +736,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
case KEY_1: case KEY_1:
tio_printf("Send file with XMODEM-CRC"); tio_printf("Send file with XMODEM-CRC");
tio_printf_raw("Enter file name: "); if (tio_subcmd_readln("Enter file name: "))
if (tio_readln())
{ {
int ret; int ret;
@ -755,8 +749,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
case KEY_2: case KEY_2:
tio_printf("Receive file with XMODEM-CRC"); tio_printf("Receive file with XMODEM-CRC");
tio_printf_raw("Enter file name: "); if (tio_subcmd_readln("Enter file name: "))
if (tio_readln())
{ {
int ret; int ret;
@ -769,8 +762,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
case KEY_3: case KEY_3:
tio_printf("Send file with XMODEM-SUM"); tio_printf("Send file with XMODEM-SUM");
tio_printf_raw("Enter file name: "); if (tio_subcmd_readln("Enter file name: "))
if (tio_readln())
{ {
int ret; int ret;
@ -783,8 +775,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
case KEY_4: case KEY_4:
tio_printf("Receive file with XMODEM-SUM"); tio_printf("Receive file with XMODEM-SUM");
tio_printf_raw("Enter file name: "); if (tio_subcmd_readln("Enter file name: "))
if (tio_readln())
{ {
int ret; int ret;
@ -1087,8 +1078,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
case KEY_R: case KEY_R:
/* Run script */ /* Run script */
tio_printf("Run Lua script"); tio_printf("Run Lua script");
tio_printf_raw("Enter file name: "); if (tio_subcmd_readln("Enter file name: "))
if (tio_readln())
{ {
clear_line(); clear_line();
script_run(device_fd, 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: case KEY_SHIFT_R:
/* Execute shell command */ /* Execute shell command */
tio_printf("Execute shell command with I/O redirected to device"); tio_printf("Execute shell command with I/O redirected to device");
tio_printf_raw("Enter command: "); if (tio_subcmd_readln("Enter command: "))
if (tio_readln())
execute_shell_command(device_fd, line); execute_shell_command(device_fd, line);
break; break;
@ -1163,8 +1152,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
case KEY_Y: case KEY_Y:
tio_printf("Send file with YMODEM"); tio_printf("Send file with YMODEM");
tio_printf_raw("Enter file name: "); if (tio_subcmd_readln("Enter file name: "))
if (tio_readln())
{ {
int ret; int ret;
@ -2718,7 +2706,15 @@ int tty_connect(void)
} }
// Initialize readline like history // 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 */ /* Input loop */
while (true) while (true)
@ -2972,15 +2968,15 @@ int tty_connect(void)
if (input_char == '\r') if (input_char == '\r')
{ {
// Carriage return // Carriage return
readline_input(input_char); readline_input(readline_ctx, input_char);
// Write current line to tty device // 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)); tty_write(device_fd, rl_line, strlen(rl_line));
} }
else else
{ {
readline_input(input_char); readline_input(readline_ctx, input_char);
forward = false; forward = false;
} }
break; break;