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 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- 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); -- 2.43.0