From 6ee4c3caa6faf42b9825dca39e9130ac83f425a8 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 18:07:32 -0300 Subject: Enter, o and O give the new line the old one's indent as it is, tabs and all, as acme's autoindent does; a level more after an opening delimiter is a tab where the indent is tabs The indent was rebuilt as spaces, partial levels dropped, so a tab-indented line went on with spaces (or with one space, after Enter). Co-Authored-By: Claude Opus 5.5 --- src/edit.zig | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) (limited to 'src/edit.zig') diff --git a/src/edit.zig b/src/edit.zig index 3e3ba341..bf65935e 100644 --- a/src/edit.zig +++ b/src/edit.zig @@ -899,11 +899,18 @@ fn insertEdit(p: *Pardes, t: *Text, key: Key) void { t.show(); return; } - const indent = modal.newlineIndentWidth(line, c.col); + // The line's own indent, as it is (tabs stay tabs), and a + // level more after an opening delimiter: a tab where the + // indent is tabs, else spaces. + const lead = modal.indentText(line); + const more = modal.newlineIndentWidth(line, c.col) - lead.len; + const tabbed = std.mem.indexOfScalar(u8, lead, '\t') != null; + const indent = lead.len + (if (more == 0) 0 else if (tabbed) 1 else more); const arena = p.scratch.allocator(); const block = arena.alloc(u8, 1 + indent) catch return; block[0] = '\n'; - @memset(block[1..], ' '); + @memcpy(block[1..][0..lead.len], lead); + @memset(block[1 + lead.len ..], if (tabbed) '\t' else ' '); // a tag keeps its blanks: they part its words, and the last // may be its read-only prefix's const kept = if (t.what == .body) std.mem.trimEnd(u8, before, " \t").len else c.col; @@ -2136,6 +2143,29 @@ test "insert newline adds one indent level after a closing call" { try std.testing.expectEqual(@as(i32, 8), pane.body.cur_col); } +test "Enter in insert mode keeps a tab-indented line's tabs, as o and O do" { + const gpa = std.testing.allocator; + const p = try Pardes.init(gpa, .{ .tty_only = true }); + defer p.deinit(); + while (p.nextEffect()) |_| {} + const pane = try p.setTestFile("\t\tvalue = 1;\n\tif (x) {\n"); + pane.body.mode = .insert; + pane.body.cur_col = 12; + p.update(.{ .key = .{ .cp = Key.enter } }); + try std.testing.expectEqualStrings("\t\tvalue = 1;\n\t\t\n\tif (x) {\n", pane.file.?.content); + try std.testing.expectEqual(@as(i32, 2), pane.body.cur_col); + // After an opening brace, a level more: a tab, as the indent is tabs. + pane.body.cur_row = 2; + pane.body.cur_col = 9; + p.update(.{ .key = .{ .cp = Key.enter } }); + try std.testing.expectEqualStrings("\t\tvalue = 1;\n\t\t\n\tif (x) {\n\t\t\n", pane.file.?.content); + // o opens a line with the indent as it is. + p.update(.{ .key = .{ .cp = Key.escape } }); + pane.body.cur_row = 0; + p.update(.{ .key = .{ .cp = 'o' } }); + try std.testing.expect(std.mem.startsWith(u8, pane.file.?.content, "\t\tvalue = 1;\n\t\t\n")); +} + test "insert and normal modes edit complete Unicode graphemes" { const gpa = std.testing.allocator; const p = try Pardes.init(gpa, .{ .cols = 60, .rows = 12 }); -- cgit v1.3