[Concept,15/19] cli: Add Ctrl+Left/Right arrow support for word navigation

Message ID 20260130035849.3580212-16-simon.glass@canonical.com
State New
Headers
Series Enhanced command-line editing with undo/redo support |

Commit Message

Simon Glass Jan. 30, 2026, 3:58 a.m. UTC
  Add support for Ctrl+Left and Ctrl+Right arrow keys to move the cursor
by word in the command line editor. Ctrl+Left moves backward to the
start of the previous word, and Ctrl+Right moves forward to the end of
the next word.

Decode the escape sequences (ESC[1;5D and ESC[1;5C) arein cli_getch.c
and convert then to CTL_CH('r') and CTL_CH('t') respectively. Handle the
actual word-movement logic is then handled in cli_readline.c, guarded by
CONFIG_CMDLINE_EDITOR

Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: Simon Glass <simon.glass@canonical.com>
---

 common/cli_getch.c    | 25 +++++++++++++++++++++++++
 common/cli_readline.c | 31 +++++++++++++++++++++++++++++++
 test/boot/editenv.c   |  8 ++++++++
 3 files changed, 64 insertions(+)
  

Patch

diff --git a/common/cli_getch.c b/common/cli_getch.c
index a5ed6eb6fcf..8df1997911a 100644
--- a/common/cli_getch.c
+++ b/common/cli_getch.c
@@ -114,6 +114,12 @@  static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
 			if (cch->esc_save[2] == '2')
 				act = ESC_SAVE;
 			break;
+		case ';':
+			/* Ctrl+arrow: ESC [ 1 ; */
+			if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) &&
+			    cch->esc_save[2] == '1')
+				act = ESC_SAVE;
+			break;
 		}
 		break;
 	case 4:
@@ -122,12 +128,31 @@  static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
 		case '1':
 			act = ESC_SAVE;
 			break;		/* bracketed paste */
+		case '5':
+			/* Ctrl+arrow: ESC [ 1 ; 5 */
+			if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) &&
+			    cch->esc_save[3] == ';')
+				act = ESC_SAVE;
+			break;
 		}
 		break;
 	case 5:
 		if (ichar == '~') {	/* bracketed paste */
 			ichar = 0;
 			act = ESC_CONVERTED;
+		} else if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) &&
+			   cch->esc_save[4] == '5') {
+			/* Ctrl+arrow: ESC [ 1 ; 5 D/C */
+			switch (ichar) {
+			case 'D':	/* Ctrl+<- key */
+				ichar = CTL_CH('r');
+				act = ESC_CONVERTED;
+				break;	/* pass to backward-word handler */
+			case 'C':	/* Ctrl+-> key */
+				ichar = CTL_CH('t');
+				act = ESC_CONVERTED;
+				break;	/* pass to forward-word handler */
+			}
 		}
 	}
 
diff --git a/common/cli_readline.c b/common/cli_readline.c
index d2b02933fd7..4c25e9a04ba 100644
--- a/common/cli_readline.c
+++ b/common/cli_readline.c
@@ -333,6 +333,37 @@  int cread_line_process_ch(struct cli_line_state *cls, char ichar)
 			cls->num--;
 		}
 		break;
+	case CTL_CH('r'):	/* backward-word */
+		if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) && cls->num) {
+			uint pos = cls->num;
+
+			/* skip spaces before word */
+			while (pos > 0 && buf[pos - 1] == ' ')
+				pos--;
+			/* skip word characters */
+			while (pos > 0 && buf[pos - 1] != ' ')
+				pos--;
+			cls_putchars(cls, cls->num - pos, CTL_BACKSPACE);
+			cls->num = pos;
+		}
+		break;
+	case CTL_CH('t'):	/* forward-word */
+		if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) && cls->num < cls->eol_num) {
+			uint pos = cls->num;
+
+			/* skip spaces after cursor */
+			while (pos < cls->eol_num && buf[pos] == ' ') {
+				cls_putch(cls, buf[pos]);
+				pos++;
+			}
+			/* skip word characters */
+			while (pos < cls->eol_num && buf[pos] != ' ') {
+				cls_putch(cls, buf[pos]);
+				pos++;
+			}
+			cls->num = pos;
+		}
+		break;
 	case CTL_CH('d'):
 		if (cls->num < cls->eol_num) {
 			uint wlen;
diff --git a/test/boot/editenv.c b/test/boot/editenv.c
index 0f9db54474d..20273c53647 100644
--- a/test/boot/editenv.c
+++ b/test/boot/editenv.c
@@ -171,6 +171,14 @@  static int editenv_test_funcs(struct unit_test_state *uts)
 	ut_assertok(expo_editenv_init("testvar", initial, &info));
 	ut_asserteq(16611, ut_check_video(uts, "init"));
 
+	/* Navigate up to previous line */
+	ut_assertok(editenv_send(&info, BKEY_UP));
+	ut_asserteq(16684, ut_check_video(uts, "up"));
+
+	/* Navigate back down */
+	ut_assertok(editenv_send(&info, BKEY_DOWN));
+	ut_asserteq(16611, ut_check_video(uts, "down"));
+
 	/* Type a character and press Enter to accept */
 	ut_assertok(editenv_send(&info, '*'));
 	ut_asserteq(16689, ut_check_video(uts, "insert"));