From d52f3189d8794b6ee07f963f6a56535f3bd7f060 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Tue, 9 Dec 2025 16:49:39 +0900 Subject: [PATCH] mruby-bin-mirb: add auto-indent for continuation lines - automatically indent continuation lines based on block depth - detect block-opening keywords (def, class, if, do, etc.) and braces - use ANSI escape sequences to fix indentation for: - block-closing keywords (end, }) - mid-block keywords (else, elsif, rescue, ensure, when) - only active for interactive TTY input with ANSI support - requires GNU readline (not available with linenoise) Co-authored-by: Claude --- mrbgems/mruby-bin-mirb/tools/mirb/mirb.c | 179 ++++++++++++++++++++++- 1 file changed, 178 insertions(+), 1 deletion(-) diff --git a/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c b/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c index e8cc70ea5..ef6a11072 100644 --- a/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c +++ b/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c @@ -29,6 +29,13 @@ #include #include +#ifdef _WIN32 +#include +#define isatty(fd) _isatty(fd) +#else +#include +#endif + #include "mirb_completion.h" /* obsolete configuration */ @@ -81,6 +88,151 @@ static const char history_file_name[] = ".mirb_history"; +/* Auto-indent support for GNU readline (not linenoise) */ +#if !defined(MRB_USE_LINENOISE) && defined(RL_READLINE_VERSION) +static int mirb_indent_level = 0; +static mrb_bool mirb_use_ansi = FALSE; + +static int +mirb_startup_hook(void) +{ + if (mirb_indent_level > 0) { + int i; + for (i = 0; i < mirb_indent_level; i++) { + rl_insert_text(" "); + } + } + return 0; +} + +/* Check if terminal supports ANSI escape sequences */ +static mrb_bool +supports_escape_sequences(void) +{ + const char *term; + + if (!isatty(fileno(stdout))) return FALSE; + + term = getenv("TERM"); + if (!term || strcmp(term, "dumb") == 0) return FALSE; + + /* Respect NO_COLOR convention */ + if (getenv("NO_COLOR")) return FALSE; + + return TRUE; +} + +/* Check if line starts with a mid-block keyword (else, elsif, rescue, ensure, when) + * These keywords should be at the same indent level as the opening keyword */ +static mrb_bool +is_midblock_keyword(const char *line) +{ + /* skip leading whitespace */ + while (*line == ' ' || *line == '\t') line++; + + if (strncmp(line, "else", 4) == 0 && !ISALNUM(line[4]) && line[4] != '_') return TRUE; + if (strncmp(line, "elsif ", 6) == 0) return TRUE; + if (strncmp(line, "rescue", 6) == 0 && !ISALNUM(line[6]) && line[6] != '_') return TRUE; + if (strncmp(line, "ensure", 6) == 0 && !ISALNUM(line[6]) && line[6] != '_') return TRUE; + if (strncmp(line, "when ", 5) == 0) return TRUE; + + return FALSE; +} + +/* Reprint line with corrected indentation using ANSI escapes */ +static void +reprint_line_with_indent(int line_num, const char *line, int indent) +{ + const char *content = line; + const char *end; + int i; + + /* Skip original leading whitespace to get actual content */ + while (*content == ' ' || *content == '\t') content++; + + /* Find end of content (exclude trailing newline) */ + end = content + strlen(content); + while (end > content && (end[-1] == '\n' || end[-1] == '\r')) end--; + + /* Move cursor up, clear line, return to beginning */ + printf("\033[A\r\033[K"); + + /* Print prompt */ + printf("%d* ", line_num); + + /* Print corrected indentation */ + for (i = 0; i < indent; i++) { + printf(" "); + } + + /* Print content (no newline - cursor stays at end of line) */ + printf("%.*s", (int)(end - content), content); + + /* Move cursor back down to next line */ + printf("\n"); + fflush(stdout); +} + +/* Calculate indent level by counting open blocks in code */ +static int +calc_indent_level(const char *code) +{ + int level = 0; + const char *p = code; + + while (*p) { + /* Skip strings */ + if (*p == '"' || *p == '\'') { + char quote = *p++; + while (*p && *p != quote) { + if (*p == '\\' && p[1]) p++; + p++; + } + if (*p) p++; + continue; + } + /* Skip comments */ + if (*p == '#') { + while (*p && *p != '\n') p++; + continue; + } + /* Check for keywords */ + if (p == code || !ISALNUM(p[-1])) { + if (strncmp(p, "def ", 4) == 0 || + strncmp(p, "class ", 6) == 0 || + strncmp(p, "module ", 7) == 0 || + strncmp(p, "do\n", 3) == 0 || + strncmp(p, "do ", 3) == 0 || + (strncmp(p, "do", 2) == 0 && (p[2] == '\0' || p[2] == '\n')) || + strncmp(p, "if ", 3) == 0 || + strncmp(p, "unless ", 7) == 0 || + strncmp(p, "case ", 5) == 0 || + strncmp(p, "while ", 6) == 0 || + strncmp(p, "until ", 6) == 0 || + strncmp(p, "for ", 4) == 0 || + strncmp(p, "begin\n", 6) == 0 || + (strncmp(p, "begin", 5) == 0 && (p[5] == '\0' || p[5] == '\n'))) { + level++; + } + else if (strncmp(p, "end\n", 4) == 0 || + strncmp(p, "end ", 4) == 0 || + (strncmp(p, "end", 3) == 0 && (p[3] == '\0' || p[3] == '\n'))) { + if (level > 0) level--; + } + } + /* Check for block opening with { */ + if (*p == '{') { + level++; + } + else if (*p == '}') { + if (level > 0) level--; + } + p++; + } + return level; +} +#endif /* !MRB_USE_LINENOISE && RL_READLINE_VERSION */ + static char * get_history_path(mrb_state *mrb) { @@ -596,10 +748,17 @@ main(int argc, char **argv) mrb_ccontext_filename(mrb, cxt, "(mirb)"); if (args.verbose) cxt->dump_result = TRUE; - /* Setup tab completion */ + /* Setup tab completion and auto-indent */ #ifdef MRB_USE_READLINE #ifndef MRB_USE_LINENOISE mirb_setup_readline_completion(mrb, cxt); +#if defined(RL_READLINE_VERSION) + /* Only enable auto-indent for interactive input */ + if (isatty(fileno(stdin))) { + rl_startup_hook = mirb_startup_hook; + mirb_use_ansi = supports_escape_sequences(); + } +#endif #else mirb_setup_linenoise_completion(mrb, cxt); #endif @@ -661,6 +820,10 @@ main(int argc, char **argv) { char prompt[32]; snprintf(prompt, sizeof(prompt), "%d%c ", line_num, code_block_open ? '*' : '>'); +#if !defined(MRB_USE_LINENOISE) && defined(RL_READLINE_VERSION) + /* Set indent level for auto-indent on continuation lines */ + mirb_indent_level = code_block_open ? calc_indent_level(ruby_code) : 0; +#endif line = MIRB_READLINE(prompt); } signal(SIGINT, SIG_DFL); @@ -689,6 +852,20 @@ main(int argc, char **argv) continue; } strcat(ruby_code, last_code_line); +#if !defined(MRB_USE_LINENOISE) && defined(RL_READLINE_VERSION) + /* Check if indent level decreased or mid-block keyword needs dedent */ + if (mirb_use_ansi && mirb_indent_level > 0) { + int new_level = calc_indent_level(ruby_code); + if (new_level < mirb_indent_level) { + /* Block closed (end, }) - use new lower level */ + reprint_line_with_indent(line_num - 1, last_code_line, new_level); + } + else if (is_midblock_keyword(last_code_line)) { + /* Mid-block keyword (else, elsif, rescue, ensure, when) - dedent by 1 */ + reprint_line_with_indent(line_num - 1, last_code_line, mirb_indent_level - 1); + } + } +#endif } else { if (check_keyword(last_code_line, "quit") || check_keyword(last_code_line, "exit")) {