input: Up/Down in INSERT mode walks sent-message history (UX-2)

ESC in INSERT mode used to switch straight to NORMAL.  Now it follows
the same arrow-key probe COMMAND mode already does: if the next two
bytes are "[A" or "[B", treat as Up / Down and walk through the last
16 messages this client has sent.  A plain ESC still falls through to
NORMAL — the 50 ms probe timeout keeps that path responsive.

Storage: new client_t fields
  char insert_history[16][MAX_MESSAGE_LEN];
  int  insert_history_count;
  int  insert_history_pos;

Recording: every Enter that broadcasts a message pushes the input
buffer onto the ring (with FIFO eviction at 16) and resets pos.
Down at the bottom of the ring returns to an empty input.

This is the standard chat-client recall that vim users (and anyone
who's ever used a shell) expect.
This commit is contained in:
m1ngsama 2026-05-17 13:43:15 +08:00
parent 94f3d28562
commit f3217de36b
2 changed files with 56 additions and 2 deletions

View file

@ -24,6 +24,11 @@ typedef struct client {
char command_history[16][256];
int command_history_count;
int command_history_pos;
/* INSERT mode chat-message history. Last 16 messages this client
* sent, oldest first. Up/Down in INSERT mode walks through it. */
char insert_history[16][MAX_MESSAGE_LEN];
int insert_history_count;
int insert_history_pos;
char command_output[2048];
bool show_motd; /* command_output holds MOTD text */
char exec_command[MAX_EXEC_COMMAND_LEN];

View file

@ -206,13 +206,62 @@ static bool handle_key(client_t *client, unsigned char key, char *input) {
/* Mode-specific handling */
switch (client->mode) {
case MODE_INSERT:
if (key == 27) { /* ESC */
if (key == 27) { /* ESC — may also be the start of an arrow seq */
char seq[2];
int n = ssh_channel_read_timeout(client->channel, seq, 1, 0, 50);
if (n == 1 && seq[0] == '[') {
n = ssh_channel_read_timeout(client->channel, &seq[1], 1, 0, 50);
if (n == 1) {
if (seq[1] == 'A') { /* Up — walk back through sent history */
if (client->insert_history_count > 0 &&
client->insert_history_pos > 0) {
client->insert_history_pos--;
strncpy(input,
client->insert_history[client->insert_history_pos],
MAX_MESSAGE_LEN - 1);
input[MAX_MESSAGE_LEN - 1] = '\0';
tui_render_input(client, input);
}
return true;
} else if (seq[1] == 'B') { /* Down — walk forward */
if (client->insert_history_pos <
client->insert_history_count - 1) {
client->insert_history_pos++;
strncpy(input,
client->insert_history[client->insert_history_pos],
MAX_MESSAGE_LEN - 1);
input[MAX_MESSAGE_LEN - 1] = '\0';
} else {
client->insert_history_pos =
client->insert_history_count;
input[0] = '\0';
}
tui_render_input(client, input);
return true;
}
}
}
/* Plain ESC — fall through to NORMAL mode */
client->mode = MODE_NORMAL;
client->scroll_pos = 0;
tui_render_screen(client);
return true; /* Key consumed */
return true;
} else if (key == '\r' || key == '\n') { /* Enter */
if (input[0] != '\0') {
/* Record into the per-client INSERT history ring */
int max_hist = (int)(sizeof(client->insert_history) /
sizeof(client->insert_history[0]));
if (client->insert_history_count >= max_hist) {
memmove(&client->insert_history[0],
&client->insert_history[1],
(max_hist - 1) * sizeof(client->insert_history[0]));
client->insert_history_count = max_hist - 1;
}
snprintf(client->insert_history[client->insert_history_count],
sizeof(client->insert_history[0]), "%s", input);
client->insert_history_count++;
client->insert_history_pos = client->insert_history_count;
message_t msg = {
.timestamp = time(NULL),
};