this repo has no description
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

at b698a3641da7f3886bde4aeda7fcecc8d3642580 904 lines 31 kB view raw
1const std = @import("std"); 2 3const Screen = @import("Screen.zig"); 4const Cell = @import("Cell.zig"); 5const Mouse = @import("Mouse.zig"); 6const Segment = @import("Cell.zig").Segment; 7const Unicode = @import("Unicode.zig"); 8const gw = @import("gwidth.zig"); 9 10const Window = @This(); 11 12/// absolute horizontal offset from the screen 13x_off: i17, 14/// absolute vertical offset from the screen 15y_off: i17, 16/// relative horizontal offset, from parent window. This only accumulates if it is negative so that 17/// we can clip the window correctly 18parent_x_off: i17, 19/// relative vertical offset, from parent window. This only accumulates if it is negative so that 20/// we can clip the window correctly 21parent_y_off: i17, 22/// width of the window. This can't be larger than the terminal screen 23width: u16, 24/// height of the window. This can't be larger than the terminal screen 25height: u16, 26 27screen: *Screen, 28unicode: *const Unicode, 29 30/// Creates a new window with offset relative to parent and size clamped to the 31/// parent's size. Windows do not retain a reference to their parent and are 32/// unaware of resizes. 33fn initChild( 34 self: Window, 35 x_off: i17, 36 y_off: i17, 37 maybe_width: ?u16, 38 maybe_height: ?u16, 39) Window { 40 const max_height = @max(self.height - y_off, 0); 41 const max_width = @max(self.width - x_off, 0); 42 const width: u16 = maybe_width orelse max_width; 43 const height: u16 = maybe_height orelse max_height; 44 45 return Window{ 46 .x_off = x_off + self.x_off, 47 .y_off = y_off + self.y_off, 48 .parent_x_off = @min(self.parent_x_off + x_off, 0), 49 .parent_y_off = @min(self.parent_y_off + y_off, 0), 50 .width = @min(width, max_width), 51 .height = @min(height, max_height), 52 .screen = self.screen, 53 .unicode = self.unicode, 54 }; 55} 56 57pub const ChildOptions = struct { 58 x_off: i17 = 0, 59 y_off: i17 = 0, 60 /// the width of the resulting child, including any borders 61 width: ?u16 = null, 62 /// the height of the resulting child, including any borders 63 height: ?u16 = null, 64 border: BorderOptions = .{}, 65}; 66 67pub const BorderOptions = struct { 68 style: Cell.Style = .{}, 69 where: union(enum) { 70 none, 71 all, 72 top, 73 right, 74 bottom, 75 left, 76 other: Locations, 77 } = .none, 78 glyphs: Glyphs = .single_rounded, 79 80 pub const Locations = packed struct { 81 top: bool = false, 82 right: bool = false, 83 bottom: bool = false, 84 left: bool = false, 85 }; 86 87 pub const Glyphs = union(enum) { 88 single_rounded, 89 single_square, 90 /// custom border glyphs. each glyph should be one cell wide and the 91 /// following indices apply: 92 /// [0] = top left 93 /// [1] = horizontal 94 /// [2] = top right 95 /// [3] = vertical 96 /// [4] = bottom right 97 /// [5] = bottom left 98 custom: [6][]const u8, 99 }; 100 101 const single_rounded: [6][]const u8 = .{ "", "", "", "", "", "" }; 102 const single_square: [6][]const u8 = .{ "", "", "", "", "", "" }; 103}; 104 105/// create a child window 106pub fn child(self: Window, opts: ChildOptions) Window { 107 var result = self.initChild(opts.x_off, opts.y_off, opts.width, opts.height); 108 109 const glyphs = switch (opts.border.glyphs) { 110 .single_rounded => BorderOptions.single_rounded, 111 .single_square => BorderOptions.single_square, 112 .custom => |custom| custom, 113 }; 114 115 const top_left: Cell.Character = .{ .grapheme = glyphs[0], .width = 1 }; 116 const horizontal: Cell.Character = .{ .grapheme = glyphs[1], .width = 1 }; 117 const top_right: Cell.Character = .{ .grapheme = glyphs[2], .width = 1 }; 118 const vertical: Cell.Character = .{ .grapheme = glyphs[3], .width = 1 }; 119 const bottom_right: Cell.Character = .{ .grapheme = glyphs[4], .width = 1 }; 120 const bottom_left: Cell.Character = .{ .grapheme = glyphs[5], .width = 1 }; 121 const style = opts.border.style; 122 123 const h = result.height; 124 const w = result.width; 125 126 const loc: BorderOptions.Locations = switch (opts.border.where) { 127 .none => return result, 128 .all => .{ .top = true, .bottom = true, .right = true, .left = true }, 129 .bottom => .{ .bottom = true }, 130 .right => .{ .right = true }, 131 .left => .{ .left = true }, 132 .top => .{ .top = true }, 133 .other => |loc| loc, 134 }; 135 if (loc.top) { 136 var i: u16 = 0; 137 while (i < w) : (i += 1) { 138 result.writeCell(i, 0, .{ .char = horizontal, .style = style }); 139 } 140 } 141 if (loc.bottom) { 142 var i: u16 = 0; 143 while (i < w) : (i += 1) { 144 result.writeCell(i, h -| 1, .{ .char = horizontal, .style = style }); 145 } 146 } 147 if (loc.left) { 148 var i: u16 = 0; 149 while (i < h) : (i += 1) { 150 result.writeCell(0, i, .{ .char = vertical, .style = style }); 151 } 152 } 153 if (loc.right) { 154 var i: u16 = 0; 155 while (i < h) : (i += 1) { 156 result.writeCell(w -| 1, i, .{ .char = vertical, .style = style }); 157 } 158 } 159 // draw corners 160 if (loc.top and loc.left) 161 result.writeCell(0, 0, .{ .char = top_left, .style = style }); 162 if (loc.top and loc.right) 163 result.writeCell(w -| 1, 0, .{ .char = top_right, .style = style }); 164 if (loc.bottom and loc.left) 165 result.writeCell(0, h -| 1, .{ .char = bottom_left, .style = style }); 166 if (loc.bottom and loc.right) 167 result.writeCell(w -| 1, h -| 1, .{ .char = bottom_right, .style = style }); 168 169 const x_off: u16 = if (loc.left) 1 else 0; 170 const y_off: u16 = if (loc.top) 1 else 0; 171 const h_delt: u16 = if (loc.bottom) 1 else 0; 172 const w_delt: u16 = if (loc.right) 1 else 0; 173 const h_ch: u16 = h -| y_off -| h_delt; 174 const w_ch: u16 = w -| x_off -| w_delt; 175 return result.initChild(x_off, y_off, w_ch, h_ch); 176} 177 178/// writes a cell to the location in the window 179pub fn writeCell(self: Window, col: u16, row: u16, cell: Cell) void { 180 if (self.height <= row or 181 self.width <= col or 182 self.x_off + col < 0 or 183 self.y_off + row < 0 or 184 self.parent_x_off + col < 0 or 185 self.parent_y_off + row < 0) 186 return; 187 188 self.screen.writeCell(@intCast(col + self.x_off), @intCast(row + self.y_off), cell); 189} 190 191/// reads a cell at the location in the window 192pub fn readCell(self: Window, col: u16, row: u16) ?Cell { 193 if (self.height <= row or 194 self.width <= col or 195 self.x_off + col < 0 or 196 self.y_off + row < 0 or 197 self.parent_x_off + col < 0 or 198 self.parent_y_off + row < 0) 199 return null; 200 return self.screen.readCell(@intCast(col + self.x_off), @intCast(row + self.y_off)); 201} 202 203/// fills the window with the default cell 204pub fn clear(self: Window) void { 205 self.fill(.{ .default = true }); 206} 207 208/// returns the width of the grapheme. This depends on the terminal capabilities 209pub fn gwidth(self: Window, str: []const u8) u16 { 210 return gw.gwidth(str, self.screen.width_method, &self.unicode.width_data); 211} 212 213/// fills the window with the provided cell 214pub fn fill(self: Window, cell: Cell) void { 215 if (self.x_off + self.width < 0 or 216 self.y_off + self.height < 0 or 217 self.screen.width < self.x_off or 218 self.screen.height < self.y_off) 219 return; 220 const first_row: usize = @intCast(@max(self.y_off, 0)); 221 if (self.x_off == 0 and self.width == self.screen.width) { 222 // we have a full width window, therefore contiguous memory. 223 const start = @min(first_row * self.width, self.screen.buf.len); 224 const end = @min(start + (@as(usize, @intCast(self.height)) * self.width), self.screen.buf.len); 225 @memset(self.screen.buf[start..end], cell); 226 } else { 227 // Non-contiguous. Iterate over rows an memset 228 var row: usize = first_row; 229 const first_col: usize = @max(self.x_off, 0); 230 const last_row = @min(self.height + self.y_off, self.screen.height); 231 while (row < last_row) : (row += 1) { 232 const start = @min(first_col + (row * self.screen.width), self.screen.buf.len); 233 var end = @min(start + self.width, start + (self.screen.width - first_col)); 234 end = @min(end, self.screen.buf.len); 235 @memset(self.screen.buf[start..end], cell); 236 } 237 } 238} 239 240/// hide the cursor 241pub fn hideCursor(self: Window) void { 242 self.screen.cursor_vis = false; 243} 244 245/// show the cursor at the given coordinates, 0 indexed 246pub fn showCursor(self: Window, col: u16, row: u16) void { 247 if (self.x_off + col < 0 or 248 self.y_off + row < 0 or 249 row >= self.height or 250 col >= self.width) 251 return; 252 self.screen.cursor_vis = true; 253 self.screen.cursor_row = @intCast(row + self.y_off); 254 self.screen.cursor_col = @intCast(col + self.x_off); 255} 256 257pub fn setCursorShape(self: Window, shape: Cell.CursorShape) void { 258 self.screen.cursor_shape = shape; 259} 260 261/// Options to use when printing Segments to a window 262pub const PrintOptions = struct { 263 /// vertical offset to start printing at 264 row_offset: u16 = 0, 265 /// horizontal offset to start printing at 266 col_offset: u16 = 0, 267 268 /// wrap behavior for printing 269 wrap: enum { 270 /// wrap at grapheme boundaries 271 grapheme, 272 /// wrap at word boundaries 273 word, 274 /// stop printing after one line 275 none, 276 } = .grapheme, 277 278 /// when true, print will write to the screen for rendering. When false, 279 /// nothing is written. The return value describes the size of the wrapped 280 /// text 281 commit: bool = true, 282}; 283 284pub const PrintResult = struct { 285 col: u16, 286 row: u16, 287 overflow: bool, 288}; 289 290/// prints segments to the window. returns true if the text overflowed with the 291/// given wrap strategy and size. 292pub fn print(self: Window, segments: []const Segment, opts: PrintOptions) PrintResult { 293 var row = opts.row_offset; 294 switch (opts.wrap) { 295 .grapheme => { 296 var col: u16 = opts.col_offset; 297 const overflow: bool = blk: for (segments) |segment| { 298 var iter = self.unicode.graphemeIterator(segment.text); 299 while (iter.next()) |grapheme| { 300 if (col >= self.width) { 301 row += 1; 302 col = 0; 303 } 304 if (row >= self.height) break :blk true; 305 const s = grapheme.bytes(segment.text); 306 if (std.mem.eql(u8, s, "\n")) { 307 row +|= 1; 308 col = 0; 309 continue; 310 } 311 const w = self.gwidth(s); 312 if (w == 0) continue; 313 if (opts.commit) self.writeCell(col, row, .{ 314 .char = .{ 315 .grapheme = s, 316 .width = @intCast(w), 317 }, 318 .style = segment.style, 319 .link = segment.link, 320 .wrapped = col + w >= self.width, 321 }); 322 col += w; 323 } 324 } else false; 325 if (col >= self.width) { 326 row += 1; 327 col = 0; 328 } 329 return .{ 330 .row = row, 331 .col = col, 332 .overflow = overflow, 333 }; 334 }, 335 .word => { 336 var col: u16 = opts.col_offset; 337 var overflow: bool = false; 338 var soft_wrapped: bool = false; 339 outer: for (segments) |segment| { 340 var line_iter: LineIterator = .{ .buf = segment.text }; 341 while (line_iter.next()) |line| { 342 defer { 343 // We only set soft_wrapped to false if a segment actually contains a linebreak 344 if (line_iter.has_break) { 345 soft_wrapped = false; 346 row += 1; 347 col = 0; 348 } 349 } 350 var iter: WhitespaceTokenizer = .{ .buf = line }; 351 while (iter.next()) |token| { 352 switch (token) { 353 .whitespace => |len| { 354 if (soft_wrapped) continue; 355 for (0..len) |_| { 356 if (col >= self.width) { 357 col = 0; 358 row += 1; 359 break; 360 } 361 if (opts.commit) { 362 self.writeCell(col, row, .{ 363 .char = .{ 364 .grapheme = " ", 365 .width = 1, 366 }, 367 .style = segment.style, 368 .link = segment.link, 369 }); 370 } 371 col += 1; 372 } 373 }, 374 .word => |word| { 375 const width = self.gwidth(word); 376 if (width + col > self.width and width < self.width) { 377 row += 1; 378 col = 0; 379 } 380 381 var grapheme_iterator = self.unicode.graphemeIterator(word); 382 while (grapheme_iterator.next()) |grapheme| { 383 soft_wrapped = false; 384 if (row >= self.height) { 385 overflow = true; 386 break :outer; 387 } 388 const s = grapheme.bytes(word); 389 const w = self.gwidth(s); 390 if (opts.commit) self.writeCell(col, row, .{ 391 .char = .{ 392 .grapheme = s, 393 .width = @intCast(w), 394 }, 395 .style = segment.style, 396 .link = segment.link, 397 }); 398 col += w; 399 if (col >= self.width) { 400 row += 1; 401 col = 0; 402 soft_wrapped = true; 403 } 404 } 405 }, 406 } 407 } 408 } 409 } 410 return .{ 411 // remove last row counter 412 .row = row, 413 .col = col, 414 .overflow = overflow, 415 }; 416 }, 417 .none => { 418 var col: u16 = opts.col_offset; 419 const overflow: bool = blk: for (segments) |segment| { 420 var iter = self.unicode.graphemeIterator(segment.text); 421 while (iter.next()) |grapheme| { 422 if (col >= self.width) break :blk true; 423 const s = grapheme.bytes(segment.text); 424 if (std.mem.eql(u8, s, "\n")) break :blk true; 425 const w = self.gwidth(s); 426 if (w == 0) continue; 427 if (opts.commit) self.writeCell(col, row, .{ 428 .char = .{ 429 .grapheme = s, 430 .width = @intCast(w), 431 }, 432 .style = segment.style, 433 .link = segment.link, 434 }); 435 col +|= w; 436 } 437 } else false; 438 return .{ 439 .row = row, 440 .col = col, 441 .overflow = overflow, 442 }; 443 }, 444 } 445 return false; 446} 447 448/// print a single segment. This is just a shortcut for print(&.{segment}, opts) 449pub fn printSegment(self: Window, segment: Segment, opts: PrintOptions) PrintResult { 450 return self.print(&.{segment}, opts); 451} 452 453/// scrolls the window down one row (IE inserts a blank row at the bottom of the 454/// screen and shifts all rows up one) 455pub fn scroll(self: Window, n: u16) void { 456 if (n > self.height) return; 457 var row: u16 = @max(self.y_off, 0); 458 const first_col: u16 = @max(self.x_off, 0); 459 while (row < self.height - n) : (row += 1) { 460 const dst_start = (row * self.screen.width) + first_col; 461 const dst_end = dst_start + self.width; 462 463 const src_start = ((row + n) * self.screen.width) + first_col; 464 const src_end = src_start + self.width; 465 @memcpy(self.screen.buf[dst_start..dst_end], self.screen.buf[src_start..src_end]); 466 } 467 const last_row = self.child(.{ 468 .y_off = self.height - n, 469 }); 470 last_row.clear(); 471} 472 473/// returns the mouse event if the mouse event occurred within the window. If 474/// the mouse event occurred outside the window, null is returned 475pub fn hasMouse(win: Window, mouse: ?Mouse) ?Mouse { 476 const event = mouse orelse return null; 477 if (event.col >= win.x_off and 478 event.col < (win.x_off + win.width) and 479 event.row >= win.y_off and 480 event.row < (win.y_off + win.height)) return event else return null; 481} 482 483test "Window size set" { 484 var parent = Window{ 485 .x_off = 0, 486 .y_off = 0, 487 .parent_x_off = 0, 488 .parent_y_off = 0, 489 .width = 20, 490 .height = 20, 491 .screen = undefined, 492 .unicode = undefined, 493 }; 494 495 const ch = parent.initChild(1, 1, null, null); 496 try std.testing.expectEqual(19, ch.width); 497 try std.testing.expectEqual(19, ch.height); 498} 499 500test "Window size set too big" { 501 var parent = Window{ 502 .x_off = 0, 503 .y_off = 0, 504 .parent_x_off = 0, 505 .parent_y_off = 0, 506 .width = 20, 507 .height = 20, 508 .screen = undefined, 509 .unicode = undefined, 510 }; 511 512 const ch = parent.initChild(0, 0, 21, 21); 513 try std.testing.expectEqual(20, ch.width); 514 try std.testing.expectEqual(20, ch.height); 515} 516 517test "Window size set too big with offset" { 518 var parent = Window{ 519 .x_off = 0, 520 .y_off = 0, 521 .parent_x_off = 0, 522 .parent_y_off = 0, 523 .width = 20, 524 .height = 20, 525 .screen = undefined, 526 .unicode = undefined, 527 }; 528 529 const ch = parent.initChild(10, 10, 21, 21); 530 try std.testing.expectEqual(10, ch.width); 531 try std.testing.expectEqual(10, ch.height); 532} 533 534test "Window size nested offsets" { 535 var parent = Window{ 536 .x_off = 1, 537 .y_off = 1, 538 .parent_x_off = 0, 539 .parent_y_off = 0, 540 .width = 20, 541 .height = 20, 542 .screen = undefined, 543 .unicode = undefined, 544 }; 545 546 const ch = parent.initChild(10, 10, 21, 21); 547 try std.testing.expectEqual(11, ch.x_off); 548 try std.testing.expectEqual(11, ch.y_off); 549} 550 551test "Window offsets" { 552 var parent = Window{ 553 .x_off = 0, 554 .y_off = 0, 555 .parent_x_off = 0, 556 .parent_y_off = 0, 557 .width = 20, 558 .height = 20, 559 .screen = undefined, 560 .unicode = undefined, 561 }; 562 563 const ch = parent.initChild(10, 10, 21, 21); 564 const ch2 = ch.initChild(-4, -4, null, null); 565 // Reading ch2 at row 0 should be null 566 try std.testing.expect(ch2.readCell(0, 0) == null); 567 // Should not panic us 568 ch2.writeCell(0, 0, undefined); 569} 570 571test "print: grapheme" { 572 const alloc = std.testing.allocator_instance.allocator(); 573 const unicode = try Unicode.init(alloc); 574 defer unicode.deinit(alloc); 575 var screen: Screen = .{ .width_method = .unicode }; 576 const win: Window = .{ 577 .x_off = 0, 578 .y_off = 0, 579 .parent_x_off = 0, 580 .parent_y_off = 0, 581 .width = 4, 582 .height = 2, 583 .screen = &screen, 584 .unicode = &unicode, 585 }; 586 const opts: PrintOptions = .{ 587 .commit = false, 588 .wrap = .grapheme, 589 }; 590 591 { 592 var segments = [_]Segment{ 593 .{ .text = "a" }, 594 }; 595 const result = win.print(&segments, opts); 596 try std.testing.expectEqual(1, result.col); 597 try std.testing.expectEqual(0, result.row); 598 try std.testing.expectEqual(false, result.overflow); 599 } 600 { 601 var segments = [_]Segment{ 602 .{ .text = "abcd" }, 603 }; 604 const result = win.print(&segments, opts); 605 try std.testing.expectEqual(0, result.col); 606 try std.testing.expectEqual(1, result.row); 607 try std.testing.expectEqual(false, result.overflow); 608 } 609 { 610 var segments = [_]Segment{ 611 .{ .text = "abcde" }, 612 }; 613 const result = win.print(&segments, opts); 614 try std.testing.expectEqual(1, result.col); 615 try std.testing.expectEqual(1, result.row); 616 try std.testing.expectEqual(false, result.overflow); 617 } 618 { 619 var segments = [_]Segment{ 620 .{ .text = "abcdefgh" }, 621 }; 622 const result = win.print(&segments, opts); 623 try std.testing.expectEqual(0, result.col); 624 try std.testing.expectEqual(2, result.row); 625 try std.testing.expectEqual(false, result.overflow); 626 } 627 { 628 var segments = [_]Segment{ 629 .{ .text = "abcdefghi" }, 630 }; 631 const result = win.print(&segments, opts); 632 try std.testing.expectEqual(0, result.col); 633 try std.testing.expectEqual(2, result.row); 634 try std.testing.expectEqual(true, result.overflow); 635 } 636} 637 638test "print: word" { 639 const alloc = std.testing.allocator_instance.allocator(); 640 const unicode = try Unicode.init(alloc); 641 defer unicode.deinit(alloc); 642 var screen: Screen = .{ 643 .width_method = .unicode, 644 }; 645 const win: Window = .{ 646 .x_off = 0, 647 .y_off = 0, 648 .parent_x_off = 0, 649 .parent_y_off = 0, 650 .width = 4, 651 .height = 2, 652 .screen = &screen, 653 .unicode = &unicode, 654 }; 655 const opts: PrintOptions = .{ 656 .commit = false, 657 .wrap = .word, 658 }; 659 660 { 661 var segments = [_]Segment{ 662 .{ .text = "a" }, 663 }; 664 const result = win.print(&segments, opts); 665 try std.testing.expectEqual(1, result.col); 666 try std.testing.expectEqual(0, result.row); 667 try std.testing.expectEqual(false, result.overflow); 668 } 669 { 670 var segments = [_]Segment{ 671 .{ .text = " " }, 672 }; 673 const result = win.print(&segments, opts); 674 try std.testing.expectEqual(1, result.col); 675 try std.testing.expectEqual(0, result.row); 676 try std.testing.expectEqual(false, result.overflow); 677 } 678 { 679 var segments = [_]Segment{ 680 .{ .text = " a" }, 681 }; 682 const result = win.print(&segments, opts); 683 try std.testing.expectEqual(2, result.col); 684 try std.testing.expectEqual(0, result.row); 685 try std.testing.expectEqual(false, result.overflow); 686 } 687 { 688 var segments = [_]Segment{ 689 .{ .text = "a b" }, 690 }; 691 const result = win.print(&segments, opts); 692 try std.testing.expectEqual(3, result.col); 693 try std.testing.expectEqual(0, result.row); 694 try std.testing.expectEqual(false, result.overflow); 695 } 696 { 697 var segments = [_]Segment{ 698 .{ .text = "a b c" }, 699 }; 700 const result = win.print(&segments, opts); 701 try std.testing.expectEqual(1, result.col); 702 try std.testing.expectEqual(1, result.row); 703 try std.testing.expectEqual(false, result.overflow); 704 } 705 { 706 var segments = [_]Segment{ 707 .{ .text = "hello" }, 708 }; 709 const result = win.print(&segments, opts); 710 try std.testing.expectEqual(1, result.col); 711 try std.testing.expectEqual(1, result.row); 712 try std.testing.expectEqual(false, result.overflow); 713 } 714 { 715 var segments = [_]Segment{ 716 .{ .text = "hi tim" }, 717 }; 718 const result = win.print(&segments, opts); 719 try std.testing.expectEqual(3, result.col); 720 try std.testing.expectEqual(1, result.row); 721 try std.testing.expectEqual(false, result.overflow); 722 } 723 { 724 var segments = [_]Segment{ 725 .{ .text = "hello tim" }, 726 }; 727 const result = win.print(&segments, opts); 728 try std.testing.expectEqual(0, result.col); 729 try std.testing.expectEqual(2, result.row); 730 try std.testing.expectEqual(true, result.overflow); 731 } 732 { 733 var segments = [_]Segment{ 734 .{ .text = "hello ti" }, 735 }; 736 const result = win.print(&segments, opts); 737 try std.testing.expectEqual(0, result.col); 738 try std.testing.expectEqual(2, result.row); 739 try std.testing.expectEqual(false, result.overflow); 740 } 741 { 742 var segments = [_]Segment{ 743 .{ .text = "h" }, 744 .{ .text = "e" }, 745 }; 746 const result = win.print(&segments, opts); 747 try std.testing.expectEqual(2, result.col); 748 try std.testing.expectEqual(0, result.row); 749 try std.testing.expectEqual(false, result.overflow); 750 } 751 { 752 var segments = [_]Segment{ 753 .{ .text = "h" }, 754 .{ .text = "e" }, 755 .{ .text = "l" }, 756 .{ .text = "l" }, 757 .{ .text = "o" }, 758 }; 759 const result = win.print(&segments, opts); 760 try std.testing.expectEqual(1, result.col); 761 try std.testing.expectEqual(1, result.row); 762 try std.testing.expectEqual(false, result.overflow); 763 } 764 { 765 var segments = [_]Segment{ 766 .{ .text = "he\n" }, 767 }; 768 const result = win.print(&segments, opts); 769 try std.testing.expectEqual(0, result.col); 770 try std.testing.expectEqual(1, result.row); 771 try std.testing.expectEqual(false, result.overflow); 772 } 773 { 774 var segments = [_]Segment{ 775 .{ .text = "he\n\n" }, 776 }; 777 const result = win.print(&segments, opts); 778 try std.testing.expectEqual(0, result.col); 779 try std.testing.expectEqual(2, result.row); 780 try std.testing.expectEqual(false, result.overflow); 781 } 782 { 783 var segments = [_]Segment{ 784 .{ .text = "not now" }, 785 }; 786 const result = win.print(&segments, opts); 787 try std.testing.expectEqual(3, result.col); 788 try std.testing.expectEqual(1, result.row); 789 try std.testing.expectEqual(false, result.overflow); 790 } 791 { 792 var segments = [_]Segment{ 793 .{ .text = "note now" }, 794 }; 795 const result = win.print(&segments, opts); 796 try std.testing.expectEqual(3, result.col); 797 try std.testing.expectEqual(1, result.row); 798 try std.testing.expectEqual(false, result.overflow); 799 } 800 { 801 var segments = [_]Segment{ 802 .{ .text = "note" }, 803 .{ .text = " now" }, 804 }; 805 const result = win.print(&segments, opts); 806 try std.testing.expectEqual(3, result.col); 807 try std.testing.expectEqual(1, result.row); 808 try std.testing.expectEqual(false, result.overflow); 809 } 810 { 811 var segments = [_]Segment{ 812 .{ .text = "note " }, 813 .{ .text = "now" }, 814 }; 815 const result = win.print(&segments, opts); 816 try std.testing.expectEqual(3, result.col); 817 try std.testing.expectEqual(1, result.row); 818 try std.testing.expectEqual(false, result.overflow); 819 } 820} 821 822/// Iterates a slice of bytes by linebreaks. Lines are split by '\r', '\n', or '\r\n' 823const LineIterator = struct { 824 buf: []const u8, 825 index: usize = 0, 826 has_break: bool = true, 827 828 fn next(self: *LineIterator) ?[]const u8 { 829 if (self.index >= self.buf.len) return null; 830 831 const start = self.index; 832 const end = std.mem.indexOfAnyPos(u8, self.buf, self.index, "\r\n") orelse { 833 if (start == 0) self.has_break = false; 834 self.index = self.buf.len; 835 return self.buf[start..]; 836 }; 837 838 self.index = end; 839 self.consumeCR(); 840 self.consumeLF(); 841 return self.buf[start..end]; 842 } 843 844 // consumes a \n byte 845 fn consumeLF(self: *LineIterator) void { 846 if (self.index >= self.buf.len) return; 847 if (self.buf[self.index] == '\n') self.index += 1; 848 } 849 850 // consumes a \r byte 851 fn consumeCR(self: *LineIterator) void { 852 if (self.index >= self.buf.len) return; 853 if (self.buf[self.index] == '\r') self.index += 1; 854 } 855}; 856 857/// Returns tokens of text and whitespace 858const WhitespaceTokenizer = struct { 859 buf: []const u8, 860 index: usize = 0, 861 862 const Token = union(enum) { 863 // the length of whitespace. Tab = 8 864 whitespace: usize, 865 word: []const u8, 866 }; 867 868 fn next(self: *WhitespaceTokenizer) ?Token { 869 if (self.index >= self.buf.len) return null; 870 const Mode = enum { 871 whitespace, 872 word, 873 }; 874 const first = self.buf[self.index]; 875 const mode: Mode = if (first == ' ' or first == '\t') .whitespace else .word; 876 switch (mode) { 877 .whitespace => { 878 var len: usize = 0; 879 while (self.index < self.buf.len) : (self.index += 1) { 880 switch (self.buf[self.index]) { 881 ' ' => len += 1, 882 '\t' => len += 8, 883 else => break, 884 } 885 } 886 return .{ .whitespace = len }; 887 }, 888 .word => { 889 const start = self.index; 890 while (self.index < self.buf.len) : (self.index += 1) { 891 switch (self.buf[self.index]) { 892 ' ', '\t' => break, 893 else => {}, 894 } 895 } 896 return .{ .word = self.buf[start..self.index] }; 897 }, 898 } 899 } 900}; 901 902test "refAllDecls" { 903 std.testing.refAllDecls(@This()); 904}