Something went wrong. Try again.
A charm-like tui library
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394const measure = @import("measure.zig");const util = @import("util.zig");const LineSlicer = @This();const std = @import("std");
data: []const u8,/// Byte offset where the next segment starts, or `null` once iteration is/// done. A non-null value (including `data.len`) means one more segment is/// pending, so a trailing `\n` yields a final empty segment.next_index: ?usize = 0,single: bool = false,consumed_single: bool = false,
pub fn init(data: []const u8) LineSlicer { return .{ .data = data };}
pub fn singleLine(data: []const u8) LineSlicer { return .{ .data = data, .single = true };}
/// Returns the next line. Splits on `\n` like lipgloss `getLines`: the result/// has exactly `count('\n') + 1` segments, so a trailing `\n` produces a final/// empty segment and empty input produces one empty segment.pub fn next(self: *LineSlicer) ?[]const u8 { if (self.single) { if (self.consumed_single) return null; self.consumed_single = true; return self.data; } const start = self.next_index orelse return null; if (std.mem.indexOfScalarPos(u8, self.data, start, '\n')) |nl| { self.next_index = nl + 1; return self.data[start..nl]; } self.next_index = null; return self.data[start..];}
pub fn count(self: *LineSlicer) u16 { const saved = self.*; defer self.* = saved; var n: u16 = 0; while (self.next()) |_| n = util.satAdd(n, 1); return n;}
pub fn maxWidth(self: *LineSlicer) u16 { const saved = self.*; defer self.* = saved; var max: u16 = 0; while (self.next()) |line| { const w = measure.cellWidth(line); if (w > max) max = w; } return max;}
test "next splits on newline" { var it: LineSlicer = .init("a\nb\nc"); try std.testing.expectEqualStrings("a", it.next().?); try std.testing.expectEqualStrings("b", it.next().?); try std.testing.expectEqualStrings("c", it.next().?); try std.testing.expectEqual(@as(?[]const u8, null), it.next());}
test "next yields trailing empty line" { var it: LineSlicer = .init("hi\n"); try std.testing.expectEqualStrings("hi", it.next().?); try std.testing.expectEqualStrings("", it.next().?); try std.testing.expectEqual(@as(?[]const u8, null), it.next());}
test "next yields one empty segment for empty input" { var it: LineSlicer = .init(""); try std.testing.expectEqualStrings("", it.next().?); try std.testing.expectEqual(@as(?[]const u8, null), it.next());}
test "count matches lineCount semantics" { var a: LineSlicer = .init(""); try std.testing.expectEqual(@as(u16, 1), a.count()); var b: LineSlicer = .init("a\nb\n"); try std.testing.expectEqual(@as(u16, 3), b.count()); var c: LineSlicer = .init("a\n\nb"); try std.testing.expectEqual(@as(u16, 3), c.count());}
test "singleLine yields whole input once" { var it: LineSlicer = .singleLine("a\nb"); try std.testing.expectEqualStrings("a\nb", it.next().?); try std.testing.expectEqual(@as(?[]const u8, null), it.next());}