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.
*/
#include "readline.h"
#include "print.h"
#include "misc.h"
#include <assert.h>
#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;
}
}

View file

@ -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);

View file

@ -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;