From d47dedd17dc39d82d9edbdbf9d0ff595794e1c10 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Sat, 13 Dec 2025 08:07:50 +0900 Subject: [PATCH] mruby-bin-mirb: improve Enter key behavior and auto-indentation - fix Enter in middle of line with trailing blank continuation line: now properly splits the line and removes redundant blank line - fix auto-indentation when inserting in middle of existing code: calculate indent from lines up to cursor, not entire buffer - add mirb_buffer_delete_line() for removing lines from buffer Co-authored-by: Claude --- .../mruby-bin-mirb/tools/mirb/mirb_buffer.c | 30 +++++ .../mruby-bin-mirb/tools/mirb/mirb_buffer.h | 5 + .../mruby-bin-mirb/tools/mirb/mirb_editor.c | 119 +++++++++++++++--- 3 files changed, 137 insertions(+), 17 deletions(-) diff --git a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_buffer.c b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_buffer.c index b7ab38cc9..2b2ee19e9 100644 --- a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_buffer.c +++ b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_buffer.c @@ -443,6 +443,36 @@ mirb_buffer_newline(mirb_buffer *buf) return TRUE; } +/* Delete a line at the given index */ +void +mirb_buffer_delete_line(mirb_buffer *buf, size_t line_idx) +{ + if (line_idx >= buf->line_count) return; + if (buf->line_count <= 1) return; /* Keep at least one line */ + + /* Free the line's data */ + line_free(&buf->lines[line_idx]); + + /* Shift remaining lines down */ + if (line_idx < buf->line_count - 1) { + memmove(&buf->lines[line_idx], + &buf->lines[line_idx + 1], + sizeof(mirb_line) * (buf->line_count - line_idx - 1)); + } + + buf->line_count--; + + /* Adjust cursor if needed */ + if (buf->cursor_line >= buf->line_count) { + buf->cursor_line = buf->line_count - 1; + } + if (buf->cursor_col > buf->lines[buf->cursor_line].len) { + buf->cursor_col = buf->lines[buf->cursor_line].len; + } + + buf->modified = TRUE; +} + /* * Move cursor left */ diff --git a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_buffer.h b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_buffer.h index 53a6b32ad..a2aaa09f5 100644 --- a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_buffer.h +++ b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_buffer.h @@ -105,6 +105,11 @@ mrb_bool mirb_buffer_delete_forward(mirb_buffer *buf); */ mrb_bool mirb_buffer_newline(mirb_buffer *buf); +/* + * Delete a line at the given index + */ +void mirb_buffer_delete_line(mirb_buffer *buf, size_t line_idx); + /* * Cursor movement functions * Return TRUE if cursor moved diff --git a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_editor.c b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_editor.c index 85f9e765c..ca13ba5c3 100644 --- a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_editor.c +++ b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_editor.c @@ -43,6 +43,35 @@ leading_spaces(const char *line) /* * Calculate indent level by counting open blocks in code */ +/* + * Get buffer content up to and including a specific line + * Caller must free the returned string + */ +static char * +buffer_to_string_upto_line(mirb_buffer *buf, size_t up_to_line) +{ + size_t total = 0; + size_t lines_to_include = (up_to_line < buf->line_count) ? up_to_line + 1 : buf->line_count; + + for (size_t i = 0; i < lines_to_include; i++) { + total += buf->lines[i].len; + if (i < lines_to_include - 1) total++; /* newline */ + } + + char *str = (char*)malloc(total + 1); + if (str == NULL) return NULL; + + char *p = str; + for (size_t i = 0; i < lines_to_include; i++) { + memcpy(p, buf->lines[i].data, buf->lines[i].len); + p += buf->lines[i].len; + if (i < lines_to_include - 1) *p++ = '\n'; + } + *p = '\0'; + + return str; +} + static int calc_indent_level(const char *code) { @@ -394,28 +423,84 @@ handle_key(mirb_editor *ed, int key, mirb_edit_result *result) 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); - if (code) { - mrb_bool complete = ed->check_complete(code, ed->check_complete_data); - if (!complete) { - /* Calculate indent level before adding newline */ - int indent = calc_indent_level(code); - free(code); - /* Add newline and continue editing */ - mirb_buffer_newline(&ed->buf); - /* Insert indentation spaces (2 spaces per level) */ - for (int i = 0; i < indent * 2; i++) { - mirb_buffer_insert_char(&ed->buf, ' '); + { + /* + * Smart Enter behavior: + * - If cursor is at end of line AND next line is the last line and is empty, + * just move to it (don't create duplicate empty lines) + * - Otherwise, insert a new line (split current line at cursor) + * If splitting in the middle and there was a trailing blank line, remove it + */ + mrb_bool inserting_in_middle = (ed->buf.cursor_line < ed->buf.line_count - 1); + + if (inserting_in_middle) { + /* Only consider smart navigation if cursor is at end of current line */ + mirb_line *current_line = &ed->buf.lines[ed->buf.cursor_line]; + size_t next_line_idx = ed->buf.cursor_line + 1; + mrb_bool next_is_last = (next_line_idx == ed->buf.line_count - 1); + mrb_bool next_is_blank = FALSE; + + if (next_is_last) { + mirb_line *next_line = &ed->buf.lines[next_line_idx]; + next_is_blank = TRUE; + for (size_t i = 0; i < next_line->len; i++) { + if (next_line->data[i] != ' ' && next_line->data[i] != '\t') { + next_is_blank = FALSE; + break; + } } + } + + if (ed->buf.cursor_col == current_line->len && next_is_last && next_is_blank) { + /* Cursor at end of line, next is blank last line: just move to it */ + mirb_buffer_cursor_down(&ed->buf); + mirb_buffer_cursor_end(&ed->buf); return TRUE; } - free(code); + + if (ed->buf.cursor_col < current_line->len && next_is_last && next_is_blank) { + /* Cursor in middle of line, next is blank last line: remove it before split */ + mirb_buffer_delete_line(&ed->buf, next_line_idx); + } + /* Fall through to insert new line (split at cursor) */ } + + /* Check if input is complete */ + if (ed->check_complete) { + char *code = mirb_buffer_to_string(&ed->buf); + if (code) { + mrb_bool complete = ed->check_complete(code, ed->check_complete_data); + if (!complete) { + /* + * Calculate indent level for the new line. + * If inserting in the middle of existing code, only consider + * lines up to and including the current line to avoid + * being affected by 'end' keywords on later lines. + */ + int indent; + if (inserting_in_middle) { + char *partial = buffer_to_string_upto_line(&ed->buf, ed->buf.cursor_line); + indent = partial ? calc_indent_level(partial) : 0; + free(partial); + } + else { + indent = calc_indent_level(code); + } + free(code); + /* Add newline and continue editing */ + mirb_buffer_newline(&ed->buf); + /* Insert indentation spaces (2 spaces per level) */ + for (int i = 0; i < indent * 2; i++) { + mirb_buffer_insert_char(&ed->buf, ' '); + } + return TRUE; + } + free(code); + } + } + *result = MIRB_EDIT_OK; + return FALSE; } - *result = MIRB_EDIT_OK; - return FALSE; case MIRB_KEY_CTRL_C: *result = MIRB_EDIT_INTERRUPT;