diff options
| author | Gabriel Schneider <[email protected]> | 2026-06-30 20:29:05 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-06-30 20:29:27 -0300 |
| commit | 8b60c2c29c016c7db0fa6ee63769cea1defd9b3c (patch) | |
| tree | 24935a622521142b3754df6de0a95c6cef5a5e18 /modal.zig | |
| parent | 1f65d7368c9a31427b145f683eb891da16181b73 (diff) | |
| download | pardes-8b60c2c29c016c7db0fa6ee63769cea1defd9b3c.tar.gz pardes-8b60c2c29c016c7db0fa6ee63769cea1defd9b3c.zip | |
minor fixes
Diffstat (limited to 'modal.zig')
| -rw-r--r-- | modal.zig | 55 |
1 files changed, 55 insertions, 0 deletions
@@ -306,6 +306,38 @@ pub fn deleteLines(alloc: std.mem.Allocator, content: []const u8, r0: usize, r1: return .{ .content = try out.toOwnedSlice(alloc), .deleted = try deleted.toOwnedSlice(alloc) }; } +// normalize two cursors into document order (lo <= hi), then byte offsets of the +// INCLUSIVE range [lo .. hi] (the char under hi is included). e == s means empty. +fn rangeBytes(content: []const u8, a: Cursor, b: Cursor) struct { s: usize, e: usize } { + var lo = a; + var hi = b; + if (hi.row < lo.row or (hi.row == lo.row and hi.col < lo.col)) { + lo = b; + hi = a; + } + const s = lineStartOffset(content, lo.row) + @min(lo.col, lineSlice(content, lo.row).len); + var e = lineStartOffset(content, hi.row) + @min(hi.col, lineSlice(content, hi.row).len); + if (e < content.len) e += 1; // include the char under the head + return .{ .s = s, .e = @max(s, e) }; +} + +// the text of the inclusive char range [a, b] (cursors in either order). Caller frees. +pub fn rangeText(alloc: std.mem.Allocator, content: []const u8, a: Cursor, b: Cursor) ![]u8 { + const r = rangeBytes(content, a, b); + return alloc.dupe(u8, content[r.s..r.e]); +} + +// delete the inclusive char range [a, b]. returns new content + the removed text. +pub fn deleteRange(alloc: std.mem.Allocator, content: []const u8, a: Cursor, b: Cursor) !Deleted { + const r = rangeBytes(content, a, b); + const deleted = try alloc.dupe(u8, content[r.s..r.e]); + errdefer alloc.free(deleted); + var out: std.ArrayList(u8) = .empty; + try out.appendSlice(alloc, content[0..r.s]); + try out.appendSlice(alloc, content[r.e..]); + return .{ .content = try out.toOwnedSlice(alloc), .deleted = deleted }; +} + // replace line `row`'s text with "" (keep the line, empty it). For `c`hange line. pub fn clearLine(alloc: std.mem.Allocator, content: []const u8, row: usize) ![]u8 { const line = lineSlice(content, row); @@ -884,6 +916,29 @@ test "deleteLines only line" { try std.testing.expectEqualStrings("only", d.deleted); } +test "rangeText + deleteRange (char-wise select)" { + const a = std.testing.allocator; + const content = "hello\nworld\nfoo"; + // same-line inclusive range: "hello"[1..3] -> "ell" + const t1 = try rangeText(a, content, .{ .row = 0, .col = 1 }, .{ .row = 0, .col = 3 }); + defer a.free(t1); + try std.testing.expectEqualStrings("ell", t1); + // reversed cursors give the same range + const t2 = try rangeText(a, content, .{ .row = 0, .col = 3 }, .{ .row = 0, .col = 1 }); + defer a.free(t2); + try std.testing.expectEqualStrings("ell", t2); + // cross-line range includes the newline: row0 col3 .. row1 col1 -> "lo\nwo" + const t3 = try rangeText(a, content, .{ .row = 0, .col = 3 }, .{ .row = 1, .col = 1 }); + defer a.free(t3); + try std.testing.expectEqualStrings("lo\nwo", t3); + // delete the same cross-line range + const d = try deleteRange(a, content, .{ .row = 0, .col = 3 }, .{ .row = 1, .col = 1 }); + defer a.free(d.content); + defer a.free(d.deleted); + try std.testing.expectEqualStrings("helrld\nfoo", d.content); + try std.testing.expectEqualStrings("lo\nwo", d.deleted); +} + test "clearLine" { const content = "keep\nzap me\nkeep2"; const a = std.testing.allocator; |
