@@ -11,6 +11,7 @@
#include <cli.h>
#include <expo.h>
#include <log.h>
+#include <malloc.h>
#include <menu.h>
#include <video_console.h>
#include <linux/errno.h>
@@ -18,6 +19,12 @@
#include <linux/string.h>
#include "scene_internal.h"
+#ifdef CONFIG_CMDLINE_UNDO_COUNT
+#define UNDO_COUNT CONFIG_CMDLINE_UNDO_COUNT
+#else
+#define UNDO_COUNT 64
+#endif
+
int scene_txtin_init(struct scene_txtin *tin, uint size, uint line_chars)
{
char *buf;
@@ -161,6 +168,10 @@ static void scene_txtin_putch(struct cli_line_state *cls, int ch)
void scene_txtin_close(struct scene *scn, struct scene_txtin *tin)
{
+ struct cli_line_state *cls = &tin->cls;
+
+ cli_cread_uninit(cls);
+
/* cursor is not needed now */
vidconsole_readline_end(scn->expo->cons, tin->ctx);
}
@@ -265,7 +276,7 @@ int scene_txtin_open(struct scene *scn, struct scene_obj *obj,
struct udevice *cons = scn->expo->cons;
struct scene_obj_txt *txt;
void *ctx;
- int ret;
+ int ret, i;
ctx = tin->ctx;
if (!ctx) {
@@ -286,10 +297,31 @@ int scene_txtin_open(struct scene *scn, struct scene_obj *obj,
vidconsole_set_cursor_pos(cons, ctx, txt->obj.bbox.x0, txt->obj.bbox.y0);
vidconsole_entry_start(cons, ctx);
- cli_cread_init(cls, abuf_data(&tin->buf), abuf_size(&tin->buf));
+ cli_cread_init_undo(cls, abuf_data(&tin->buf), abuf_size(&tin->buf));
cls->insert = true;
ed->putch = scene_txtin_putch;
cls->priv = scn;
+
+ /* Initialise undo ring buffer */
+ alist_init_struct(&ed->undo.pos, struct cli_undo_pos);
+ for (i = 0; i < UNDO_COUNT; i++) {
+ struct cli_undo_pos *pos;
+
+ pos = alist_ensure(&ed->undo.pos, i, struct cli_undo_pos);
+ abuf_init_size(&pos->buf, abuf_size(&tin->buf));
+ }
+
+ /* Initialise redo ring buffer */
+ alist_init_struct(&ed->redo.pos, struct cli_undo_pos);
+ for (i = 0; i < UNDO_COUNT; i++) {
+ struct cli_undo_pos *pos;
+
+ pos = alist_ensure(&ed->redo.pos, i, struct cli_undo_pos);
+ abuf_init_size(&pos->buf, abuf_size(&tin->buf));
+ }
+
+ /* yank buffer is initialised by cli_cread_init_undo() above */
+
if (obj->type == SCENEOBJT_TEXTEDIT) {
ed->multiline = true;
ed->line_nav = scene_txtin_line_nav;
@@ -66,6 +66,8 @@ config CMDLINE_EDITOR
- Undo/redo support (Ctrl+Z / Ctrl+Shift+Z)
- Yank/paste of killed text (Ctrl+Y)
+ This uses additional memory to store the undo, redo, and yank buffers.
+
config CMDLINE_UNDO
bool "Support undo in command-line editing"
depends on CMDLINE_EDITOR
@@ -75,6 +77,19 @@ config CMDLINE_UNDO
pressing Ctrl+Z restores the previous state of the edit buffer.
This uses additional memory to store the undo state.
+config CMDLINE_UNDO_COUNT
+ int "Number of undo levels"
+ depends on CMDLINE_UNDO
+ default 64
+ range 1 64
+ help
+ Number of undo/redo levels to support. Each level requires memory
+ to store a copy of the edit buffer. With multiple levels,
+ pressing Ctrl+Z repeatedly undoes progressively older changes, and
+ Ctrl+Shift+Z redoes them. Set to 1 for single-level undo/redo, or
+ higher for multi-level. Note that any new edit clears the redo
+ history.
+
config CMDLINE_PS_SUPPORT
bool "Enable support for changing the command prompt string at run-time"
depends on HUSH_PARSER
@@ -134,14 +134,20 @@ static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
cch->esc_save[3] == ';')
act = ESC_SAVE;
break;
+ case '6':
+ /* Ctrl+Shift+key: ESC [ 1 ; 6 */
+ 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') {
+ }
+ if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) && cch->esc_save[4] == '5') {
/* Ctrl+arrow: ESC [ 1 ; 5 D/C */
switch (ichar) {
case 'D': /* Ctrl+<- key */
@@ -154,6 +160,15 @@ static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
break; /* pass to forward-word handler */
}
}
+ if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) && cch->esc_save[4] == '6') {
+ /* Ctrl+Shift+key: ESC [ 1 ; 6 x */
+ switch (ichar) {
+ case 'z': /* Ctrl+Shift+Z: redo */
+ ichar = CTL_CH('g');
+ act = ESC_CONVERTED;
+ break;
+ }
+ }
}
*actp = act;
@@ -113,6 +113,14 @@ static char hist_data[HIST_MAX][HIST_SIZE + 1];
#endif
static char *hist_list[HIST_MAX];
+#if CONFIG_IS_ENABLED(CMDLINE_EDITOR)
+#ifdef CONFIG_CMDLINE_UNDO_COUNT
+#define UNDO_COUNT CONFIG_CMDLINE_UNDO_COUNT
+#else
+#define UNDO_COUNT 64
+#endif
+#endif
+
#define add_idx_minus_one() ((hist_add_idx == 0) ? hist_max : hist_add_idx-1)
/**
@@ -358,7 +366,7 @@ 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 */
+/* undo/redo/yank functions are in cli_undo.c when CMDLINE_EDITOR 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)
@@ -374,6 +382,9 @@ static void cread_add_char(struct cli_line_state *cls, char ichar, int insert,
(*eol_num)++;
}
+ /* new edit invalidates redo history */
+ cread_clear_redo(cls);
+
if (insert) {
wlen = *eol_num - *num;
if (wlen > 1)
@@ -472,6 +483,7 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar)
if (cls->num < cls->eol_num) {
uint wlen;
+ cread_save_undo(cls);
wlen = cls->eol_num - cls->num - 1;
if (wlen) {
memmove(&buf[cls->num], &buf[cls->num + 1],
@@ -511,6 +523,7 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar)
if (cls->num) {
uint base, wlen;
+ cread_save_undo(cls);
for (base = cls->num - 1;
base >= 0 && buf[base] == ' ';)
base--;
@@ -519,7 +532,6 @@ 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],
@@ -541,13 +553,18 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar)
}
break;
case CTL_CH('y'):
-#if CONFIG_IS_ENABLED(CMDLINE_UNDO)
+#if CONFIG_IS_ENABLED(CMDLINE_EDITOR)
cread_yank(cls);
#endif
break;
case CTL_CH('z'):
cread_restore_undo(cls);
break;
+ case CTL_CH('g'):
+#if CONFIG_IS_ENABLED(CMDLINE_EDITOR)
+ cread_redo(cls);
+#endif
+ break;
case CTL_CH('u'):
cread_save_undo(cls);
cread_save_yank(cls, buf, cls->eol_num);
@@ -560,6 +577,7 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar)
if (cls->num) {
uint wlen;
+ cread_save_undo(cls);
wlen = cls->eol_num - cls->num;
cls->num--;
memmove(&buf[cls->num], &buf[cls->num + 1], wlen);
@@ -605,6 +623,8 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar)
break;
}
+ cread_save_undo(cls);
+
/* nuke the current line */
/* first, go home */
BEGINNING_OF_LINE();
@@ -632,6 +652,7 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar)
buf[cls->num] = '\0';
col = strlen(cls->prompt) + cls->eol_num;
num2 = cls->num;
+ cread_save_undo(cls);
if (cmd_auto_complete(cls->prompt, buf, &num2, &col)) {
col = num2 - cls->num;
cls->num += col;
@@ -669,17 +690,22 @@ void cli_cread_init_undo(struct cli_line_state *cls, char *buf, uint 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)) {
+ if (CONFIG_IS_ENABLED(CMDLINE_EDITOR)) {
struct cli_editor_state *ed = cli_editor(cls);
-
- abuf_uninit(&ed->undo.buf);
+ struct cli_undo_pos *pos;
+
+ alist_for_each(pos, &ed->undo.pos)
+ abuf_uninit(&pos->buf);
+ alist_uninit(&ed->undo.pos);
+ alist_for_each(pos, &ed->redo.pos)
+ abuf_uninit(&pos->buf);
+ alist_uninit(&ed->redo.pos);
abuf_uninit(&ed->yank);
}
}
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * CLI undo/yank support
+ * CLI undo/redo/yank support
*
* Copyright 2025 Google LLC
* Written by Simon Glass <sjg@chromium.org>
@@ -55,26 +55,134 @@ static void cls_putchars(struct cli_line_state *cls, int count, int ch)
#define getcmd_cbeep(cls) cls_putch(cls, '\a')
+void cread_save_redo(struct cli_line_state *cls)
+{
+ struct cli_editor_state *ed = cli_editor(cls);
+ struct cli_undo_state *redo = &ed->redo;
+ struct cli_undo_pos *pos;
+ uint idx;
+
+ if (!redo->pos.alloc)
+ return;
+
+ /* save at current head position */
+ idx = redo->head;
+ pos = alist_getw(&redo->pos, idx, struct cli_undo_pos);
+ memcpy(abuf_data(&pos->buf), cls->buf, cls->len);
+ pos->num = cls->num;
+ pos->eol_num = cls->eol_num;
+
+ /* advance head (ring buffer) */
+ redo->head = (redo->head + 1) % redo->pos.alloc;
+
+ /* track how many redo levels are available */
+ if (redo->count < redo->pos.alloc)
+ redo->count++;
+}
+
+void cread_clear_redo(struct cli_line_state *cls)
+{
+ struct cli_editor_state *ed = cli_editor(cls);
+
+ ed->redo.count = 0;
+ ed->redo.head = 0;
+}
+
void cread_save_undo(struct cli_line_state *cls)
{
struct cli_editor_state *ed = cli_editor(cls);
struct cli_undo_state *undo = &ed->undo;
+ struct cli_undo_pos *pos;
+ uint idx;
- if (abuf_size(&undo->buf)) {
- memcpy(abuf_data(&undo->buf), cls->buf, cls->len);
- undo->num = cls->num;
- undo->eol_num = cls->eol_num;
- }
+ if (!undo->pos.alloc)
+ return;
+
+ /* save at current head position */
+ idx = undo->head;
+ pos = alist_getw(&undo->pos, idx, struct cli_undo_pos);
+ memcpy(abuf_data(&pos->buf), cls->buf, cls->len);
+ pos->num = cls->num;
+ pos->eol_num = cls->eol_num;
+
+ /* advance head (ring buffer) */
+ undo->head = (undo->head + 1) % undo->pos.alloc;
+
+ /* track how many undo levels are available */
+ if (undo->count < undo->pos.alloc)
+ undo->count++;
+
+ /* new edit invalidates redo history */
+ cread_clear_redo(cls);
}
void cread_restore_undo(struct cli_line_state *cls)
+{
+ struct cli_undo_state *undo = &cli_editor(cls)->undo;
+ const struct cli_undo_pos *pos;
+ uint idx;
+
+ if (!undo->pos.alloc || !undo->count)
+ return;
+
+ /* save current state to redo buffer before restoring */
+ cread_save_redo(cls);
+
+ /* move back to previous undo state */
+ undo->head = undo->head ? undo->head - 1 : undo->pos.alloc - 1;
+ undo->count--;
+ idx = undo->head;
+
+ /* 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 */
+ pos = alist_get(&undo->pos, idx, struct cli_undo_pos);
+ memcpy(cls->buf, abuf_data(&pos->buf), cls->len);
+ cls->eol_num = pos->eol_num;
+
+ /* display restored content */
+ cls_putnstr(cls, cls->buf, cls->eol_num);
+
+ /* position cursor */
+ cls_putchars(cls, cls->eol_num - pos->num, CTL_BACKSPACE);
+ cls->num = pos->num;
+}
+
+void cread_redo(struct cli_line_state *cls)
{
struct cli_editor_state *ed = cli_editor(cls);
struct cli_undo_state *undo = &ed->undo;
+ struct cli_undo_state *redo = &ed->redo;
+ struct cli_undo_pos *pos;
+ const struct cli_undo_pos *rpos;
+ uint idx;
- if (!abuf_size(&undo->buf))
+ if (!redo->pos.alloc || !redo->count)
return;
+ /* save current state to undo buffer */
+ idx = undo->head;
+ pos = alist_getw(&undo->pos, idx, struct cli_undo_pos);
+ memcpy(abuf_data(&pos->buf), cls->buf, cls->len);
+ pos->num = cls->num;
+ pos->eol_num = cls->eol_num;
+ undo->head = (undo->head + 1) % undo->pos.alloc;
+ if (undo->count < undo->pos.alloc)
+ undo->count++;
+
+ /* move back to previous redo state */
+ redo->head = redo->head ? redo->head - 1 : redo->pos.alloc - 1;
+ redo->count--;
+ idx = redo->head;
+
/* go to start of line */
while (cls->num) {
cls_putch(cls, CTL_BACKSPACE);
@@ -85,16 +193,17 @@ void cread_restore_undo(struct cli_line_state *cls)
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;
+ /* restore from redo buffer */
+ rpos = alist_get(&redo->pos, idx, struct cli_undo_pos);
+ memcpy(cls->buf, abuf_data(&rpos->buf), cls->len);
+ cls->eol_num = rpos->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;
+ cls_putchars(cls, cls->eol_num - rpos->num, CTL_BACKSPACE);
+ cls->num = rpos->num;
}
void cread_save_yank(struct cli_line_state *cls, const char *text, uint len)
@@ -366,6 +366,9 @@ type
"textline"
A line of text which can be edited
+ "textedit"
+ A multi-line text editor
+
"box"
A rectangle with a given line width (not filled)
@@ -446,6 +449,17 @@ max-chars:
Specifies the maximum number of characters permitted to be in the textline.
The user will be prevented from adding more.
+Textedit nodes have the same properties as textline nodes, with the following
+differences:
+
+- The editor supports multiple lines of text
+- Pressing Enter inserts a newline instead of closing the editor
+- Home/End move to start/end of the current line
+- Ctrl+K kills to end of the current line (not entire buffer)
+- Up/Down (Ctrl+P/N) navigate between lines
+
+See :doc:`../usage/cmdline` for a full list of editing keys.
+
Box nodes have the following additional properties:
width
@@ -91,3 +91,71 @@ convenient::
=> i2c speed 0x30000
Setting bus speed to 196608 Hz
+
+Command-line editing
+--------------------
+
+U-Boot supports command-line editing when `CONFIG_CMDLINE_EDITING` is enabled.
+This provides an Emacs-like interface for editing commands before they are
+executed. The following key bindings are available:
+
+Cursor movement
+~~~~~~~~~~~~~~~
+
+- **Left arrow** or **Ctrl+B**: Move cursor left one character
+- **Right arrow** or **Ctrl+F**: Move cursor right one character
+- **Ctrl+Left** or **Alt+B**: Move cursor left one word
+- **Ctrl+Right** or **Alt+F**: Move cursor right one word
+- **Home** or **Ctrl+A**: Move to beginning of line
+- **End** or **Ctrl+E**: Move to end of line
+
+Character deletion
+~~~~~~~~~~~~~~~~~~
+
+- **Backspace** or **Ctrl+H**: Delete character before cursor
+- **Delete** or **Ctrl+D**: Delete character at cursor
+- **Ctrl+K**: Kill (delete) from cursor to end of line
+- **Ctrl+W**: Kill word before cursor
+- **Ctrl+U**: Kill entire line
+- **Ctrl+X**: Kill entire line (same as Ctrl+U)
+
+History
+~~~~~~~
+
+- **Up arrow** or **Ctrl+P**: Previous command in history
+- **Down arrow** or **Ctrl+N**: Next command in history
+
+Undo, redo, and yank
+~~~~~~~~~~~~~~~~~~~~
+
+When `CONFIG_CMDLINE_UNDO` is enabled, the following features are available:
+
+- **Ctrl+Z**: Undo the last edit operation
+- **Ctrl+Shift+Z**: Redo the last undone operation
+- **Ctrl+Y**: Yank (paste) previously killed text
+
+Text killed by Ctrl+K, Ctrl+W, Ctrl+U, or Ctrl+X is saved to a yank buffer
+and can be pasted with Ctrl+Y.
+
+The number of undo/redo levels can be configured with `CONFIG_CMDLINE_UNDO_COUNT`
+(default 1, maximum 64). Each level saves the complete buffer state,
+so higher values use more memory. Note that any new edit clears the redo
+history.
+
+Other
+~~~~~
+
+- **Tab**: Command and argument completion (if `CONFIG_AUTO_COMPLETE` is enabled)
+- **Ctrl+C**: Cancel current input
+- **Enter**: Execute command
+
+Multiline editing
+~~~~~~~~~~~~~~~~~
+
+In multiline mode (used by expo text editors), some keys have modified
+behaviour:
+
+- **Home/End**: Move to start/end of current line (not entire buffer)
+- **Ctrl+K**: Kill to end of current line (not entire buffer)
+- **Ctrl+P/N** or **Up/Down**: Navigate between lines
+- **Enter**: Insert newline (instead of executing)
@@ -8,6 +8,7 @@
#define __CLI_H
#include <abuf.h>
+#include <alist.h>
#include <stdbool.h>
#include <linux/types.h>
@@ -29,18 +30,44 @@ struct cli_ch_state {
struct cli_line_state;
/**
- * struct cli_undo_state - state for undo buffer
+ * struct cli_undo_pos - saved state for a single undo/redo level
*
- * @buf: Buffer for saved state
- * @num: Saved cursor position
- * @eol_num: Saved end-of-line position
+ * Before any editing operation (insert, delete, kill, etc.), the entire
+ * buffer state is saved so it can be restored on undo. The buffer contents,
+ * cursor position, and line length are captured together.
+ *
+ * @buf: Complete copy of the edit buffer at the time of save
+ * @num: Cursor position (offset from start of buffer)
+ * @eol_num: Number of characters in the buffer (end-of-line position)
*/
-struct cli_undo_state {
+struct cli_undo_pos {
struct abuf buf;
uint num;
uint eol_num;
};
+/**
+ * struct cli_undo_state - state for undo/redo ring buffer
+ *
+ * This implements a ring buffer for storing undo or redo states. Each state
+ * consists of a complete copy of the edit buffer plus the cursor position.
+ * The ring buffer allows multiple levels of undo/redo up to alloc entries.
+ *
+ * When saving a new state, it is written at the @head index, then @head
+ * advances (wrapping at alloc). When restoring, @head moves back and the
+ * state at that index is restored. The @count tracks how many valid states
+ * are available for undo/redo.
+ *
+ * @pos: List of &struct cli_undo_pos entries
+ * @head: Index where the next state will be saved (0 to alloc-1)
+ * @count: Number of valid states available (0 to alloc)
+ */
+struct cli_undo_state {
+ struct alist pos;
+ uint head;
+ uint count;
+};
+
/**
* struct cli_editor_state - state for enhanced editing features
*
@@ -51,6 +78,7 @@ struct cli_undo_state {
* @multiline: true if input may contain multiple lines (enables
* Ctrl-P/N for line navigation instead of history)
* @undo: Undo ring buffer state
+ * @redo: Redo ring buffer state
* @yank: Buffer for killed text (for Ctrl+Y yank)
* @yank_len: Length of killed text in yank buffer
*/
@@ -81,6 +109,9 @@ struct cli_editor_state {
/** @undo: Undo state (if CONFIG_CMDLINE_UNDO) */
struct cli_undo_state undo;
+ /** @redo: Redo state (if CONFIG_CMDLINE_UNDO) */
+ struct cli_undo_state redo;
+
/** @yank: Buffer for killed text (for Ctrl+Y yank) */
struct abuf yank;
@@ -393,13 +424,14 @@ 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)
+#if CONFIG_IS_ENABLED(CMDLINE_EDITOR)
/**
* cread_save_undo() - Save current state for undo
*
+ * Saves the buffer contents and cursor position to the undo ring buffer.
+ * Each call pushes a new undo state that can be restored with Ctrl+Z.
+ * Also clears the redo buffer since a new edit invalidates redo history.
+ *
* @cls: CLI line state
*/
void cread_save_undo(struct cli_line_state *cls);
@@ -407,13 +439,29 @@ void cread_save_undo(struct cli_line_state *cls);
/**
* cread_restore_undo() - Restore previous state from undo buffer
*
+ * Restores the buffer contents and cursor position from the most recent
+ * undo state. Multiple calls restore progressively older states. The
+ * current state is saved to the redo buffer before restoring.
+ *
* @cls: CLI line state
*/
void cread_restore_undo(struct cli_line_state *cls);
+/**
+ * cread_redo() - Redo previously undone change
+ *
+ * Restores the buffer contents and cursor position from the redo buffer.
+ * The current state is saved to the undo buffer before restoring.
+ *
+ * @cls: CLI line state
+ */
+void cread_redo(struct cli_line_state *cls);
+
/**
* cread_save_yank() - Save killed text to yank buffer
*
+ * Saves the specified text so it can be yanked (pasted) later with Ctrl+Y.
+ *
* @cls: CLI line state
* @text: Text to save
* @len: Length of text
@@ -423,9 +471,21 @@ void cread_save_yank(struct cli_line_state *cls, const char *text, uint len);
/**
* cread_yank() - Insert yanked text at cursor position
*
+ * Inserts the previously killed text at the current cursor position.
+ *
* @cls: CLI line state
*/
void cread_yank(struct cli_line_state *cls);
+
+/**
+ * cread_clear_redo() - Clear the redo buffer
+ *
+ * Called when a new edit is made to invalidate the redo history. This should
+ * be called for any edit operation that modifies the buffer.
+ *
+ * @cls: CLI line state
+ */
+void cread_clear_redo(struct cli_line_state *cls);
#else
static inline void cread_save_undo(struct cli_line_state *cls)
{
@@ -435,6 +495,10 @@ static inline void cread_restore_undo(struct cli_line_state *cls)
{
}
+static inline void cread_redo(struct cli_line_state *cls)
+{
+}
+
static inline void cread_save_yank(struct cli_line_state *cls, const char *text,
uint len)
{
@@ -443,6 +507,10 @@ static inline void cread_save_yank(struct cli_line_state *cls, const char *text,
static inline void cread_yank(struct cli_line_state *cls)
{
}
+
+static inline void cread_clear_redo(struct cli_line_state *cls)
+{
+}
#endif
#endif
@@ -74,8 +74,8 @@ static int editenv_test_base(struct unit_test_state *uts)
int ret;
/*
- * Type "test" then press Ctrl-S to save
- * \x13 is Ctrl-S
+ * Type "test" then press Ctrl-S to accept (Enter inserts newline in
+ * multiline mode)
*/
console_in_puts("test\x13");
ret = expo_editenv("myvar", NULL, buf, sizeof(buf));
@@ -94,7 +94,7 @@ static int editenv_test_initial(struct unit_test_state *uts)
/*
* Start with "world", go to start with Ctrl-A, type "hello ", then
- * press Ctrl-S to save
+ * press Ctrl-S to accept
*/
console_in_puts("\x01hello \x13");
ret = expo_editenv("myvar", "world", buf, sizeof(buf));
@@ -190,11 +190,61 @@ static int editenv_test_funcs(struct unit_test_state *uts)
ut_assertok(editenv_send(&info, CTL_CH('k')));
ut_asserteq(16033, ut_check_video(uts, "kill"));
+ /* Test undo - should restore the killed text */
+ ut_assertok(editenv_send(&info, CTL_CH('z')));
+ ut_asserteq(16877, ut_check_video(uts, "undo"));
+
+ /* Kill again and yank it back - text should be restored */
+ ut_assertok(editenv_send(&info, CTL_CH('k')));
+ ut_asserteq(16033, ut_check_video(uts, "kill2"));
+
+ ut_assertok(editenv_send(&info, CTL_CH('y')));
+ ut_asserteq(16808, ut_check_video(uts, "yank"));
+
+ /* Test Home - should go to start of current line */
+ ut_assertok(editenv_send(&info, CTL_CH('a')));
+ ut_asserteq(0, info.ted->tin.cls.num);
+ ut_asserteq(16845, ut_check_video(uts, "home"));
+
+ /* Test End - should go to end of current line */
+ ut_assertok(editenv_send(&info, CTL_CH('e')));
+ ut_asserteq(16808, ut_check_video(uts, "end"));
+
+ /* Go left two words with Ctrl+R */
+ ut_assertok(editenv_send(&info, CTL_CH('r')));
+ ut_asserteq(16838, ut_check_video(uts, "left1"));
+
+ ut_assertok(editenv_send(&info, CTL_CH('r')));
+ ut_asserteq(16812, ut_check_video(uts, "left2"));
+
+ /* Delete three words with Ctrl+W */
+ ut_assertok(editenv_send(&info, CTL_CH('w')));
+ ut_asserteq(16691, ut_check_video(uts, "delw1"));
+
+ ut_assertok(editenv_send(&info, CTL_CH('w')));
+ ut_asserteq(16445, ut_check_video(uts, "delw2"));
+
+ ut_assertok(editenv_send(&info, CTL_CH('w')));
+ ut_asserteq(16118, ut_check_video(uts, "delw3"));
+
+ /* Undo to restore one deleted word */
+ ut_assertok(editenv_send(&info, CTL_CH('z')));
+ ut_asserteq(16445, ut_check_video(uts, "undo1"));
+
+ /* Type a character - this clears the redo buffer */
+ ut_assertok(editenv_send(&info, '!'));
+ ut_asserteq(16469, ut_check_video(uts, "type"));
+
+ /* Redo (Ctrl+G) should do nothing since typing cleared the redo buffer */
+ ut_assertok(editenv_send(&info, CTL_CH('g')));
+ ut_asserteq(16469, ut_check_video(uts, "redo"));
+
+ /* Press Ctrl-S to save */
ut_asserteq(1, editenv_send(&info, BKEY_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"));
+ /* The '*' and '!' are inserted; redo did nothing since it was cleared */
+ ut_assert(strstr(expo_editenv_result(&info), "tes*ted"));
+ ut_asserteq(16469, ut_check_video(uts, "save"));
expo_editenv_uninit(&info);
@@ -1735,6 +1735,16 @@ static int expo_render_textedit(struct unit_test_state *uts)
ut_asserteq(90, ted->tin.cls.eol_num);
ut_asserteq('\n', ((char *)abuf_data(&ted->tin.buf))[89]);
+ /* Ctrl+Z undoes the yank */
+ ut_assertok(expo_send_key(exp, CTL_CH('z')));
+ ut_asserteq(85, ted->tin.cls.num);
+ ut_asserteq(86, ted->tin.cls.eol_num);
+
+ /* Ctrl+Shift+Z (internal code 'g') redoes the yank */
+ ut_assertok(expo_send_key(exp, CTL_CH('g')));
+ ut_asserteq(89, ted->tin.cls.num);
+ ut_asserteq(90, ted->tin.cls.eol_num);
+
/* clear multiline mode, close the textedit with Enter (BKEY_SELECT) */
ted->obj.flags &= ~SCENEOF_MULTILINE;
ut_assertok(expo_send_key(exp, BKEY_SELECT));
@@ -1743,7 +1753,7 @@ static int expo_render_textedit(struct unit_test_state *uts)
ut_asserteq(OBJ_TEXTED, act.select.id);
ut_assertok(scene_set_open(scn, act.select.id, false));
- /* check the textedit is closed and text is changed */
+ /* check the textedit is closed and text is changed (redo restored latr) */
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 latr\n",