@@ -0,0 +1,180 @@
1+/*
2+** mirb_history.c - Command history for mirb editor
3+**
4+** See Copyright Notice in mruby.h
5+*/
6+7+#include "mirb_history.h"
8+#include <stdio.h>
9+#include <stdlib.h>
10+#include <string.h>
11+12+/*
13+ * Initialize history
14+ */
15+mrb_bool
16+mirb_history_init(mirb_history *hist, size_t capacity)
17+{
18+memset(hist, 0, sizeof(*hist));
19+20+if (capacity == 0) capacity = MIRB_HISTORY_SIZE;
21+22+hist->entries = (char**)calloc(capacity, sizeof(char*));
23+if (hist->entries == NULL) return FALSE;
24+25+hist->capacity = capacity;
26+hist->count = 0;
27+hist->start = 0;
28+hist->pos = 0;
29+hist->saved_input = NULL;
30+hist->browsing = FALSE;
31+32+return TRUE;
33+}
34+35+/*
36+ * Free history resources
37+ */
38+void
39+mirb_history_free(mirb_history *hist)
40+{
41+if (hist->entries) {
42+for (size_t i = 0; i < hist->capacity; i++) {
43+free(hist->entries[i]);
44+ }
45+free(hist->entries);
46+hist->entries = NULL;
47+ }
48+free(hist->saved_input);
49+hist->saved_input = NULL;
50+hist->count = 0;
51+hist->capacity = 0;
52+}
53+54+/*
55+ * Get actual index in circular buffer
56+ */
57+static size_t
58+actual_index(mirb_history *hist, size_t logical_idx)
59+{
60+return (hist->start + logical_idx) % hist->capacity;
61+}
62+63+/*
64+ * Add entry to history
65+ */
66+void
67+mirb_history_add(mirb_history *hist, const char *entry)
68+{
69+if (entry == NULL || entry[0] == '\0') return;
70+71+/* Don't add if same as last entry */
72+if (hist->count > 0) {
73+size_t last_idx = actual_index(hist, hist->count - 1);
74+if (strcmp(hist->entries[last_idx], entry) == 0) {
75+return;
76+ }
77+ }
78+79+char *copy = strdup(entry);
80+if (copy == NULL) return;
81+82+if (hist->count < hist->capacity) {
83+/* Still have room */
84+size_t idx = actual_index(hist, hist->count);
85+hist->entries[idx] = copy;
86+hist->count++;
87+ }
88+else {
89+/* Buffer is full, overwrite oldest */
90+size_t idx = hist->start;
91+free(hist->entries[idx]);
92+hist->entries[idx] = copy;
93+hist->start = (hist->start + 1) % hist->capacity;
94+ }
95+96+/* Reset browsing state */
97+hist->browsing = FALSE;
98+hist->pos = hist->count;
99+}
100+101+/*
102+ * Start browsing history
103+ */
104+void
105+mirb_history_browse_start(mirb_history *hist, const char *current_input)
106+{
107+if (hist->browsing) return;
108+109+free(hist->saved_input);
110+hist->saved_input = current_input ? strdup(current_input) : NULL;
111+hist->browsing = TRUE;
112+hist->pos = hist->count; /* Start past the end (at current input) */
113+}
114+115+/*
116+ * Stop browsing history
117+ */
118+void
119+mirb_history_browse_stop(mirb_history *hist)
120+{
121+free(hist->saved_input);
122+hist->saved_input = NULL;
123+hist->browsing = FALSE;
124+hist->pos = hist->count;
125+}
126+127+/*
128+ * Get previous entry (older)
129+ */
130+const char *
131+mirb_history_prev(mirb_history *hist)
132+{
133+if (hist->count == 0) return NULL;
134+135+if (!hist->browsing) {
136+/* Should call browse_start first, but handle gracefully */
137+hist->browsing = TRUE;
138+hist->pos = hist->count;
139+ }
140+141+if (hist->pos == 0) {
142+/* Already at oldest entry */
143+return NULL;
144+ }
145+146+hist->pos--;
147+return hist->entries[actual_index(hist, hist->pos)];
148+}
149+150+/*
151+ * Get next entry (newer)
152+ */
153+const char *
154+mirb_history_next(mirb_history *hist)
155+{
156+if (!hist->browsing) return NULL;
157+158+if (hist->pos >= hist->count) {
159+/* Already at current input */
160+return NULL;
161+ }
162+163+hist->pos++;
164+165+if (hist->pos >= hist->count) {
166+/* Moved past newest entry, return saved input */
167+return hist->saved_input ? hist->saved_input : "";
168+ }
169+170+return hist->entries[actual_index(hist, hist->pos)];
171+}
172+173+/*
174+ * Get current entry count
175+ */
176+size_t
177+mirb_history_count(mirb_history *hist)
178+{
179+return hist->count;
180+}