@@ -66,6 +66,15 @@ config CMDLINE_EDITOR
- Undo/redo support (Ctrl+Z / Ctrl+Shift+Z)
- Yank/paste of killed text (Ctrl+Y)
+config CMDLINE_UNDO
+ bool "Support undo in command-line editing"
+ depends on CMDLINE_EDITOR
+ default y
+ help
+ Enable an undo buffer for command-line editing. When enabled,
+ pressing Ctrl+Z restores the previous state of the edit buffer.
+ This uses additional memory to store the undo state.
+
config CMDLINE_PS_SUPPORT
bool "Enable support for changing the command prompt string at run-time"
depends on HUSH_PARSER
@@ -10,6 +10,7 @@ obj-y += main.o
obj-y += memtop.o
obj-y += exports.o
obj-y += cli_getch.o cli_simple.o cli_readline.o
+obj-$(CONFIG_CMDLINE_UNDO) += cli_undo.o
obj-$(CONFIG_HUSH_OLD_PARSER) += cli_hush.o
obj-$(CONFIG_HUSH_MODERN_PARSER) += cli_hush_modern.o
obj-$(CONFIG_AUTOBOOT) += autoboot.o
@@ -358,6 +358,8 @@ static void cread_end_of_line(struct cli_line_state *cls)
#define REFRESH_TO_EOL() GOTO_LINE_END(cls->eol_num)
#endif
+/* undo/yank functions are in cli_undo.c when CMDLINE_UNDO is enabled */
+
static void cread_add_char(struct cli_line_state *cls, char ichar, int insert,
uint *num, uint *eol_num, char *buf, uint len)
{
@@ -484,9 +486,21 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar)
cls->eol_num--;
}
break;
- case CTL_CH('k'):
+ case CTL_CH('k'): {
+ uint erase_to = cls->eol_num;
+
+ ed = cli_editor(cls);
+ if (ed && ed->multiline) {
+ char *nl = strchr(&buf[cls->num], '\n');
+
+ if (nl)
+ erase_to = nl - buf;
+ }
+ cread_save_undo(cls);
+ cread_save_yank(cls, &buf[cls->num], erase_to - cls->num);
cread_erase_to_eol(cls);
break;
+ }
case CTL_CH('e'):
REFRESH_TO_EOL();
break;
@@ -505,6 +519,8 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar)
/* now delete chars from base to cls->num */
wlen = cls->num - base;
+ cread_save_undo(cls);
+ cread_save_yank(cls, &buf[base], wlen);
cls->eol_num -= wlen;
memmove(&buf[base], &buf[cls->num],
cls->eol_num - base + 1);
@@ -517,7 +533,24 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar)
}
break;
case CTL_CH('x'):
+ if (CONFIG_IS_ENABLED(CMDLINE_UNDO)) {
+ cread_save_undo(cls);
+ cread_save_yank(cls, buf, cls->eol_num);
+ BEGINNING_OF_LINE();
+ cread_erase_to_eol(cls);
+ }
+ break;
+ case CTL_CH('y'):
+#if CONFIG_IS_ENABLED(CMDLINE_UNDO)
+ cread_yank(cls);
+#endif
+ break;
+ case CTL_CH('z'):
+ cread_restore_undo(cls);
+ break;
case CTL_CH('u'):
+ cread_save_undo(cls);
+ cread_save_yank(cls, buf, cls->eol_num);
BEGINNING_OF_LINE();
cread_erase_to_eol(cls);
break;
@@ -630,6 +663,27 @@ void cli_cread_init(struct cli_line_state *cls, char *buf, uint buf_size)
cls->len = buf_size;
}
+void cli_cread_init_undo(struct cli_line_state *cls, char *buf, uint buf_size)
+{
+ cli_cread_init(cls, buf, buf_size);
+ if (CONFIG_IS_ENABLED(CMDLINE_UNDO)) {
+ struct cli_editor_state *ed = cli_editor(cls);
+
+ abuf_init_size(&ed->undo.buf, buf_size);
+ abuf_init_size(&ed->yank, buf_size);
+ }
+}
+
+void cli_cread_uninit(struct cli_line_state *cls)
+{
+ if (CONFIG_IS_ENABLED(CMDLINE_UNDO)) {
+ struct cli_editor_state *ed = cli_editor(cls);
+
+ abuf_uninit(&ed->undo.buf);
+ abuf_uninit(&ed->yank);
+ }
+}
+
void cli_cread_add_initial(struct cli_line_state *cls)
{
int init_len = strlen(cls->buf);
new file mode 100644
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * CLI undo/yank support
+ *
+ * Copyright 2025 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <cli.h>
+#include <command.h>
+#include <stdio.h>
+#include <linux/string.h>
+#include <asm/global_data.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define CTL_BACKSPACE ('\b')
+
+/**
+ * cls_putch() - Output a character, using callback if available
+ *
+ * @cls: CLI line state
+ * @ch: Character to output
+ */
+static void cls_putch(struct cli_line_state *cls, int ch)
+{
+ struct cli_editor_state *ed = cli_editor(cls);
+
+ if (ed && ed->putch)
+ ed->putch(cls, ch);
+ else
+ putc(ch);
+}
+
+static void cls_putnstr(struct cli_line_state *cls, const char *str, size_t n)
+{
+ while (n-- > 0)
+ cls_putch(cls, *str++);
+}
+
+/**
+ * cls_putchars() - Output a character multiple times
+ *
+ * @cls: CLI line state
+ * @count: Number of times to output the character
+ * @ch: Character to output
+ */
+static void cls_putchars(struct cli_line_state *cls, int count, int ch)
+{
+ int i;
+
+ for (i = 0; i < count; i++)
+ cls_putch(cls, ch);
+}
+
+#define getcmd_cbeep(cls) cls_putch(cls, '\a')
+
+void cread_save_undo(struct cli_line_state *cls)
+{
+ struct cli_editor_state *ed = cli_editor(cls);
+ struct cli_undo_state *undo = &ed->undo;
+
+ if (abuf_size(&undo->buf)) {
+ memcpy(abuf_data(&undo->buf), cls->buf, cls->len);
+ undo->num = cls->num;
+ undo->eol_num = cls->eol_num;
+ }
+}
+
+void cread_restore_undo(struct cli_line_state *cls)
+{
+ struct cli_editor_state *ed = cli_editor(cls);
+ struct cli_undo_state *undo = &ed->undo;
+
+ if (!abuf_size(&undo->buf))
+ return;
+
+ /* go to start of line */
+ while (cls->num) {
+ cls_putch(cls, CTL_BACKSPACE);
+ cls->num--;
+ }
+
+ /* erase current content on screen */
+ cls_putchars(cls, cls->eol_num, ' ');
+ cls_putchars(cls, cls->eol_num, CTL_BACKSPACE);
+
+ /* restore from undo buffer */
+ memcpy(cls->buf, abuf_data(&undo->buf), cls->len);
+ cls->eol_num = undo->eol_num;
+
+ /* display restored content */
+ cls_putnstr(cls, cls->buf, cls->eol_num);
+
+ /* position cursor */
+ cls_putchars(cls, cls->eol_num - undo->num, CTL_BACKSPACE);
+ cls->num = undo->num;
+}
+
+void cread_save_yank(struct cli_line_state *cls, const char *text, uint len)
+{
+ struct cli_editor_state *ed = cli_editor(cls);
+
+ if (abuf_size(&ed->yank) && len > 0 && len < cls->len) {
+ memcpy(abuf_data(&ed->yank), text, len);
+ ed->yank_len = len;
+ }
+}
+
+void cread_yank(struct cli_line_state *cls)
+{
+ struct cli_editor_state *ed = cli_editor(cls);
+ char *buf = cls->buf;
+ uint i;
+
+ if (!abuf_size(&ed->yank) || !ed->yank_len)
+ return;
+
+ /* check if there's room */
+ if (cls->eol_num + ed->yank_len > cls->len - 1) {
+ getcmd_cbeep(cls);
+ return;
+ }
+
+ cread_save_undo(cls);
+
+ /* make room for yanked text */
+ memmove(&buf[cls->num + ed->yank_len], &buf[cls->num],
+ cls->eol_num - cls->num + 1);
+
+ /* insert yanked text */
+ memcpy(&buf[cls->num], abuf_data(&ed->yank), ed->yank_len);
+ cls->eol_num += ed->yank_len;
+
+ /* display from cursor to end */
+ cls_putnstr(cls, &buf[cls->num], cls->eol_num - cls->num);
+
+ /* move cursor to end of inserted text */
+ cls->num += ed->yank_len;
+ for (i = cls->num; i < cls->eol_num; i++)
+ cls_putch(cls, CTL_BACKSPACE);
+}
@@ -7,6 +7,7 @@
#ifndef __CLI_H
#define __CLI_H
+#include <abuf.h>
#include <stdbool.h>
#include <linux/types.h>
@@ -27,6 +28,19 @@ struct cli_ch_state {
struct cli_line_state;
+/**
+ * struct cli_undo_state - state for undo buffer
+ *
+ * @buf: Buffer for saved state
+ * @num: Saved cursor position
+ * @eol_num: Saved end-of-line position
+ */
+struct cli_undo_state {
+ struct abuf buf;
+ uint num;
+ uint eol_num;
+};
+
/**
* struct cli_editor_state - state for enhanced editing features
*
@@ -36,6 +50,9 @@ struct cli_line_state;
* @line_nav: Handle multi-line navigation (Ctrl-P/N)
* @multiline: true if input may contain multiple lines (enables
* Ctrl-P/N for line navigation instead of history)
+ * @undo: Undo ring buffer state
+ * @yank: Buffer for killed text (for Ctrl+Y yank)
+ * @yank_len: Length of killed text in yank buffer
*/
struct cli_editor_state {
/**
@@ -60,6 +77,15 @@ struct cli_editor_state {
* Ctrl-P/N for line navigation instead of history)
*/
bool multiline;
+
+ /** @undo: Undo state (if CONFIG_CMDLINE_UNDO) */
+ struct cli_undo_state undo;
+
+ /** @yank: Buffer for killed text (for Ctrl+Y yank) */
+ struct abuf yank;
+
+ /** @yank_len: Length of killed text in yank buffer */
+ uint yank_len;
};
/**
@@ -336,6 +362,24 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar);
*/
void cli_cread_init(struct cli_line_state *cls, char *buf, uint buf_size);
+/**
+ * cli_cread_init_undo() - Set up a new cread struct with undo support
+ *
+ * Like cli_cread_init() but also sets up the undo buffer.
+ *
+ * @cls: CLI line state
+ * @buf: Text buffer containing the initial text
+ * @buf_size: Buffer size, including nul terminator
+ */
+void cli_cread_init_undo(struct cli_line_state *cls, char *buf, uint buf_size);
+
+/**
+ * cli_cread_uninit() - Free resources allocated by cli_cread_init_undo()
+ *
+ * @cls: CLI line state
+ */
+void cli_cread_uninit(struct cli_line_state *cls);
+
/**
* cli_cread_add_initial() - Output initial buffer contents
*
@@ -349,4 +393,56 @@ void cli_cread_add_initial(struct cli_line_state *cls);
/** cread_print_hist_list() - Print the command-line history list */
void cread_print_hist_list(void);
+/*
+ * Undo/yank functions - implementations in cli_undo.c when CMDLINE_UNDO is enabled
+ */
+#if CONFIG_IS_ENABLED(CMDLINE_UNDO)
+/**
+ * cread_save_undo() - Save current state for undo
+ *
+ * @cls: CLI line state
+ */
+void cread_save_undo(struct cli_line_state *cls);
+
+/**
+ * cread_restore_undo() - Restore previous state from undo buffer
+ *
+ * @cls: CLI line state
+ */
+void cread_restore_undo(struct cli_line_state *cls);
+
+/**
+ * cread_save_yank() - Save killed text to yank buffer
+ *
+ * @cls: CLI line state
+ * @text: Text to save
+ * @len: Length of text
+ */
+void cread_save_yank(struct cli_line_state *cls, const char *text, uint len);
+
+/**
+ * cread_yank() - Insert yanked text at cursor position
+ *
+ * @cls: CLI line state
+ */
+void cread_yank(struct cli_line_state *cls);
+#else
+static inline void cread_save_undo(struct cli_line_state *cls)
+{
+}
+
+static inline void cread_restore_undo(struct cli_line_state *cls)
+{
+}
+
+static inline void cread_save_yank(struct cli_line_state *cls, const char *text,
+ uint len)
+{
+}
+
+static inline void cread_yank(struct cli_line_state *cls)
+{
+}
+#endif
+
#endif
@@ -1729,6 +1729,12 @@ static int expo_render_textedit(struct unit_test_state *uts)
ut_asserteq(86, ted->tin.cls.eol_num);
ut_asserteq('\n', ((char *)abuf_data(&ted->tin.buf))[85]);
+ /* Ctrl+Y yanks back the killed text "latr" */
+ ut_assertok(expo_send_key(exp, CTL_CH('y')));
+ ut_asserteq(89, ted->tin.cls.num);
+ ut_asserteq(90, ted->tin.cls.eol_num);
+ ut_asserteq('\n', ((char *)abuf_data(&ted->tin.buf))[89]);
+
/* clear multiline mode, close the textedit with Enter (BKEY_SELECT) */
ted->obj.flags &= ~SCENEOF_MULTILINE;
ut_assertok(expo_send_key(exp, BKEY_SELECT));
@@ -1740,11 +1746,11 @@ 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("This\ns the initial contents of the text "
- "editor but it is ely that more will be added \n",
+ "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));
- ut_asserteq(21099, video_compress_fb(uts, dev, false));
+ ut_asserteq(21251, video_compress_fb(uts, dev, false));
abuf_uninit(&buf);
abuf_uninit(&logo_copy);