@@ -29,6 +29,13 @@
2929#include <signal.h>
3030#include <setjmp.h>
313132+#ifdef _WIN32
33+#include <io.h>
34+#define isatty(fd) _isatty(fd)
35+#else
36+#include <unistd.h>
37+#endif
38+3239#include "mirb_completion.h"
33403441/* obsolete configuration */
@@ -81,6 +88,151 @@
81888289static const char history_file_name[] = ".mirb_history";
839091+/* Auto-indent support for GNU readline (not linenoise) */
92+#if !defined(MRB_USE_LINENOISE) && defined(RL_READLINE_VERSION)
93+static int mirb_indent_level = 0;
94+static mrb_bool mirb_use_ansi = FALSE;
95+96+static int
97+mirb_startup_hook(void)
98+{
99+if (mirb_indent_level > 0) {
100+int i;
101+for (i = 0; i < mirb_indent_level; i++) {
102+rl_insert_text(" ");
103+ }
104+ }
105+return 0;
106+}
107+108+/* Check if terminal supports ANSI escape sequences */
109+static mrb_bool
110+supports_escape_sequences(void)
111+{
112+const char *term;
113+114+if (!isatty(fileno(stdout))) return FALSE;
115+116+term = getenv("TERM");
117+if (!term || strcmp(term, "dumb") == 0) return FALSE;
118+119+/* Respect NO_COLOR convention */
120+if (getenv("NO_COLOR")) return FALSE;
121+122+return TRUE;
123+}
124+125+/* Check if line starts with a mid-block keyword (else, elsif, rescue, ensure, when)
126+ * These keywords should be at the same indent level as the opening keyword */
127+static mrb_bool
128+is_midblock_keyword(const char *line)
129+{
130+/* skip leading whitespace */
131+while (*line == ' ' || *line == '\t') line++;
132+133+if (strncmp(line, "else", 4) == 0 && !ISALNUM(line[4]) && line[4] != '_') return TRUE;
134+if (strncmp(line, "elsif ", 6) == 0) return TRUE;
135+if (strncmp(line, "rescue", 6) == 0 && !ISALNUM(line[6]) && line[6] != '_') return TRUE;
136+if (strncmp(line, "ensure", 6) == 0 && !ISALNUM(line[6]) && line[6] != '_') return TRUE;
137+if (strncmp(line, "when ", 5) == 0) return TRUE;
138+139+return FALSE;
140+}
141+142+/* Reprint line with corrected indentation using ANSI escapes */
143+static void
144+reprint_line_with_indent(int line_num, const char *line, int indent)
145+{
146+const char *content = line;
147+const char *end;
148+int i;
149+150+/* Skip original leading whitespace to get actual content */
151+while (*content == ' ' || *content == '\t') content++;
152+153+/* Find end of content (exclude trailing newline) */
154+end = content + strlen(content);
155+while (end > content && (end[-1] == '\n' || end[-1] == '\r')) end--;
156+157+/* Move cursor up, clear line, return to beginning */
158+printf("\033[A\r\033[K");
159+160+/* Print prompt */
161+printf("%d* ", line_num);
162+163+/* Print corrected indentation */
164+for (i = 0; i < indent; i++) {
165+printf(" ");
166+ }
167+168+/* Print content (no newline - cursor stays at end of line) */
169+printf("%.*s", (int)(end - content), content);
170+171+/* Move cursor back down to next line */
172+printf("\n");
173+fflush(stdout);
174+}
175+176+/* Calculate indent level by counting open blocks in code */
177+static int
178+calc_indent_level(const char *code)
179+{
180+int level = 0;
181+const char *p = code;
182+183+while (*p) {
184+/* Skip strings */
185+if (*p == '"' || *p == '\'') {
186+char quote = *p++;
187+while (*p && *p != quote) {
188+if (*p == '\\' && p[1]) p++;
189+p++;
190+ }
191+if (*p) p++;
192+continue;
193+ }
194+/* Skip comments */
195+if (*p == '#') {
196+while (*p && *p != '\n') p++;
197+continue;
198+ }
199+/* Check for keywords */
200+if (p == code || !ISALNUM(p[-1])) {
201+if (strncmp(p, "def ", 4) == 0 ||
202+strncmp(p, "class ", 6) == 0 ||
203+strncmp(p, "module ", 7) == 0 ||
204+strncmp(p, "do\n", 3) == 0 ||
205+strncmp(p, "do ", 3) == 0 ||
206+ (strncmp(p, "do", 2) == 0 && (p[2] == '\0' || p[2] == '\n')) ||
207+strncmp(p, "if ", 3) == 0 ||
208+strncmp(p, "unless ", 7) == 0 ||
209+strncmp(p, "case ", 5) == 0 ||
210+strncmp(p, "while ", 6) == 0 ||
211+strncmp(p, "until ", 6) == 0 ||
212+strncmp(p, "for ", 4) == 0 ||
213+strncmp(p, "begin\n", 6) == 0 ||
214+ (strncmp(p, "begin", 5) == 0 && (p[5] == '\0' || p[5] == '\n'))) {
215+level++;
216+ }
217+else if (strncmp(p, "end\n", 4) == 0 ||
218+strncmp(p, "end ", 4) == 0 ||
219+ (strncmp(p, "end", 3) == 0 && (p[3] == '\0' || p[3] == '\n'))) {
220+if (level > 0) level--;
221+ }
222+ }
223+/* Check for block opening with { */
224+if (*p == '{') {
225+level++;
226+ }
227+else if (*p == '}') {
228+if (level > 0) level--;
229+ }
230+p++;
231+ }
232+return level;
233+}
234+#endif /* !MRB_USE_LINENOISE && RL_READLINE_VERSION */
235+84236static char *
85237get_history_path(mrb_state *mrb)
86238{
@@ -596,10 +748,17 @@ main(int argc, char **argv)
596748mrb_ccontext_filename(mrb, cxt, "(mirb)");
597749if (args.verbose) cxt->dump_result = TRUE;
598750599-/* Setup tab completion */
751+/* Setup tab completion and auto-indent */
600752#ifdef MRB_USE_READLINE
601753#ifndef MRB_USE_LINENOISE
602754mirb_setup_readline_completion(mrb, cxt);
755+#if defined(RL_READLINE_VERSION)
756+/* Only enable auto-indent for interactive input */
757+if (isatty(fileno(stdin))) {
758+rl_startup_hook = mirb_startup_hook;
759+mirb_use_ansi = supports_escape_sequences();
760+ }
761+#endif
603762#else
604763mirb_setup_linenoise_completion(mrb, cxt);
605764#endif
@@ -661,6 +820,10 @@ main(int argc, char **argv)
661820 {
662821char prompt[32];
663822snprintf(prompt, sizeof(prompt), "%d%c ", line_num, code_block_open ? '*' : '>');
823+#if !defined(MRB_USE_LINENOISE) && defined(RL_READLINE_VERSION)
824+/* Set indent level for auto-indent on continuation lines */
825+mirb_indent_level = code_block_open ? calc_indent_level(ruby_code) : 0;
826+#endif
664827line = MIRB_READLINE(prompt);
665828 }
666829signal(SIGINT, SIG_DFL);
@@ -689,6 +852,20 @@ main(int argc, char **argv)
689852continue;
690853 }
691854strcat(ruby_code, last_code_line);
855+#if !defined(MRB_USE_LINENOISE) && defined(RL_READLINE_VERSION)
856+/* Check if indent level decreased or mid-block keyword needs dedent */
857+if (mirb_use_ansi && mirb_indent_level > 0) {
858+int new_level = calc_indent_level(ruby_code);
859+if (new_level < mirb_indent_level) {
860+/* Block closed (end, }) - use new lower level */
861+reprint_line_with_indent(line_num - 1, last_code_line, new_level);
862+ }
863+else if (is_midblock_keyword(last_code_line)) {
864+/* Mid-block keyword (else, elsif, rescue, ensure, when) - dedent by 1 */
865+reprint_line_with_indent(line_num - 1, last_code_line, mirb_indent_level - 1);
866+ }
867+ }
868+#endif
692869 }
693870else {
694871if (check_keyword(last_code_line, "quit") || check_keyword(last_code_line, "exit")) {