diff --git a/src/buffer/Buffer.zig b/src/buffer/Buffer.zig index 03bbe2d..01222fc 100644 --- a/src/buffer/Buffer.zig +++ b/src/buffer/Buffer.zig @@ -333,21 +333,21 @@ const Node = union(enum) { }; } - fn collect(self: *const Node, l: *ArrayList(*const Node)) !void { + fn collect(self: *const Node, allocator: Allocator, l: *ArrayList(*const Node)) !void { switch (self.*) { .node => |*node| { - try node.left.collect(l); - try node.right.collect(l); + try node.left.collect(allocator, l); + try node.right.collect(allocator, l); }, - .leaf => (try l.addOne()).* = self, + .leaf => (try l.addOne(allocator)).* = self, } } fn collect_leaves(self: *const Node, allocator: Allocator) ![]*const Node { - var leaves = ArrayList(*const Node).init(allocator); - try leaves.ensureTotalCapacity(self.lines()); - try self.collect(&leaves); - return leaves.toOwnedSlice(); + var leaves: ArrayList(*const Node) = .empty; + try leaves.ensureTotalCapacity(allocator, self.lines()); + try self.collect(allocator, &leaves); + return leaves.toOwnedSlice(allocator); } fn walk_const(self: *const Node, f: Walker.F, ctx: *anyopaque, metrics: Metrics) Walker { @@ -751,12 +751,12 @@ const Node = union(enum) { return Node.new(allocator, try merge_in_place(leaves[0..mid], allocator), try merge_in_place(leaves[mid..], allocator)); } - pub fn get_line(self: *const Node, line: usize, result: *ArrayList(u8), metrics: Metrics) !void { + pub fn get_line(self: *const Node, line: usize, result: *std.Io.Writer, metrics: Metrics) !void { const Ctx = struct { - line: *ArrayList(u8), + line: *std.Io.Writer, fn walker(ctx_: *anyopaque, leaf: *const Leaf, _: Metrics) Walker { const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_))); - ctx.line.appendSlice(leaf.buf) catch |e| return .{ .err = e }; + ctx.line.writeAll(leaf.buf) catch |e| return .{ .err = e }; return if (!leaf.eol) Walker.keep_walking else Walker.stop; } }; @@ -1039,53 +1039,34 @@ const Node = union(enum) { }; } - pub fn get_byte_pos(self: *const Node, pos_: Cursor, metrics_: Metrics, eol_mode: EolMode) !usize { - const Ctx = struct { + pub fn get_byte_pos(self: *const Node, pos_: Cursor, metrics: Metrics, eol_mode: EolMode) !usize { + const ctx_ = struct { + pos: usize = 0, line: usize = 0, - abs_col: usize = 0, - pos: Cursor, - byte_pos: usize = 0, - metrics: Metrics, - const Ctx = @This(); - const Writer = std.io.Writer(*Ctx, error{Stop}, write); - fn write(ctx: *Ctx, bytes: []const u8) error{Stop}!usize { - if (ctx.line >= ctx.pos.row) { - return ctx.get_col_bytes(bytes, bytes.len); - } else for (bytes, 1..) |char, i| { - ctx.byte_pos += 1; - if (char == '\n') { - ctx.line += 1; - if (ctx.line >= ctx.pos.row) - return ctx.get_col_bytes(bytes[i..], bytes.len); - } + col: usize = 0, + target_line: usize, + target_col: usize, + eol_mode: EolMode, + fn walker(ctx_: *anyopaque, egc: []const u8, wcwidth: usize, _: Metrics) Walker { + const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_))); + if (ctx.line == ctx.target_line and ctx.col == ctx.target_col) return Walker.stop; + if (egc[0] == '\n') { + ctx.pos += switch (ctx.eol_mode) { + .lf => 1, + .crlf => 2, + }; + ctx.line += 1; + ctx.col = 0; + } else { + ctx.pos += egc.len; + ctx.col += wcwidth; } - return bytes.len; - } - fn get_col_bytes(ctx: *Ctx, bytes: []const u8, result: usize) error{Stop}!usize { - var buf: []const u8 = bytes; - while (buf.len > 0) { - if (ctx.abs_col >= ctx.pos.col) return error.Stop; - if (buf[0] == '\n') return error.Stop; - var cols: c_int = undefined; - const egc_bytes = ctx.metrics.egc_length(ctx.metrics, buf, &cols, ctx.abs_col); - ctx.abs_col += @intCast(cols); - ctx.byte_pos += egc_bytes; - buf = buf[egc_bytes..]; - } - return result; - } - fn writer(ctx: *Ctx) Writer { - return .{ .context = ctx }; + return Walker.keep_walking; } }; - var ctx: Ctx = .{ - .pos = pos_, - .metrics = metrics_, - }; - self.store(ctx.writer(), eol_mode) catch |e| switch (e) { - error.Stop => return ctx.byte_pos, - }; - return error.NotFound; + var ctx: ctx_ = .{ .target_line = pos_.row, .target_col = pos_.col, .eol_mode = eol_mode }; + self.walk_egc_forward(0, ctx_.walker, &ctx, metrics) catch {}; + return ctx.pos; } pub fn debug_render_chunks(self: *const Node, allocator: std.mem.Allocator, line: usize, metrics_: Metrics) ![]const u8 { @@ -1119,6 +1100,49 @@ const Node = union(enum) { break :blk l.toOwnedSlice(); } else error.NotFound; } + + pub fn write_range( + self: *const Node, + sel: Selection, + writer: *std.Io.Writer, + wcwidth_: ?*usize, + metrics: Metrics, + ) std.Io.Writer.Error!void { + const Ctx = struct { + col: usize = 0, + sel: Selection, + writer: *std.Io.Writer, + wcwidth: usize = 0, + fn walker(ctx_: *anyopaque, egc: []const u8, wcwidth: usize, _: Metrics) Walker { + const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_))); + if (ctx.col < ctx.sel.begin.col) { + ctx.col += wcwidth; + return Walker.keep_walking; + } + _ = ctx.writer.write(egc) catch |e| return Walker{ .err = e }; + ctx.wcwidth += wcwidth; + if (egc[0] == '\n') { + ctx.col = 0; + ctx.sel.begin.col = 0; + ctx.sel.begin.row += 1; + } else { + ctx.col += wcwidth; + ctx.sel.begin.col += wcwidth; + } + return if (ctx.sel.begin.eql(ctx.sel.end)) + Walker.stop + else + Walker.keep_walking; + } + }; + + var ctx: Ctx = .{ .sel = sel, .writer = writer }; + ctx.sel.normalize(); + if (sel.begin.eql(sel.end)) + return; + self.walk_egc_forward(sel.begin.row, Ctx.walker, &ctx, metrics) catch return error.WriteFailed; + if (wcwidth_) |p| p.* = ctx.wcwidth; + } }; pub fn create(allocator: Allocator) error{OutOfMemory}!*Self { @@ -1346,8 +1370,8 @@ pub fn refresh_from_file(self: *Self) LoadFromFileError!void { } pub fn store_to_string(self: *const Self, allocator: Allocator, eol_mode: EolMode) ![]u8 { - var s = try ArrayList(u8).initCapacity(allocator, self.root.weights_sum().len); - try self.root.store(s.writer(), eol_mode); + var s: std.Io.Writer.Allocating = try .initCapacity(allocator, self.root.weights_sum().len); + try self.root.store(&s.writer, eol_mode); return s.toOwnedSlice(); } diff --git a/src/keybind/keybind.zig b/src/keybind/keybind.zig index e4839ae..91253c7 100644 --- a/src/keybind/keybind.zig +++ b/src/keybind/keybind.zig @@ -804,7 +804,7 @@ test "parse" { const actual: []const KeyEvent = parsed; try expectEqual(expected.len, actual.len); for (expected, 0..) |expected_event, i| { - try std.testing.expectFmt(expected_event, "{}", .{actual[i]}); + try std.testing.expectFmt(expected_event, "{f}", .{actual[i]}); } } } diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 5016c3d..fadd940 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -820,10 +820,10 @@ pub const Editor = struct { fn update_buf_and_eol_mode(self: *Self, root: Buffer.Root, eol_mode: Buffer.EolMode, utf8_sanitized: bool) !void { const b = self.buffer orelse return error.Stop; var sfa = std.heap.stackFallback(512, self.allocator); - const allocator = sfa.get(); + const sfa_allocator = sfa.get(); if (!self.pause_undo) { - const meta = try self.store_undo_meta(allocator); - defer allocator.free(meta); + const meta = try self.store_undo_meta(sfa_allocator); + defer sfa_allocator.free(meta); try b.store_undo(meta); } b.update(root); @@ -850,9 +850,9 @@ pub const Editor = struct { try self.send_editor_jump_source(); self.cancel_all_matches(); var sfa = std.heap.stackFallback(512, self.allocator); - const allocator = sfa.get(); - const redo_metadata = try self.store_current_undo_meta(allocator); - defer allocator.free(redo_metadata); + const sfa_allocator = sfa.get(); + const redo_metadata = try self.store_current_undo_meta(sfa_allocator); + defer sfa_allocator.free(redo_metadata); const meta = b_mut.undo(redo_metadata) catch |e| switch (e) { error.Stop => { self.logger.print("nothing to undo", .{}); @@ -893,9 +893,9 @@ pub const Editor = struct { self.pause_undo = false; const b = self.buffer orelse return; var sfa = std.heap.stackFallback(512, self.allocator); - const allocator = sfa.get(); - const meta = try self.store_undo_meta(allocator); - defer allocator.free(meta); + const sfa_allocator = sfa.get(); + const meta = try self.store_undo_meta(sfa_allocator); + defer sfa_allocator.free(meta); const root = self.buf_root() catch return; if (self.pause_undo_root) |paused_root| b.update(paused_root); try b.store_undo(meta); @@ -947,49 +947,6 @@ pub const Editor = struct { return ctx.col; } - fn write_range( - self: *const Self, - root: Buffer.Root, - sel: Selection, - writer: *std.Io.Writer, - wcwidth_: ?*usize, - ) std.Io.Writer.Error!void { - const Ctx = struct { - col: usize = 0, - sel: Selection, - writer: *std.Io.Writer, - wcwidth: usize = 0, - fn walker(ctx_: *anyopaque, egc: []const u8, wcwidth: usize, _: Buffer.Metrics) Buffer.Walker { - const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_))); - if (ctx.col < ctx.sel.begin.col) { - ctx.col += wcwidth; - return Buffer.Walker.keep_walking; - } - _ = ctx.writer.write(egc) catch |e| return Buffer.Walker{ .err = e }; - ctx.wcwidth += wcwidth; - if (egc[0] == '\n') { - ctx.col = 0; - ctx.sel.begin.col = 0; - ctx.sel.begin.row += 1; - } else { - ctx.col += wcwidth; - ctx.sel.begin.col += wcwidth; - } - return if (ctx.sel.begin.eql(ctx.sel.end)) - Buffer.Walker.stop - else - Buffer.Walker.keep_walking; - } - }; - - var ctx: Ctx = .{ .sel = sel, .writer = writer }; - ctx.sel.normalize(); - if (sel.begin.eql(sel.end)) - return; - root.walk_egc_forward(sel.begin.row, Ctx.walker, &ctx, self.metrics) catch return error.WriteFailed; - if (wcwidth_) |p| p.* = ctx.wcwidth; - } - pub fn update(self: *Self) void { self.update_scroll(); self.update_event() catch {}; @@ -3617,8 +3574,9 @@ pub const Editor = struct { const saved = cursel.*; const sel = cursel.expand_selection_to_line(root, self.metrics) catch return error.Stop; var sfa = std.heap.stackFallback(4096, self.allocator); - const cut_text = copy_selection(root, sel.*, sfa.get(), self.metrics) catch return error.Stop; - defer allocator.free(cut_text); + const sfa_allocator = sfa.get(); + const cut_text = copy_selection(root, sel.*, sfa_allocator, self.metrics) catch return error.Stop; + defer sfa_allocator.free(cut_text); root = try self.delete_selection(root, cursel, allocator); try cursel.cursor.move_up(root, self.metrics); root = self.insert(root, cursel, cut_text, allocator) catch return error.Stop; @@ -3644,8 +3602,9 @@ pub const Editor = struct { const saved = cursel.*; const sel = cursel.expand_selection_to_line(root, self.metrics) catch return error.Stop; var sfa = std.heap.stackFallback(4096, self.allocator); - const cut_text = copy_selection(root, sel.*, sfa.get(), self.metrics) catch return error.Stop; - defer allocator.free(cut_text); + const sfa_allocator = sfa.get(); + const cut_text = copy_selection(root, sel.*, sfa_allocator, self.metrics) catch return error.Stop; + defer sfa_allocator.free(cut_text); root = try self.delete_selection(root, cursel, allocator); try cursel.cursor.move_down(root, self.metrics); root = self.insert(root, cursel, cut_text, allocator) catch return error.Stop; @@ -3671,8 +3630,9 @@ pub const Editor = struct { const sel: Selection = if (cursel.selection) |sel_| sel_ else Selection.line_from_cursor(cursel.cursor, root, self.metrics); cursel.disable_selection(root, self.metrics); var sfa = std.heap.stackFallback(4096, self.allocator); - const text = copy_selection(root, sel, sfa.get(), self.metrics) catch return error.Stop; - defer allocator.free(text); + const sfa_allocator = sfa.get(); + const text = copy_selection(root, sel, sfa_allocator, self.metrics) catch return error.Stop; + defer sfa_allocator.free(text); cursel.cursor = sel.begin; root = self.insert(root, cursel, text, allocator) catch return error.Stop; cursel.selection = .{ .begin = sel.begin, .end = sel.end }; @@ -3693,8 +3653,9 @@ pub const Editor = struct { const sel: Selection = if (cursel.selection) |sel_| sel_ else Selection.line_from_cursor(cursel.cursor, root, self.metrics); cursel.disable_selection(root, self.metrics); var sfa = std.heap.stackFallback(4096, self.allocator); - const text = copy_selection(root, sel, sfa.get(), self.metrics) catch return error.Stop; - defer allocator.free(text); + const sfa_allocator = sfa.get(); + const text = copy_selection(root, sel, sfa_allocator, self.metrics) catch return error.Stop; + defer sfa_allocator.free(text); cursel.cursor = sel.end; root = self.insert(root, cursel, text, allocator) catch return error.Stop; cursel.selection = .{ .begin = sel.end, .end = cursel.cursor }; @@ -3714,11 +3675,12 @@ pub const Editor = struct { const saved = cursel.*; const sel = cursel.expand_selection_to_line(root, self.metrics) catch return error.Stop; var sfa = std.heap.stackFallback(4096, self.allocator); - const alloc = sfa.get(); - const text = copy_selection(root, sel.*, alloc, self.metrics) catch return error.Stop; - defer allocator.free(text); + const sfa_allocator = sfa.get(); + const text = copy_selection(root, sel.*, sfa_allocator, self.metrics) catch return error.Stop; + defer sfa_allocator.free(text); root = try self.delete_selection(root, cursel, allocator); - const new_text = text_manip.toggle_prefix_in_text(self.prefix, text, alloc) catch return error.Stop; + const new_text = text_manip.toggle_prefix_in_text(self.prefix, text, sfa_allocator) catch return error.Stop; + defer sfa_allocator.free(new_text); root = self.insert(root, cursel, new_text, allocator) catch return error.Stop; cursel.* = saved; cursel.cursor.clamp_to_buffer(root, self.metrics); @@ -4514,8 +4476,8 @@ pub const Editor = struct { const row = cursel.cursor.row; const leading_ws = @min(find_first_non_ws(root, row, self.metrics), cursel.cursor.col); var sfa = std.heap.stackFallback(512, self.allocator); - const allocator = sfa.get(); - var stream: std.Io.Writer.Allocating = .init(allocator); + const sfa_allocator = sfa.get(); + var stream: std.Io.Writer.Allocating = .init(sfa_allocator); defer stream.deinit(); const writer = &stream.writer; _ = try writer.write("\n"); @@ -4591,8 +4553,8 @@ pub const Editor = struct { const row = cursel.cursor.row; try move_cursor_left(root, &cursel.cursor, self.metrics); var sfa = std.heap.stackFallback(512, self.allocator); - const allocator = sfa.get(); - var stream: std.Io.Writer.Allocating = .init(allocator); + const sfa_allocator = sfa.get(); + var stream: std.Io.Writer.Allocating = .init(sfa_allocator); defer stream.deinit(); try self.generate_leading_ws(&stream.writer, leading_ws); if (stream.written().len > 0) @@ -4624,8 +4586,8 @@ pub const Editor = struct { const row = cursel.cursor.row; try move_cursor_end(root, &cursel.cursor, self.metrics); var sfa = std.heap.stackFallback(512, self.allocator); - const allocator = sfa.get(); - var stream: std.Io.Writer.Allocating = .init(allocator); + const sfa_allocator = sfa.get(); + var stream: std.Io.Writer.Allocating = .init(sfa_allocator); defer stream.deinit(); try stream.writer.writeAll("\n"); try self.generate_leading_ws(&stream.writer, leading_ws); @@ -5840,7 +5802,7 @@ pub const Editor = struct { } var sp_buf: [tp.subprocess.max_chunk_size]u8 = undefined; var writer = sp.writer(&sp_buf); - try self.write_range(state.before_root, sel, &writer.interface, null); + try state.before_root.write_range(sel, &writer.interface, null, self.metrics); try writer.interface.flush(); self.logger.print("filter: sent", .{}); state.work_root = try state.work_root.delete_range(sel, buf_a_, null, self.metrics); @@ -5930,10 +5892,11 @@ pub const Editor = struct { break :ret sel; }; var sfa = std.heap.stackFallback(4096, self.allocator); - const cut_text = copy_selection(root, sel.*, sfa.get(), self.metrics) catch return error.Stop; - defer allocator.free(cut_text); - const ucased = Buffer.unicode.get_letter_casing().toUpperStr(allocator, cut_text) catch return error.Stop; - defer allocator.free(ucased); + const sfa_allocator = sfa.get(); + const cut_text = copy_selection(root, sel.*, sfa_allocator, self.metrics) catch return error.Stop; + defer sfa_allocator.free(cut_text); + const ucased = Buffer.unicode.get_letter_casing().toUpperStr(sfa_allocator, cut_text) catch return error.Stop; + defer sfa_allocator.free(ucased); root = try self.delete_selection(root, cursel, allocator); root = self.insert(root, cursel, ucased, allocator) catch return error.Stop; cursel.* = saved; @@ -5958,10 +5921,11 @@ pub const Editor = struct { break :ret sel; }; var sfa = std.heap.stackFallback(4096, self.allocator); - const cut_text = copy_selection(root, sel.*, sfa.get(), self.metrics) catch return error.Stop; - defer allocator.free(cut_text); - const ucased = Buffer.unicode.get_letter_casing().toLowerStr(allocator, cut_text) catch return error.Stop; - defer allocator.free(ucased); + const sfa_allocator = sfa.get(); + const cut_text = copy_selection(root, sel.*, sfa_allocator, self.metrics) catch return error.Stop; + defer sfa_allocator.free(cut_text); + const ucased = Buffer.unicode.get_letter_casing().toLowerStr(sfa_allocator, cut_text) catch return error.Stop; + defer sfa_allocator.free(ucased); root = try self.delete_selection(root, cursel, allocator); root = self.insert(root, cursel, ucased, allocator) catch return error.Stop; cursel.* = saved; @@ -5987,7 +5951,7 @@ pub const Editor = struct { }; var range: std.Io.Writer.Allocating = .init(self.allocator); defer range.deinit(); - self.write_range(root, sel.*, &range.writer, null) catch return error.Stop; + root.write_range(sel.*, &range.writer, null, self.metrics) catch return error.Stop; const bytes = range.written(); const letter_casing = Buffer.unicode.get_letter_casing(); diff --git a/test/tests_buffer.zig b/test/tests_buffer.zig index 95ab500..5208748 100644 --- a/test/tests_buffer.zig +++ b/test/tests_buffer.zig @@ -28,51 +28,18 @@ fn metrics() Buffer.Metrics { } fn get_big_doc(eol_mode: *Buffer.EolMode) !*Buffer { - const BigDocGen = struct { - line_num: usize = 0, - lines: usize = 10000, + const nl_lines = 10000; - buf: [128]u8 = undefined, - line_buf: []u8 = "", - read_count: usize = 0, - - const Self = @This(); - const Reader = std.io.Reader(*Self, Err, read); - const Err = error{NoSpaceLeft}; - - fn gen_line(self: *Self) Err!void { - var stream = std.io.fixedBufferStream(&self.buf); - const writer = stream.writer(); - try writer.print("this is line {d}\n", .{self.line_num}); - self.line_buf = stream.getWritten(); - self.read_count = 0; - self.line_num += 1; - } - - fn read(self: *Self, buffer: []u8) Err!usize { - if (self.line_num > self.lines) - return 0; - if (self.line_buf.len == 0 or self.line_buf.len - self.read_count == 0) - try self.gen_line(); - const read_count = self.read_count; - const bytes_to_read = @min(self.line_buf.len - read_count, buffer.len); - @memcpy(buffer[0..bytes_to_read], self.line_buf[read_count .. read_count + bytes_to_read]); - self.read_count += bytes_to_read; - return bytes_to_read; - } - - fn reader(self: *Self) Reader { - return .{ .context = self }; - } - }; - var gen: BigDocGen = .{}; - var doc = ArrayList(u8).init(a); + var doc: std.Io.Writer.Allocating = .init(a); defer doc.deinit(); - try gen.reader().readAllArrayList(&doc, std.math.maxInt(usize)); + + for (0..nl_lines) |line_num| { + try doc.writer.print("this is line {d}\n", .{line_num}); + } + var buf = try Buffer.create(a); - var fis = std.io.fixedBufferStream(doc.items); var sanitized: bool = false; - buf.update(try buf.load(fis.reader(), doc.items.len, eol_mode, &sanitized)); + buf.update(try buf.load_from_string(doc.written(), eol_mode, &sanitized)); return buf; } @@ -108,8 +75,8 @@ test "buffer" { } fn get_line(buf: *const Buffer, line: usize) ![]const u8 { - var result = ArrayList(u8).init(a); - try buf.root.get_line(line, &result, metrics()); + var result: std.Io.Writer.Allocating = .init(a); + try buf.root.get_line(line, &result.writer, metrics()); return result.toOwnedSlice(); } @@ -119,7 +86,7 @@ test "walk_from_line" { defer buffer.deinit(); const lines = buffer.root.lines(); - try std.testing.expectEqual(lines, 10002); + try std.testing.expectEqual(lines, 10001); const line0 = try get_line(buffer, 0); defer a.free(line0); @@ -135,7 +102,7 @@ test "walk_from_line" { const line9999 = try get_line(buffer, 9999); defer a.free(line9999); - try std.testing.expect(std.mem.eql(u8, line9999, "this is line 9999")); + try std.testing.expectEqualDeep("this is line 9999", line9999); } test "line_len" { @@ -394,7 +361,7 @@ test "get_from_pos" { defer buffer.deinit(); const lines = buffer.root.lines(); - try std.testing.expectEqual(lines, 10002); + try std.testing.expectEqual(lines, 10001); const line0 = try get_line(buffer, 0); defer a.free(line0);