From 32d161f1aff374d9bb38756a1247518c3ba8e3bf Mon Sep 17 00:00:00 2001 From: AnmiTaliDev Date: Fri, 31 Jul 2026 08:45:11 +0500 Subject: [PATCH] feat: Add auto-indent on Enter Carry the current line's leading whitespace over to the new line on Enter, skipping it when the cursor is still inside that whitespace so the plain split doesn't get whitespace doubled. --- README.md | 3 ++ src/editor.c | 16 ++++++- tests/meson.build | 2 +- tests/test_autoindent.c | 98 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 tests/test_autoindent.c diff --git a/README.md b/README.md index 56df313..4e5334f 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,9 @@ If no file is given, the editor opens an empty, unnamed buffer. | Backspace / Delete | Delete a character or the selection | | Esc | Clear the selection | +Enter also auto-indents: a new line inherits the current line's leading +whitespace. + ## License GNU GPL 3.0. diff --git a/src/editor.c b/src/editor.c index 707ca8f..598a15a 100644 --- a/src/editor.c +++ b/src/editor.c @@ -497,10 +497,24 @@ void editor_insert_newline(editor_t *ed) if (editor_has_selection(ed)) { delete_selection(ed); } + + /* Auto-indent: carry the current line's leading whitespace over to the + * new line, unless the cursor is still inside that whitespace (the + * split already carries it over correctly in that case). */ + size_t indent_len = first_non_blank(&ed->buf.lines[ed->cy]); + if (ed->cx < indent_len) { + indent_len = 0; + } + buffer_split_line(&ed->buf, ed->cy, ed->cx); ed->cy += 1; ed->cx = 0; - ed->goal_rx = 0; + + if (indent_len > 0) { + buffer_insert_bytes(&ed->buf, ed->cy, 0, ed->buf.lines[ed->cy - 1].chars, indent_len); + ed->cx = indent_len; + } + ed->goal_rx = editor_cx_to_rx(&ed->buf.lines[ed->cy], ed->cx); commit_edit(ed, &cap, ed->cy, ed->cx); } diff --git a/tests/meson.build b/tests/meson.build index 3e6ee03..78e8ab2 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,4 +1,4 @@ -test_names = ['buffer', 'search', 'encoding', 'filetype', 'editor', 'undo', 'clipboard', 'replace'] +test_names = ['buffer', 'search', 'encoding', 'filetype', 'editor', 'undo', 'clipboard', 'replace', 'autoindent'] foreach name : test_names test_exe = executable( diff --git a/tests/test_autoindent.c b/tests/test_autoindent.c new file mode 100644 index 0000000..6589d71 --- /dev/null +++ b/tests/test_autoindent.c @@ -0,0 +1,98 @@ +/* SPDX-FileCopyrightText: AnmiTaliDev + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#include "editor.h" + +static void set_line(editor_t *ed, size_t row, const char *text) +{ + buffer_insert_bytes(&ed->buf, row, 0, text, strlen(text)); +} + +static void test_indent_carried_from_end_of_line(void) +{ + editor_t ed; + editor_init(&ed, 24, 80); + set_line(&ed, 0, " foo();"); + ed.cy = 0; + ed.cx = ed.buf.lines[0].len; + + editor_insert_newline(&ed); + + assert(ed.buf.num_lines == 2); + assert(memcmp(ed.buf.lines[0].chars, " foo();", 10) == 0); + assert(ed.buf.lines[1].len == 4); + assert(memcmp(ed.buf.lines[1].chars, " ", 4) == 0); + assert(ed.cy == 1 && ed.cx == 4); + + editor_undo(&ed); + assert(ed.buf.num_lines == 1); + assert(ed.buf.lines[0].len == 10); + + editor_free(&ed); +} + +static void test_indent_carried_mid_line(void) +{ + editor_t ed; + editor_init(&ed, 24, 80); + set_line(&ed, 0, " if (x) {y}"); + ed.cy = 0; + ed.cx = 9; /* right after " if (x) " (before '{') */ + + editor_insert_newline(&ed); + + assert(ed.buf.num_lines == 2); + assert(memcmp(ed.buf.lines[0].chars, " if (x) ", 9) == 0); + assert(memcmp(ed.buf.lines[1].chars, " {y}", 5) == 0); + assert(ed.cx == 2); + + editor_free(&ed); +} + +static void test_no_indent_duplicated_when_splitting_inside_whitespace(void) +{ + editor_t ed; + editor_init(&ed, 24, 80); + set_line(&ed, 0, " x"); + ed.cy = 0; + ed.cx = 2; /* inside the leading whitespace */ + + editor_insert_newline(&ed); + + assert(ed.buf.num_lines == 2); + assert(memcmp(ed.buf.lines[0].chars, " ", 2) == 0); + assert(ed.buf.lines[1].len == 3); + assert(memcmp(ed.buf.lines[1].chars, " x", 3) == 0); + + editor_free(&ed); +} + +static void test_no_indent_on_flush_left_line(void) +{ + editor_t ed; + editor_init(&ed, 24, 80); + set_line(&ed, 0, "hello"); + ed.cy = 0; + ed.cx = 5; + + editor_insert_newline(&ed); + + assert(ed.buf.num_lines == 2); + assert(ed.buf.lines[1].len == 0); + assert(ed.cx == 0); + + editor_free(&ed); +} + +int main(void) +{ + test_indent_carried_from_end_of_line(); + test_indent_carried_mid_line(); + test_no_indent_duplicated_when_splitting_inside_whitespace(); + test_no_indent_on_flush_left_line(); + return 0; +}