this repo has no description
13
fork

Configure Feed

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

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