serial_console: Add support to handle delete/arrow keys (#195)

This change adds support to serial console to handle delete
left & right arrow and backspace key by changing
result buffer based on the keypress and emulating the movement
with espace characters in the terminal.

This patch also takes care of buffer flow handling by checking
the cur and index with the max available length for each operations.
This commit is contained in:
Madrajib Lab
2025-07-10 03:12:57 +05:30
committed by GitHub
parent 266b5d76b1
commit 40932518ae

View File

@@ -5,11 +5,49 @@
#define MAX_COMDLINE_SIZE 80 #define MAX_COMDLINE_SIZE 80
#define MAX_WORDS 6 #define MAX_WORDS 6
#define BACKSPACE_CHAR 0x08
#define ESCAPE_CHAR '\x1b'
#define CSI_CHAR '['
#define LEFT_ARROW_CHAR 'D'
#define RIGHT_ARROW_CHAR 'C'
#define DELETE_KEY_1_CHAR '3'
#define DELETE_KEY_2_CHAR '~'
#define PRINT_CHAR_START 32
#define PRINT_CHAR_END 126
namespace serial_console namespace serial_console
{ {
bool global_disable_serial_console = false; bool global_disable_serial_console = false;
/**
* @brief Redraws the characters from the current cursor position to the end of the buffer on the serial console.
*
* This function is used to update the terminal display when characters are inserted or deleted
* in the middle of the input buffer. It:
* - Saves the current cursor position
* - Clears the line from the cursor to the end
* - Prints characters from 'cur' to 'len'
* - Restores the original cursor position
*
* @param cur Current cursor position within the buffer
* @param len Current length of the buffer (number of characters entered)
* @param buf Character buffer containing the input string
*/
static inline void redraw(const int cur, const int len, char *buf) {
if (!temporary_config.remote_echo)
return;
Serial.print("\x1b[s");
Serial.print("\x1b[K");
// Reprint characters from cur to end
for (int i = cur; i < len; i++) {
Serial.print((char)buf[i]);
}
Serial.print(" \x1b[u");
}
/* /*
* read_string_until: Non-blocking replacement for Serial.readStringUntil().. * read_string_until: Non-blocking replacement for Serial.readStringUntil()..
* With delimeter '\n' acts as 'read line'. * With delimeter '\n' acts as 'read line'.
@@ -20,6 +58,7 @@ namespace serial_console
bool read_string_until(char delimiter, char *result, int max_len) bool read_string_until(char delimiter, char *result, int max_len)
{ {
static int index = 0; static int index = 0;
static int cur = 0;
int c; // read character, -1 if none int c; // read character, -1 if none
int cnt = 100; // limit on amount of iterations in one go; we're supposed to be non-blocking! int cnt = 100; // limit on amount of iterations in one go; we're supposed to be non-blocking!
@@ -28,43 +67,75 @@ namespace serial_console
--cnt; --cnt;
// backspaceF // backspaceF
if (c == 8) if (c == BACKSPACE_CHAR && cur > 0) {
{ // shift characters left from cursor position
if (index > 0) memmove(&result[cur - 1], &result[cur], index - cur);
{
if(temporary_config.remote_echo) Serial.print("\x08 \x08"); // overwrite last character with space and move cursor 1 back.
index--; index--;
cur--;
// move cursor left on terminal and redraw updated string
if(temporary_config.remote_echo) Serial.print("\x1b[D");
redraw(cur, index, result);
// handle ANSI escape sequences (arrow keys, delete key)
} else if (c == ESCAPE_CHAR) {
if ((c = Serial.read()) == -1)
break;
// Expect '[' character
if (c != CSI_CHAR)
continue;
if ((c = Serial.read()) == -1)
break;
// Left arrow key
if (c == LEFT_ARROW_CHAR && cur > 0) {
// move cursor left on terminal
if(temporary_config.remote_echo) Serial.print("\x1b[D");
cur--;
// Right arrow key
} else if(c == RIGHT_ARROW_CHAR && cur < index) {
// move cursor right on terminal
if(temporary_config.remote_echo) Serial.print("\x1b[C");
cur++;
// Delete key
} else if(c == DELETE_KEY_1_CHAR) {
if ((c = Serial.read()) == -1)
break;
if (c == DELETE_KEY_2_CHAR && cur < index) {
memmove(&result[cur], &result[cur + 1], index - cur - 1);
index--;
redraw(cur, index, result);
}
} }
continue;
}
if(temporary_config.remote_echo) Serial.print((char)c); // echo // Handle printable characters
} else if (c >= PRINT_CHAR_START && c <= PRINT_CHAR_END) {
// Buffer overflow handling: // Append character at the end
// start treating current buffer as invalid: if (index < max_len - 1 && cur == index) {
// - stop collecting more data if(temporary_config.remote_echo) Serial.print((char)c);
// - return false on delimeter, flushing everything collected, result[index++] = c;
// - restart collection from scratch after delimeter, cur++;
// - return control as normal. // Insert character in the middl
} else if (index < max_len - 1) {
if (index >= max_len - 1) memmove(&result[cur + 1], &result[cur], index - cur);
{ result[cur] = c;
if (c == delimiter) // got delimeter: flush buffer quietly, restart collection. index++;
{ if(temporary_config.remote_echo) Serial.print((char)c);
cur++;
redraw(cur, index, result);
} else if (c == delimiter) { // got delimeter: flush buffer quietly, restart collection.
index = 0; index = 0;
cur = 0;
return false; return false;
} }
else
continue; // discard any data past the end of the buffer, keep reading
}
result[index++] = c;
// delimiter was found // delimiter was found
if (c == delimiter) } else if (c == delimiter) {
{
if(temporary_config.remote_echo) Serial.println();
result[index] = '\0'; // Null-terminate the string result[index] = '\0'; // Null-terminate the string
index = 0; index = 0;
cur = 0;
return true; // Success: Delimiter found return true; // Success: Delimiter found
} }
} }