mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,13 @@
|
||||
#include <signal.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#define isatty(fd) _isatty(fd)
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#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")) {
|
||||
|
||||
Reference in New Issue
Block a user