[Concept,16/19] expo: Add SCENEOF_MULTILINE flag for textedit

Message ID 20260130035849.3580212-17-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 a new flag SCENEOF_MULTILINE that allows the textedit widget to
accept multiline input. When this flag is set, pressing Enter inserts
a newline character instead of closing the textedit.

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

 boot/editenv.c      |  2 ++
 boot/scene_txtin.c  | 21 ++++++++++++++++++++-
 include/expo.h      |  2 ++
 test/boot/editenv.c | 22 +++++++++++-----------
 test/boot/expo.c    | 16 ++++++++++++++--
 5 files changed, 49 insertions(+), 14 deletions(-)
  

Patch

diff --git a/boot/editenv.c b/boot/editenv.c
index de7df6fe3fa..fd7670263b7 100644
--- a/boot/editenv.c
+++ b/boot/editenv.c
@@ -47,6 +47,7 @@  static int editenv_setup(struct expo *exp, struct udevice *dev,
 	ret = scene_texted(scn, "textedit", EDITENV_OBJ_TEXTEDIT, 70, &ted);
 	if (ret < 0)
 		return log_msg_ret("ted", ret);
+	ted->obj.flags |= SCENEOF_MULTILINE;
 
 	ret = scene_obj_set_bbox(scn, EDITENV_OBJ_TEXTEDIT, 50, 200, 1300, 400);
 	if (ret < 0)
@@ -122,6 +123,7 @@  int expo_editenv_init(const char *varname, const char *value,
 	if (ret) {
 		expo_destroy(exp);
 		return log_msg_ret("set", ret);
+
 	}
 
 	return 0;
diff --git a/boot/scene_txtin.c b/boot/scene_txtin.c
index 8b49daddd56..ab4fd5056a0 100644
--- a/boot/scene_txtin.c
+++ b/boot/scene_txtin.c
@@ -342,13 +342,32 @@  int scene_txtin_send_key(struct scene_obj *obj, struct scene_txtin *tin,
 			log_debug("menu quit\n");
 		}
 		break;
-	case BKEY_SELECT:
+	case BKEY_SAVE:
 		if (!open)
 			break;
+		/* Accept contents even in multiline mode */
 		event->type = EXPOACT_CLOSE;
 		event->select.id = obj->id;
 		scene_txtin_close(scn, tin);
 		break;
+	case BKEY_SELECT:
+		if (!open)
+			break;
+		if (obj->flags & SCENEOF_MULTILINE) {
+			char *buf = cls->buf;
+			int wlen = cls->eol_num - cls->num;
+
+			/* Insert newline at cursor position */
+			memmove(&buf[cls->num + 1], &buf[cls->num], wlen);
+			buf[cls->num] = '\n';
+			cls->num++;
+			cls->eol_num++;
+		} else {
+			event->type = EXPOACT_CLOSE;
+			event->select.id = obj->id;
+			scene_txtin_close(scn, tin);
+		}
+		break;
 	case BKEY_UP:
 		cread_line_process_ch(cls, CTL_CH('p'));
 		break;
diff --git a/include/expo.h b/include/expo.h
index 5a35d72c58f..3846b58abdd 100644
--- a/include/expo.h
+++ b/include/expo.h
@@ -321,6 +321,7 @@  enum scene_obj_align {
  * @SCENEOF_MANUAL: manually arrange the items associated with this object
  * @SCENEOF_DIRTY: object has been modified and needs to be redrawn
  * @SCENEOF_PASSWORD: textline input should show stars instead of characters
+ * @SCENEOF_MULTILINE: textedit allows multiline input (Enter adds newline)
  * @SCENEOF_LAST: used just as a check for the size of the flags mask
  */
 enum scene_obj_flags_t {
@@ -335,6 +336,7 @@  enum scene_obj_flags_t {
 	SCENEOF_MANUAL		= BIT(8),
 	SCENEOF_DIRTY		= BIT(9),
 	SCENEOF_PASSWORD	= BIT(10),
+	SCENEOF_MULTILINE	= BIT(11),
 
 	SCENEOF_LAST,	/* check for size of flags below */
 };
diff --git a/test/boot/editenv.c b/test/boot/editenv.c
index 20273c53647..69e543ea51f 100644
--- a/test/boot/editenv.c
+++ b/test/boot/editenv.c
@@ -74,10 +74,10 @@  static int editenv_test_base(struct unit_test_state *uts)
 	int ret;
 
 	/*
-	 * Type "test" then press Enter to accept
-	 * \x0d is Ctrl-M (Enter/carriage return)
+	 * Type "test" then press Ctrl-S to save
+	 * \x13 is Ctrl-S
 	 */
-	console_in_puts("test\x0d");
+	console_in_puts("test\x13");
 	ret = expo_editenv("myvar", NULL, buf, sizeof(buf));
 	ut_assertok(ret);
 	ut_asserteq_str("test", buf);
@@ -94,9 +94,9 @@  static int editenv_test_initial(struct unit_test_state *uts)
 
 	/*
 	 * Start with "world", go to start with Ctrl-A, type "hello ", then
-	 * press Enter
+	 * press Ctrl-S to save
 	 */
-	console_in_puts("\x01hello \x0d");
+	console_in_puts("\x01hello \x13");
 	ret = expo_editenv("myvar", "world", buf, sizeof(buf));
 	ut_assertok(ret);
 	ut_asserteq_str("hello world", buf);
@@ -138,11 +138,11 @@  static int editenv_test_video(struct unit_test_state *uts)
 	ut_assertok(vidconsole_select_font(con, NULL, NULL, 30));
 
 	/*
-	 * Navigate with up arrow, insert text, then press Enter. The up arrow
-	 * should be converted to Ctrl-P by scene_txtin_send_key().
-	 * \x1b[A is the escape sequence for up arrow
+	 * Navigate with up arrow, insert text, then press Ctrl-S to save.
+	 * The up arrow should be converted to Ctrl-P by scene_txtin_send_key().
+	 * \x1b[A is the escape sequence for up arrow, \x13 is Ctrl-S (save)
 	 */
-	console_in_puts("\x1b[A!\x0d");
+	console_in_puts("\x1b[A!\x13");
 	ret = expo_editenv("testvar", initial, buf, sizeof(buf));
 	ut_assertok(ret);
 
@@ -179,11 +179,11 @@  static int editenv_test_funcs(struct unit_test_state *uts)
 	ut_assertok(editenv_send(&info, BKEY_DOWN));
 	ut_asserteq(16611, ut_check_video(uts, "down"));
 
-	/* Type a character and press Enter to accept */
+	/* Type a character and press Ctrl-S to save */
 	ut_assertok(editenv_send(&info, '*'));
 	ut_asserteq(16689, ut_check_video(uts, "insert"));
 
-	ut_asserteq(1, editenv_send(&info, BKEY_SELECT));
+	ut_asserteq(1, editenv_send(&info, BKEY_SAVE));
 
 	/* The '*' should be appended to the initial text */
 	ut_assert(strstr(expo_editenv_result(&info), "editor.*"));
diff --git a/test/boot/expo.c b/test/boot/expo.c
index 824fd0cca38..f598b9cb86c 100644
--- a/test/boot/expo.c
+++ b/test/boot/expo.c
@@ -1703,7 +1703,19 @@  static int expo_render_textedit(struct unit_test_state *uts)
 	ut_assertok(expo_render(exp));
 	ut_asserteq(21083, video_compress_fb(uts, dev, false));
 
-	/* close the textedit with Enter (BKEY_SELECT) */
+	/* set multiline mode and check Enter inserts newline */
+	ted->obj.flags |= SCENEOF_MULTILINE;
+	ut_assertok(expo_send_key(exp, BKEY_SELECT));
+	ut_asserteq(90, ted->tin.cls.num);
+	ut_asserteq(90, ted->tin.cls.eol_num);
+	ut_assert(ted->obj.flags & SCENEOF_OPEN);
+	ut_asserteq('\n', ((char *)abuf_data(&ted->tin.buf))[89]);
+	ut_assertok(scene_arrange(scn));
+	ut_assertok(expo_render(exp));
+	ut_asserteq(21091, video_compress_fb(uts, dev, false));
+
+	/* clear multiline mode, close the textedit with Enter (BKEY_SELECT) */
+	ted->obj.flags &= ~SCENEOF_MULTILINE;
 	ut_assertok(expo_send_key(exp, BKEY_SELECT));
 	ut_assertok(expo_action_get(exp, &act));
 	ut_asserteq(EXPOACT_CLOSE, act.type);
@@ -1713,7 +1725,7 @@  static int expo_render_textedit(struct unit_test_state *uts)
 	/* check the textedit is closed and text is changed */
 	ut_asserteq(0, ted->obj.flags & SCENEOF_OPEN);
 	ut_asserteq_str("his\nis the initial contents of the text "
-		"editor but it is ely that more will be added latr",
+		"editor but it is ely that more will be added latr\n",
 		abuf_data(&ted->tin.buf));
 	ut_assertok(scene_arrange(scn));
 	ut_assertok(expo_render(exp));