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; +}