diff --git a/src/buffer/Buffer.zig b/src/buffer/Buffer.zig index 7d6c5d9..c736d82 100644 --- a/src/buffer/Buffer.zig +++ b/src/buffer/Buffer.zig @@ -794,6 +794,35 @@ const Node = union(enum) { return if (found) ctx.result else error.NotFound; } + pub fn byte_offset_to_line_and_col(self: *const Node, pos: usize, metrics: Metrics, eol_mode: EolMode) Cursor { + const ctx_ = struct { + pos: usize, + line: usize = 0, + col: usize = 0, + eol_mode: EolMode, + fn walker(ctx_: *anyopaque, egc: []const u8, wcwidth: usize, _: Metrics) Walker { + const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_))); + if (egc[0] == '\n') { + ctx.pos -= switch (ctx.eol_mode) { + .lf => 1, + .crlf => @min(2, ctx.pos), + }; + if (ctx.pos == 0) return Walker.stop; + ctx.line += 1; + ctx.col = 0; + } else { + ctx.pos -= @min(egc.len, ctx.pos); + if (ctx.pos == 0) return Walker.stop; + ctx.col += wcwidth; + } + return Walker.keep_walking; + } + }; + var ctx: ctx_ = .{ .pos = pos + 1, .eol_mode = eol_mode }; + self.walk_egc_forward(0, ctx_.walker, &ctx, metrics) catch {}; + return .{ .row = ctx.line, .col = ctx.col }; + } + pub fn insert_chars( self_: *const Node, line_: usize, diff --git a/test/tests_buffer.zig b/test/tests_buffer.zig index 87eef41..95ab500 100644 --- a/test/tests_buffer.zig +++ b/test/tests_buffer.zig @@ -414,3 +414,44 @@ test "get_from_pos" { const result3 = buffer.root.get_from_pos(.{ .row = 1, .col = 5 }, &result_buf, metrics()); try std.testing.expectEqualDeep(result3[0 .. line1.len - 4], line1[4..]); } + +test "byte_offset_to_line_and_col" { + const doc: []const u8 = + \\All your + \\ropes + \\are belong to + \\us! + \\All your + \\ropes + \\are belong to + \\us! + \\All your + \\ropes + \\are belong to + \\us! + ; + var eol_mode: Buffer.EolMode = .lf; + var sanitized: bool = false; + const buffer = try Buffer.create(a); + defer buffer.deinit(); + buffer.update(try buffer.load_from_string(doc, &eol_mode, &sanitized)); + + try std.testing.expectEqual(Buffer.Cursor{ .row = 0, .col = 0 }, buffer.root.byte_offset_to_line_and_col(0, metrics(), eol_mode)); + try std.testing.expectEqual(Buffer.Cursor{ .row = 0, .col = 8 }, buffer.root.byte_offset_to_line_and_col(8, metrics(), eol_mode)); + try std.testing.expectEqual(Buffer.Cursor{ .row = 1, .col = 0 }, buffer.root.byte_offset_to_line_and_col(9, metrics(), eol_mode)); + try std.testing.expectEqual(Buffer.Cursor{ .row = 1, .col = 2 }, buffer.root.byte_offset_to_line_and_col(11, metrics(), eol_mode)); + try std.testing.expectEqual(Buffer.Cursor{ .row = 4, .col = 0 }, buffer.root.byte_offset_to_line_and_col(33, metrics(), eol_mode)); + try std.testing.expectEqual(Buffer.Cursor{ .row = 8, .col = 0 }, buffer.root.byte_offset_to_line_and_col(66, metrics(), eol_mode)); + try std.testing.expectEqual(Buffer.Cursor{ .row = 11, .col = 2 }, buffer.root.byte_offset_to_line_and_col(97, metrics(), eol_mode)); + + eol_mode = .crlf; + + try std.testing.expectEqual(Buffer.Cursor{ .row = 0, .col = 0 }, buffer.root.byte_offset_to_line_and_col(0, metrics(), eol_mode)); + try std.testing.expectEqual(Buffer.Cursor{ .row = 0, .col = 8 }, buffer.root.byte_offset_to_line_and_col(8, metrics(), eol_mode)); + try std.testing.expectEqual(Buffer.Cursor{ .row = 0, .col = 8 }, buffer.root.byte_offset_to_line_and_col(9, metrics(), eol_mode)); + try std.testing.expectEqual(Buffer.Cursor{ .row = 1, .col = 0 }, buffer.root.byte_offset_to_line_and_col(10, metrics(), eol_mode)); + try std.testing.expectEqual(Buffer.Cursor{ .row = 1, .col = 2 }, buffer.root.byte_offset_to_line_and_col(12, metrics(), eol_mode)); + try std.testing.expectEqual(Buffer.Cursor{ .row = 4, .col = 0 }, buffer.root.byte_offset_to_line_and_col(37, metrics(), eol_mode)); + try std.testing.expectEqual(Buffer.Cursor{ .row = 8, .col = 0 }, buffer.root.byte_offset_to_line_and_col(74, metrics(), eol_mode)); + try std.testing.expectEqual(Buffer.Cursor{ .row = 11, .col = 2 }, buffer.root.byte_offset_to_line_and_col(108, metrics(), eol_mode)); +}