From patchwork Fri Jan 30 03:58:40 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1792 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=G/QNuQlz; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AC91969738 for ; Thu, 29 Jan 2026 20:59:50 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id jqgzCPvZkhN6 for ; Thu, 29 Jan 2026 20:59:50 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0F1A3697CB for ; Thu, 29 Jan 2026 20:59:49 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 1F6AF697E8 for ; Thu, 29 Jan 2026 20:59:48 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id hIEAmEgLI2Mi for ; Thu, 29 Jan 2026 20:59:48 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.210.42; helo=mail-ot1-f42.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-ot1-f42.google.com (mail-ot1-f42.google.com [209.85.210.42]) by mail.u-boot.org (Postfix) with ESMTPS id 9675A69738 for ; Thu, 29 Jan 2026 20:59:43 -0700 (MST) Received: by mail-ot1-f42.google.com with SMTP id 46e09a7af769-7cfd95f77a3so1102361a34.0 for ; Thu, 29 Jan 2026 19:59:43 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745582; x=1770350382; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=d04mrpUelM5OQMjEgvAUUsV147RQGD2/eQU1nfX01u0=; b=G/QNuQlzWh6Q/FhbnX382Imw7HPkOQwxhvs3P8nf45YxUQl+qfo82ofGXKv4dnSiUL AC5n6Lj1sV4WK7p5/eYS4+FzIGV5bSDFVTmPViu35l5RZYfBgT5X8ktI+v4vtlb8Mi4O frCj3ztUunBXdrnmMOIk2JehT/kpC7lH2GK+Q= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745582; x=1770350382; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=d04mrpUelM5OQMjEgvAUUsV147RQGD2/eQU1nfX01u0=; b=f81dcVfYhmva0ItBPdfWp1lyQXSH7gk+NUFxbmZ3ZLrPWcUdvCES6IYxG0kVntHVTx Hndo2Y1sluFCw0doo90BCpe4BXBLTB45qVgR4EZu8f6J1TW2RtxZCVQyPXwERHA50POB NmsdQZQxWnnCR1oqV1+te58l6mHHwqqLbS+tHIBD3cALbqG74oQnycACcnfYaRkCNODZ S2shzeIiSDwsPSbvhAXKizoJ7MsJ1YZOOLDuFDPotDRvOD0He26OWdF86wNYvFFrYqpA 3Ersv5HPBMOUggNK/Xpd9JhYl3iF5hvV6iIOY1SB45odkVV9yUHXIcsX/xNaUSSQ6VLv ACGw== X-Gm-Message-State: AOJu0YxP4klFnPjuFIt5tpMTB5uRmaKdR+gvLPH9XmU9TZv72SQI9aPb Yqb4bu3DIVKtWjVCds/7SzEZuhZ1bJ15X/UZtOeAiqYjn6+683V6xdksr6D4UIKpei89Ic3vRx9 A6z2/6g== X-Gm-Gg: AZuq6aLu0PVwwVmkD+3DZJrPxoIbrBVb0ghzzgoFdzaW/yUs9BR8Rx4KfZkA5sNQEG0 /5RwxBp0n59a/AB556LEg4KQEZ2jVlLUkwcbPxpgr0kHwctYC4opgnH93Y0cTXIBxdn1/d1Mlpz vM1rUOfKNJWejHuejxTa4pxrA3tVaUlpyZ+BOtgF2JKdwA5tSG0dKipdpArggjbIyul/vg0pVdL Ak5ziuKSosAAyf3a1TcBSWMIWVHLsJj9zqssYR9yVob3tuFKRW/EFfUBpHHSH8ain4DONhE7xTK yTUp4NNGiXEQuXgKKd9bvN4gAZY1pQCVSB1A+kfpavM5r/dInEmIg0imYTXpGQlt/DSwuheqysj 6OZQdeehuL27bR/PNjiD0F/fG376i6FsrOnvQrJ+OeT8C527bqnoMoUozZkypMkQHKE8xziQ5iQ 32B0FOMLIgbtlOQM+fLB4ef81yHkw= X-Received: by 2002:a05:6820:260:b0:663:1239:9e9e with SMTP id 006d021491bc7-6631239a1eamr218693eaf.55.1769745582297; Thu, 29 Jan 2026 19:59:42 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:41 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:40 -0700 Message-ID: <20260130035849.3580212-18-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: 7TD3444OXGCMMEP52QNJKA4H7V7CBRAV X-Message-ID-Hash: 7TD3444OXGCMMEP52QNJKA4H7V7CBRAV X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 17/19] cli: Make Ctrl+K clear to end of line in multiline mode List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: In multiline mode, Ctrl+K (kill to end of line) currently erases all text from the cursor to the end of the buffer. This is not the expected behaviour for multiline editing. Update cread_erase_to_eol() to only erase to the next newline character in multiline mode, preserving text on subsequent lines. Use a parameterized ERASE_TO() macro to share the erase logic between the multiline-aware function (when CMDLINE_EDITOR is enabled) and a simpler version (when disabled), avoiding code growth on boards without CMDLINE_EDITOR Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- common/cli_readline.c | 114 +++++++++++++++++++++++++++++++++++++++--- test/boot/editenv.c | 17 +++++-- test/boot/expo.c | 33 ++++++++---- 3 files changed, 144 insertions(+), 20 deletions(-) diff --git a/common/cli_readline.c b/common/cli_readline.c index 4c25e9a04ba..847b49450b5 100644 --- a/common/cli_readline.c +++ b/common/cli_readline.c @@ -231,13 +231,86 @@ void cread_print_hist_list(void) } } -#define BEGINNING_OF_LINE() { \ - while (cls->num) { \ +#define GOTO_LINE_START(target) { \ + while (cls->num > (target)) { \ cls_putch(cls, CTL_BACKSPACE); \ cls->num--; \ } \ } +#define ERASE_TO(erase_to) { \ + if (cls->num < (erase_to)) { \ + uint wlen = (erase_to) - cls->num; \ + \ + /* erase characters on screen */ \ + printf("%*s", wlen, ""); \ + while (wlen--) \ + cls_putch(cls, CTL_BACKSPACE); \ + \ + /* remove characters from buffer */ \ + memmove(&buf[cls->num], &buf[erase_to], \ + cls->eol_num - (erase_to) + 1); \ + cls->eol_num -= (erase_to) - cls->num; \ + } \ +} + +#if CONFIG_IS_ENABLED(CMDLINE_EDITOR) +/** + * cread_start_of_line() - Move cursor to start of line + * + * In multiline mode, moves to the character after the previous newline. + * Otherwise moves to position 0. + * + * @cls: CLI line state + */ +static void cread_start_of_line(struct cli_line_state *cls) +{ + struct cli_editor_state *ed = cli_editor(cls); + uint target = 0; + + if (ed && ed->multiline) { + char *buf = cls->buf; + uint i; + + /* find previous newline */ + for (i = cls->num; i > 0; i--) { + if (buf[i - 1] == '\n') { + target = i; + break; + } + } + } + GOTO_LINE_START(target); +} +#define BEGINNING_OF_LINE() cread_start_of_line(cls) +#else +#define BEGINNING_OF_LINE() GOTO_LINE_START(0) +#endif + +#if CONFIG_IS_ENABLED(CMDLINE_EDITOR) +static void cread_erase_to_eol(struct cli_line_state *cls) +{ + struct cli_editor_state *ed = cli_editor(cls); + char *buf = cls->buf; + uint erase_to; + + if (cls->num >= cls->eol_num) + return; + + /* + * In multiline mode, only erase to end of current line (next newline + * or end of buffer) + */ + erase_to = cls->eol_num; + if (ed && ed->multiline) { + char *nl = strchr(&buf[cls->num], '\n'); + + if (nl) + erase_to = nl - buf; + } + ERASE_TO(erase_to); +} +#else static void cread_erase_to_eol(struct cli_line_state *cls) { if (cls->num < cls->eol_num) { @@ -247,15 +320,44 @@ static void cread_erase_to_eol(struct cli_line_state *cls) } while (--cls->eol_num > cls->num); } } +#endif -#define REFRESH_TO_EOL() { \ - if (cls->num < cls->eol_num) { \ - uint wlen = cls->eol_num - cls->num; \ +#define GOTO_LINE_END(target) { \ + if (cls->num < (target)) { \ + uint wlen = (target) - cls->num; \ cls_putnstr(cls, buf + cls->num, wlen); \ - cls->num = cls->eol_num; \ + cls->num = (target); \ } \ } +#if CONFIG_IS_ENABLED(CMDLINE_EDITOR) +/** + * cread_end_of_line() - Move cursor to end of line + * + * In multiline mode, moves to the next newline character. + * Otherwise moves to end of buffer. + * + * @cls: CLI line state + */ +static void cread_end_of_line(struct cli_line_state *cls) +{ + struct cli_editor_state *ed = cli_editor(cls); + char *buf = cls->buf; + uint target = cls->eol_num; + + if (ed && ed->multiline) { + char *nl = strchr(&buf[cls->num], '\n'); + + if (nl) + target = nl - buf; + } + GOTO_LINE_END(target); +} +#define REFRESH_TO_EOL() cread_end_of_line(cls) +#else +#define REFRESH_TO_EOL() GOTO_LINE_END(cls->eol_num) +#endif + static void cread_add_char(struct cli_line_state *cls, char ichar, int insert, uint *num, uint *eol_num, char *buf, uint len) { diff --git a/test/boot/editenv.c b/test/boot/editenv.c index 69e543ea51f..ab3c6648886 100644 --- a/test/boot/editenv.c +++ b/test/boot/editenv.c @@ -179,15 +179,22 @@ 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 Ctrl-S to save */ + /* Navigate with up arrow and insert '*' */ + ut_assertok(editenv_send(&info, BKEY_UP)); + ut_asserteq(16684, ut_check_video(uts, "up2")); + ut_assertok(editenv_send(&info, '*')); - ut_asserteq(16689, ut_check_video(uts, "insert")); + ut_asserteq(16877, ut_check_video(uts, "insert")); + + /* Use Ctrl-K to kill to end of line (stops at the existing newline) */ + ut_assertok(editenv_send(&info, CTL_CH('k'))); + ut_asserteq(16033, ut_check_video(uts, "kill")); ut_asserteq(1, editenv_send(&info, BKEY_SAVE)); - /* The '*' should be appended to the initial text */ - ut_assert(strstr(expo_editenv_result(&info), "editor.*")); - ut_asserteq(16689, ut_check_video(uts, "save")); + /* The '*' is inserted after "tes", Ctrl-K killed "ted properly." */ + ut_assert(strstr(expo_editenv_result(&info), "tes*\n")); + ut_asserteq(16033, ut_check_video(uts, "save")); expo_editenv_uninit(&info); diff --git a/test/boot/expo.c b/test/boot/expo.c index f598b9cb86c..366183e4a79 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -1681,16 +1681,16 @@ static int expo_render_textedit(struct unit_test_state *uts) ut_assertok(expo_render(exp)); ut_asserteq(21211, video_compress_fb(uts, dev, false)); - /* go to start of buffer and delete a character */ + /* go to start of line (multiline Home goes to start of current line) */ ut_assertok(expo_send_key(exp, CTL_CH('a'))); - ut_asserteq(0, ted->tin.cls.num); + ut_asserteq(5, ted->tin.cls.num); ut_asserteq(91, ted->tin.cls.eol_num); ut_assertok(expo_send_key(exp, CTL_CH('d'))); - ut_asserteq(0, ted->tin.cls.num); + ut_asserteq(5, ted->tin.cls.num); ut_asserteq(90, ted->tin.cls.eol_num); ut_assertok(scene_arrange(scn)); ut_assertok(expo_render(exp)); - ut_asserteq(21147, video_compress_fb(uts, dev, false)); + ut_asserteq(21174, video_compress_fb(uts, dev, false)); /* go to end of buffer and backspace */ ut_assertok(expo_send_key(exp, CTL_CH('e'))); @@ -1701,7 +1701,7 @@ static int expo_render_textedit(struct unit_test_state *uts) ut_asserteq(89, ted->tin.cls.eol_num); ut_assertok(scene_arrange(scn)); ut_assertok(expo_render(exp)); - ut_asserteq(21083, video_compress_fb(uts, dev, false)); + ut_asserteq(21079, video_compress_fb(uts, dev, false)); /* set multiline mode and check Enter inserts newline */ ted->obj.flags |= SCENEOF_MULTILINE; @@ -1712,7 +1712,22 @@ static int expo_render_textedit(struct unit_test_state *uts) 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)); + ut_asserteq(21109, video_compress_fb(uts, dev, false)); + + /* go back 5 characters (before the newline) and use Ctrl+K */ + ut_assertok(expo_send_key(exp, CTL_CH('b'))); + ut_assertok(expo_send_key(exp, CTL_CH('b'))); + ut_assertok(expo_send_key(exp, CTL_CH('b'))); + ut_assertok(expo_send_key(exp, CTL_CH('b'))); + ut_assertok(expo_send_key(exp, CTL_CH('b'))); + ut_asserteq(85, ted->tin.cls.num); + ut_asserteq(90, ted->tin.cls.eol_num); + + /* Ctrl+K in multiline mode should only delete to the newline */ + ut_assertok(expo_send_key(exp, CTL_CH('k'))); + ut_asserteq(85, ted->tin.cls.num); + ut_asserteq(86, ted->tin.cls.eol_num); + ut_asserteq('\n', ((char *)abuf_data(&ted->tin.buf))[85]); /* clear multiline mode, close the textedit with Enter (BKEY_SELECT) */ ted->obj.flags &= ~SCENEOF_MULTILINE; @@ -1724,12 +1739,12 @@ 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\n", + ut_asserteq_str("This\ns the initial contents of the text " + "editor but it is ely that more will be added \n", abuf_data(&ted->tin.buf)); ut_assertok(scene_arrange(scn)); ut_assertok(expo_render(exp)); - ut_asserteq(21230, video_compress_fb(uts, dev, false)); + ut_asserteq(21099, video_compress_fb(uts, dev, false)); abuf_uninit(&buf); abuf_uninit(&logo_copy);