mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
mruby-bin-mirb: add command history with Up/Down navigation
Add in-memory command history for mirb sessions: - Up arrow on first line: navigate to older history entries - Down arrow on last line: navigate to newer history entries - Current input is preserved when browsing and restored when navigating past the newest entry - History uses a circular buffer (100 entries max) - Duplicate consecutive entries are not added Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -750,6 +750,10 @@ main(int argc, char **argv)
|
||||
*(mrb->c->ci->stack + 1) = result;
|
||||
#endif
|
||||
}
|
||||
/* Add to history after evaluation (success or error) */
|
||||
if (use_editor) {
|
||||
mirb_editor_history_add(&editor, ruby_code);
|
||||
}
|
||||
}
|
||||
ruby_code[0] = '\0';
|
||||
last_code_line[0] = '\0';
|
||||
|
||||
@@ -192,6 +192,12 @@ mirb_editor_init(mirb_editor *ed)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!mirb_history_init(&ed->hist, MIRB_HISTORY_SIZE)) {
|
||||
mirb_buffer_free(&ed->buf);
|
||||
mirb_term_cleanup(&ed->term);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ed->prompt = "> ";
|
||||
ed->prompt_cont = "* ";
|
||||
ed->prompt_len = 2;
|
||||
@@ -210,6 +216,7 @@ mirb_editor_cleanup(mirb_editor *ed)
|
||||
{
|
||||
if (!ed->initialized) return;
|
||||
|
||||
mirb_history_free(&ed->hist);
|
||||
mirb_buffer_free(&ed->buf);
|
||||
mirb_term_cleanup(&ed->term);
|
||||
ed->initialized = FALSE;
|
||||
@@ -336,6 +343,8 @@ handle_key(mirb_editor *ed, int key, mirb_edit_result *result)
|
||||
{
|
||||
switch (key) {
|
||||
case MIRB_KEY_ENTER:
|
||||
/* Stop history browsing */
|
||||
mirb_history_browse_stop(&ed->hist);
|
||||
/* Check if input is complete */
|
||||
if (ed->check_complete) {
|
||||
char *code = mirb_buffer_to_string(&ed->buf);
|
||||
@@ -392,12 +401,40 @@ handle_key(mirb_editor *ed, int key, mirb_edit_result *result)
|
||||
|
||||
case MIRB_KEY_UP:
|
||||
case MIRB_KEY_CTRL_P:
|
||||
mirb_buffer_cursor_up(&ed->buf);
|
||||
/* If on first line, navigate history; otherwise move cursor up */
|
||||
if (ed->buf.cursor_line == 0) {
|
||||
/* Start history browsing if not already */
|
||||
if (!ed->hist.browsing) {
|
||||
char *current = mirb_buffer_to_string(&ed->buf);
|
||||
mirb_history_browse_start(&ed->hist, current);
|
||||
free(current);
|
||||
}
|
||||
const char *prev = mirb_history_prev(&ed->hist);
|
||||
if (prev) {
|
||||
mirb_buffer_set_string(&ed->buf, prev);
|
||||
mirb_buffer_cursor_finish(&ed->buf);
|
||||
}
|
||||
}
|
||||
else {
|
||||
mirb_buffer_cursor_up(&ed->buf);
|
||||
}
|
||||
return TRUE;
|
||||
|
||||
case MIRB_KEY_DOWN:
|
||||
case MIRB_KEY_CTRL_N:
|
||||
mirb_buffer_cursor_down(&ed->buf);
|
||||
/* If on last line, navigate history; otherwise move cursor down */
|
||||
if (ed->buf.cursor_line == ed->buf.line_count - 1) {
|
||||
if (ed->hist.browsing) {
|
||||
const char *next = mirb_history_next(&ed->hist);
|
||||
if (next) {
|
||||
mirb_buffer_set_string(&ed->buf, next);
|
||||
mirb_buffer_cursor_finish(&ed->buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
mirb_buffer_cursor_down(&ed->buf);
|
||||
}
|
||||
return TRUE;
|
||||
|
||||
case MIRB_KEY_HOME:
|
||||
@@ -447,6 +484,8 @@ handle_key(mirb_editor *ed, int key, mirb_edit_result *result)
|
||||
default:
|
||||
/* Insert printable characters */
|
||||
if (key >= 32 && key < 127) {
|
||||
/* Stop history browsing when user types */
|
||||
mirb_history_browse_stop(&ed->hist);
|
||||
mirb_buffer_insert_char(&ed->buf, (char)key);
|
||||
/* Check for auto-dedent after typing 'end' or '}' */
|
||||
if (should_dedent(&ed->buf, (char)key)) {
|
||||
@@ -605,3 +644,12 @@ mirb_editor_read_simple(mirb_editor *ed, char **out_str)
|
||||
*out_str = total;
|
||||
return MIRB_EDIT_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add entry to history
|
||||
*/
|
||||
void
|
||||
mirb_editor_history_add(mirb_editor *ed, const char *entry)
|
||||
{
|
||||
mirb_history_add(&ed->hist, entry);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <mruby.h>
|
||||
#include "mirb_term.h"
|
||||
#include "mirb_buffer.h"
|
||||
#include "mirb_history.h"
|
||||
|
||||
/*
|
||||
* Editor result codes
|
||||
@@ -34,6 +35,7 @@ typedef mrb_bool mirb_check_complete_fn(const char *code, void *user_data);
|
||||
typedef struct mirb_editor {
|
||||
mirb_term term; /* terminal state */
|
||||
mirb_buffer buf; /* editing buffer */
|
||||
mirb_history hist; /* command history */
|
||||
|
||||
const char *prompt; /* primary prompt (e.g., "> ") */
|
||||
const char *prompt_cont; /* continuation prompt (e.g., "* ") */
|
||||
@@ -99,4 +101,10 @@ mirb_edit_result mirb_editor_read(mirb_editor *ed, char **out_str);
|
||||
*/
|
||||
mirb_edit_result mirb_editor_read_simple(mirb_editor *ed, char **out_str);
|
||||
|
||||
/*
|
||||
* Add entry to history
|
||||
* Called after successful command execution
|
||||
*/
|
||||
void mirb_editor_history_add(mirb_editor *ed, const char *entry);
|
||||
|
||||
#endif /* MIRB_EDITOR_H */
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
** mirb_history.c - Command history for mirb editor
|
||||
**
|
||||
** See Copyright Notice in mruby.h
|
||||
*/
|
||||
|
||||
#include "mirb_history.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Initialize history
|
||||
*/
|
||||
mrb_bool
|
||||
mirb_history_init(mirb_history *hist, size_t capacity)
|
||||
{
|
||||
memset(hist, 0, sizeof(*hist));
|
||||
|
||||
if (capacity == 0) capacity = MIRB_HISTORY_SIZE;
|
||||
|
||||
hist->entries = (char**)calloc(capacity, sizeof(char*));
|
||||
if (hist->entries == NULL) return FALSE;
|
||||
|
||||
hist->capacity = capacity;
|
||||
hist->count = 0;
|
||||
hist->start = 0;
|
||||
hist->pos = 0;
|
||||
hist->saved_input = NULL;
|
||||
hist->browsing = FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free history resources
|
||||
*/
|
||||
void
|
||||
mirb_history_free(mirb_history *hist)
|
||||
{
|
||||
if (hist->entries) {
|
||||
for (size_t i = 0; i < hist->capacity; i++) {
|
||||
free(hist->entries[i]);
|
||||
}
|
||||
free(hist->entries);
|
||||
hist->entries = NULL;
|
||||
}
|
||||
free(hist->saved_input);
|
||||
hist->saved_input = NULL;
|
||||
hist->count = 0;
|
||||
hist->capacity = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get actual index in circular buffer
|
||||
*/
|
||||
static size_t
|
||||
actual_index(mirb_history *hist, size_t logical_idx)
|
||||
{
|
||||
return (hist->start + logical_idx) % hist->capacity;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add entry to history
|
||||
*/
|
||||
void
|
||||
mirb_history_add(mirb_history *hist, const char *entry)
|
||||
{
|
||||
if (entry == NULL || entry[0] == '\0') return;
|
||||
|
||||
/* Don't add if same as last entry */
|
||||
if (hist->count > 0) {
|
||||
size_t last_idx = actual_index(hist, hist->count - 1);
|
||||
if (strcmp(hist->entries[last_idx], entry) == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
char *copy = strdup(entry);
|
||||
if (copy == NULL) return;
|
||||
|
||||
if (hist->count < hist->capacity) {
|
||||
/* Still have room */
|
||||
size_t idx = actual_index(hist, hist->count);
|
||||
hist->entries[idx] = copy;
|
||||
hist->count++;
|
||||
}
|
||||
else {
|
||||
/* Buffer is full, overwrite oldest */
|
||||
size_t idx = hist->start;
|
||||
free(hist->entries[idx]);
|
||||
hist->entries[idx] = copy;
|
||||
hist->start = (hist->start + 1) % hist->capacity;
|
||||
}
|
||||
|
||||
/* Reset browsing state */
|
||||
hist->browsing = FALSE;
|
||||
hist->pos = hist->count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start browsing history
|
||||
*/
|
||||
void
|
||||
mirb_history_browse_start(mirb_history *hist, const char *current_input)
|
||||
{
|
||||
if (hist->browsing) return;
|
||||
|
||||
free(hist->saved_input);
|
||||
hist->saved_input = current_input ? strdup(current_input) : NULL;
|
||||
hist->browsing = TRUE;
|
||||
hist->pos = hist->count; /* Start past the end (at current input) */
|
||||
}
|
||||
|
||||
/*
|
||||
* Stop browsing history
|
||||
*/
|
||||
void
|
||||
mirb_history_browse_stop(mirb_history *hist)
|
||||
{
|
||||
free(hist->saved_input);
|
||||
hist->saved_input = NULL;
|
||||
hist->browsing = FALSE;
|
||||
hist->pos = hist->count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get previous entry (older)
|
||||
*/
|
||||
const char *
|
||||
mirb_history_prev(mirb_history *hist)
|
||||
{
|
||||
if (hist->count == 0) return NULL;
|
||||
|
||||
if (!hist->browsing) {
|
||||
/* Should call browse_start first, but handle gracefully */
|
||||
hist->browsing = TRUE;
|
||||
hist->pos = hist->count;
|
||||
}
|
||||
|
||||
if (hist->pos == 0) {
|
||||
/* Already at oldest entry */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hist->pos--;
|
||||
return hist->entries[actual_index(hist, hist->pos)];
|
||||
}
|
||||
|
||||
/*
|
||||
* Get next entry (newer)
|
||||
*/
|
||||
const char *
|
||||
mirb_history_next(mirb_history *hist)
|
||||
{
|
||||
if (!hist->browsing) return NULL;
|
||||
|
||||
if (hist->pos >= hist->count) {
|
||||
/* Already at current input */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hist->pos++;
|
||||
|
||||
if (hist->pos >= hist->count) {
|
||||
/* Moved past newest entry, return saved input */
|
||||
return hist->saved_input ? hist->saved_input : "";
|
||||
}
|
||||
|
||||
return hist->entries[actual_index(hist, hist->pos)];
|
||||
}
|
||||
|
||||
/*
|
||||
* Get current entry count
|
||||
*/
|
||||
size_t
|
||||
mirb_history_count(mirb_history *hist)
|
||||
{
|
||||
return hist->count;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
** mirb_history.h - Command history for mirb editor
|
||||
**
|
||||
** See Copyright Notice in mruby.h
|
||||
*/
|
||||
|
||||
#ifndef MIRB_HISTORY_H
|
||||
#define MIRB_HISTORY_H
|
||||
|
||||
#include <mruby.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* Default history size */
|
||||
#define MIRB_HISTORY_SIZE 100
|
||||
|
||||
/*
|
||||
* History entry
|
||||
*/
|
||||
typedef struct mirb_history {
|
||||
char **entries; /* array of history entries */
|
||||
size_t capacity; /* max number of entries */
|
||||
size_t count; /* current number of entries */
|
||||
size_t start; /* index of oldest entry (circular buffer) */
|
||||
size_t pos; /* current browsing position */
|
||||
char *saved_input; /* saved current input when browsing */
|
||||
mrb_bool browsing; /* TRUE if currently browsing history */
|
||||
} mirb_history;
|
||||
|
||||
/*
|
||||
* Initialize history
|
||||
* Returns TRUE on success
|
||||
*/
|
||||
mrb_bool mirb_history_init(mirb_history *hist, size_t capacity);
|
||||
|
||||
/*
|
||||
* Free history resources
|
||||
*/
|
||||
void mirb_history_free(mirb_history *hist);
|
||||
|
||||
/*
|
||||
* Add entry to history
|
||||
* Empty strings are not added
|
||||
* Duplicate of last entry is not added
|
||||
*/
|
||||
void mirb_history_add(mirb_history *hist, const char *entry);
|
||||
|
||||
/*
|
||||
* Start browsing history
|
||||
* Saves the current input for later restoration
|
||||
*/
|
||||
void mirb_history_browse_start(mirb_history *hist, const char *current_input);
|
||||
|
||||
/*
|
||||
* Stop browsing history
|
||||
*/
|
||||
void mirb_history_browse_stop(mirb_history *hist);
|
||||
|
||||
/*
|
||||
* Get previous entry (older)
|
||||
* Returns NULL if at oldest entry or history is empty
|
||||
*/
|
||||
const char *mirb_history_prev(mirb_history *hist);
|
||||
|
||||
/*
|
||||
* Get next entry (newer)
|
||||
* Returns saved input if moving past newest entry
|
||||
* Returns NULL if not browsing
|
||||
*/
|
||||
const char *mirb_history_next(mirb_history *hist);
|
||||
|
||||
/*
|
||||
* Get current entry count
|
||||
*/
|
||||
size_t mirb_history_count(mirb_history *hist);
|
||||
|
||||
#endif /* MIRB_HISTORY_H */
|
||||
Reference in New Issue
Block a user