From 8adab79d5399503c222e013499702d3bbff623ef Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 14 Apr 2026 23:55:44 +0200 Subject: [PATCH] feat: auto detect indent size when loading buffers closes #536 --- src/buffer/Buffer.zig | 39 +++++++++++++++++++++++++++++++++++++++ src/tui/editor.zig | 8 +++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/buffer/Buffer.zig b/src/buffer/Buffer.zig index 9da402e2..e08168c4 100644 --- a/src/buffer/Buffer.zig +++ b/src/buffer/Buffer.zig @@ -52,6 +52,7 @@ file_exists: bool = true, file_eol_mode: EolMode = .lf, last_save_eol_mode: EolMode = .lf, file_utf8_sanitized: bool = false, +detected_indent_size: ?usize = null, hidden: bool = false, ephemeral: bool = false, auto_save: bool = false, @@ -1395,9 +1396,47 @@ pub fn load(self: *const Self, reader: *std.Io.Reader, eol_mode: *EolMode, utf8_ leaves[cur_leaf] = .{ .leaf = .{ .buf = line, .bol = true, .eol = false } }; if (leaves.len != cur_leaf + 1) return error.Unexpected; + + self_.detected_indent_size = detect_indent_size(leaves[0..@min(leaves.len, 1000)]); + return Node.merge_in_place(leaves, self.allocator); } +fn detect_indent_size(leaves: []const Node) ?usize { + // frequency of each leading-space count (up to 16 spaces). + const max_spaces = 16; + var freq = std.mem.zeroes([max_spaces + 1]u32); + for (leaves) |leaf_node| { + const line = leaf_node.leaf.buf; + if (line.len == 0) continue; + if (line[0] == '\t') return 0; + var spaces: usize = 0; + for (line) |c| { + if (c == ' ') spaces += 1 else break; + } + if (spaces == 0 or spaces > max_spaces) continue; + freq[spaces] += 1; + } + + // find the 3 most frequently occurring indent levels + var top = [3]usize{ 0, 0, 0 }; + for (1..freq.len) |n| { + if (freq[n] > freq[top[2]]) { + top[2] = n; + if (freq[top[2]] > freq[top[1]]) std.mem.swap(usize, &top[1], &top[2]); + if (freq[top[1]] > freq[top[0]]) std.mem.swap(usize, &top[0], &top[1]); + } + } + + // GCD of the top indent levels gives the base indent unit + var gcd: usize = 0; + for (top) |n| { + if (n == 0) continue; + gcd = if (gcd == 0) n else std.math.gcd(gcd, n); + } + return if (gcd > 0) gcd else null; +} + pub fn load_from_string(self: *const Self, s: []const u8, eol_mode: *EolMode, utf8_sanitized: *bool) LoadError!Root { var reader = std.Io.Reader.fixed(s); return self.load(&reader, eol_mode, utf8_sanitized); diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 69b45f5f..f999dd3a 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -831,6 +831,13 @@ pub const Editor = struct { } fn detect_indent_mode(self: *Self, content: []const u8) void { + if (self.buffer) |buf| { + if (buf.detected_indent_size) |detected_indent_size| { + self.indent_size = detected_indent_size; + self.indent_mode = .spaces; + return; + } + } var it = std.mem.splitScalar(u8, content, '\n'); while (it.next()) |line| { if (line.len == 0) continue; @@ -842,7 +849,6 @@ pub const Editor = struct { } self.indent_size = tui.config().indent_size; self.indent_mode = .spaces; - return; } fn refresh_tab_width(self: *Self) void {