this repo has no description
13
fork

Configure Feed

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

widgets(terminal): implement even more csi sequences

+184 -9
+2
examples/vt.zig
··· 65 65 while (vt.tryEvent()) |event| { 66 66 switch (event) { 67 67 .bell => {}, 68 + .title_change => {}, 68 69 .exited => return, 69 70 } 70 71 } ··· 82 83 83 84 const win = vx.window(); 84 85 win.clear(); 86 + win.hideCursor(); 85 87 const child = win.child(.{ 86 88 .x_off = 4, 87 89 .y_off = 2,
+182 -9
src/widgets/terminal/Terminal.zig
··· 13 13 const DisplayWidth = @import("DisplayWidth"); 14 14 const Key = vaxis.Key; 15 15 const Queue = vaxis.Queue(Event, 16); 16 + const code_point = @import("code_point"); 16 17 17 18 pub const Event = union(enum) { 18 19 exited, 19 20 bell, 21 + title_change, 20 22 }; 21 23 22 24 const grapheme = @import("grapheme"); ··· 68 70 mode: Mode = .{}, 69 71 70 72 tab_stops: std.ArrayList(u16), 73 + title: std.ArrayList(u8), 74 + 75 + last_printed: []const u8 = "", 71 76 72 77 event_queue: Queue = .{}, 73 78 ··· 102 107 .back_screen_alt = try Screen.init(allocator, opts.winsize.cols, opts.winsize.rows), 103 108 .unicode = unicode, 104 109 .tab_stops = tabs, 110 + .title = std.ArrayList(u8).init(allocator), 105 111 }; 106 112 } 107 113 ··· 133 139 self.back_screen_pri.deinit(self.allocator); 134 140 self.back_screen_alt.deinit(self.allocator); 135 141 self.tab_stops.deinit(); 142 + self.title.deinit(); 136 143 } 137 144 138 145 pub fn spawn(self: *Terminal) !void { ··· 196 203 } 197 204 } 198 205 199 - if (self.front_screen.cursor.visible) { 206 + if (self.mode.cursor) { 200 207 win.setCursorShape(self.front_screen.cursor.shape); 201 208 win.showCursor(self.front_screen.cursor.col, self.front_screen.cursor.row); 202 - } 209 + } else win.hideCursor(); 203 210 } 204 211 205 212 pub fn tryEvent(self: *Terminal) ?Event { ··· 256 263 .print => |str| { 257 264 var iter = grapheme.Iterator.init(str, &self.unicode.grapheme_data); 258 265 while (iter.next()) |g| { 259 - const bytes = g.bytes(str); 260 - const w = try vaxis.gwidth.gwidth(bytes, .unicode, &self.unicode.width_data); 261 - self.back_screen.print(bytes, @truncate(w)); 266 + const gr = g.bytes(str); 267 + // TODO: use actual instead of .unicode 268 + const w = try vaxis.gwidth.gwidth(gr, .unicode, &self.unicode.width_data); 269 + self.back_screen.print(gr, @truncate(w)); 262 270 } 263 271 }, 264 272 .c0 => |b| try self.handleC0(b), ··· 401 409 self.back_screen.cursor.row = self.back_screen.scrolling_region.top; 402 410 try self.back_screen.insertLine(n); 403 411 }, 404 - // 'W' => {}, // TODO: Tab control 412 + // Tab Control 413 + 'W' => { 414 + if (seq.private_marker) |pm| { 415 + if (pm != '?') continue; 416 + var iter = seq.iterator(u16); 417 + const n = iter.next() orelse continue; 418 + if (n != 5) continue; 419 + self.tab_stops.clearRetainingCapacity(); 420 + var col: u16 = 0; 421 + while (col < self.back_screen.width) : (col += 8) { 422 + try self.tab_stops.append(col); 423 + } 424 + } 425 + }, 405 426 'X' => { 406 427 self.back_screen.cursor.pending_wrap = false; 407 428 var iter = seq.iterator(u16); ··· 410 431 const end = @max( 411 432 self.back_screen.cursor.row * self.back_screen.width + self.back_screen.width, 412 433 n, 434 + 1, // In case n == 0 413 435 ); 414 436 var i: usize = start; 415 437 while (i < end) : (i += 1) { 416 438 self.back_screen.buf[i].erase(self.back_screen.cursor.style.bg); 417 439 } 418 440 }, 419 - // 'Z' => {}, // TODO: Back tab 420 - // Cursor Vertial Position Aboslute 441 + 'Z' => { 442 + var iter = seq.iterator(u16); 443 + const n = iter.next() orelse 1; 444 + self.horizontalBackTab(n); 445 + }, 446 + // Cursor Horizontal Position Relative 447 + 'a' => { 448 + var iter = seq.iterator(u16); 449 + const n = iter.next() orelse 1; 450 + self.back_screen.cursor.pending_wrap = false; 451 + const max_end = if (self.mode.origin) 452 + self.back_screen.scrolling_region.right 453 + else 454 + self.back_screen.width - 1; 455 + self.back_screen.cursor.col = @min( 456 + self.back_screen.cursor.col + max_end, 457 + self.back_screen.cursor.col + n, 458 + ); 459 + }, 460 + // Repeat Previous Character 461 + 'b' => { 462 + var iter = seq.iterator(u16); 463 + const n = iter.next() orelse 1; 464 + // TODO: maybe not .unicode 465 + const w = try vaxis.gwidth.gwidth(self.last_printed, .unicode, &self.unicode.width_data); 466 + var i: usize = 0; 467 + while (i < n) : (i += 1) { 468 + self.back_screen.print(self.last_printed, @truncate(w)); 469 + } 470 + }, 471 + // Device Attributes 472 + 'c' => { 473 + if (seq.private_marker) |pm| { 474 + switch (pm) { 475 + // Secondary 476 + '>' => try self.anyWriter().writeAll("\x1B[>1;69;0c"), 477 + '=' => try self.anyWriter().writeAll("\x1B[=0000c"), 478 + else => std.log.err("unhandled CSI: {}", .{seq}), 479 + } 480 + } else { 481 + // Primary 482 + try self.anyWriter().writeAll("\x1B[?62;22c"); 483 + } 484 + }, 485 + // Cursor Vertical Position Absolute 421 486 'd' => { 422 487 var iter = seq.iterator(u16); 423 488 const n = iter.next() orelse 1; ··· 427 492 n -| 1, 428 493 ); 429 494 }, 495 + // Cursor Horizontal Position Absolute 496 + 'e' => { 497 + var iter = seq.iterator(u16); 498 + const n = iter.next() orelse 1; 499 + self.back_screen.cursor.pending_wrap = false; 500 + self.back_screen.cursor.col = @min( 501 + self.back_screen.width -| 1, 502 + n -| 1, 503 + ); 504 + }, 505 + // Tab Clear 506 + 'g' => { 507 + var iter = seq.iterator(u16); 508 + const n = iter.next() orelse 0; 509 + switch (n) { 510 + 0 => { 511 + const current = try self.tab_stops.toOwnedSlice(); 512 + defer self.tab_stops.allocator.free(current); 513 + self.tab_stops.clearRetainingCapacity(); 514 + for (current) |stop| { 515 + if (stop == self.back_screen.cursor.col) continue; 516 + try self.tab_stops.append(stop); 517 + } 518 + }, 519 + 3 => self.tab_stops.clearAndFree(), 520 + else => std.log.err("unhandled CSI: {}", .{seq}), 521 + } 522 + }, 430 523 'h', 'l' => { 431 524 var iter = seq.iterator(u16); 432 525 const mode = iter.next() orelse continue; ··· 439 532 if (seq.intermediate == null and seq.private_marker == null) { 440 533 self.back_screen.sgr(seq); 441 534 } 535 + // TODO: private marker and intermediates 536 + }, 537 + 'n' => { 538 + var iter = seq.iterator(u16); 539 + const ps = iter.next() orelse 0; 540 + if (seq.intermediate == null and seq.private_marker == null) { 541 + switch (ps) { 542 + 5 => try self.anyWriter().writeAll("\x1b[0n"), 543 + 6 => try self.anyWriter().print("\x1b[{d};{d}R", .{ 544 + self.back_screen.cursor.row + 1, 545 + self.back_screen.cursor.col + 1, 546 + }), 547 + else => std.log.err("unhandled CSI: {}", .{seq}), 548 + } 549 + } 550 + }, 551 + 'p' => { 552 + var iter = seq.iterator(u16); 553 + const ps = iter.next() orelse 0; 554 + if (seq.intermediate) |int| { 555 + switch (int) { 556 + // report mode 557 + '$' => { 558 + switch (ps) { 559 + 2026 => try self.anyWriter().writeAll("\x1b[?2026;2$p"), 560 + else => { 561 + std.log.warn("unhandled mode: {}", .{ps}); 562 + try self.anyWriter().print("\x1b[?{d};0$p", .{ps}); 563 + }, 564 + } 565 + }, 566 + else => std.log.err("unhandled CSI: {}", .{seq}), 567 + } 568 + } 442 569 }, 443 570 'q' => { 444 571 if (seq.intermediate) |int| { ··· 451 578 else => {}, 452 579 } 453 580 } 581 + if (seq.private_marker) |pm| { 582 + switch (pm) { 583 + // XTVERSION 584 + '>' => try self.anyWriter().print( 585 + "\x1bP>|libvaxis {s}\x1B\\", 586 + .{"dev"}, 587 + ), 588 + else => std.log.err("unhandled CSI: {}", .{seq}), 589 + } 590 + } 454 591 }, 455 592 'r' => { 456 593 if (seq.intermediate) |_| { ··· 471 608 else => std.log.err("unhandled CSI: {}", .{seq}), 472 609 } 473 610 }, 474 - .osc => |osc| std.log.err("unhandled osc: {s}", .{osc}), 611 + .osc => |osc| { 612 + const semicolon = std.mem.indexOfScalar(u8, osc, ';') orelse { 613 + std.log.err("unhandled osc: {s}", .{osc}); 614 + continue; 615 + }; 616 + const ps = std.fmt.parseUnsigned(u8, osc[0..semicolon], 10) catch { 617 + std.log.err("unhandled osc: {s}", .{osc}); 618 + continue; 619 + }; 620 + switch (ps) { 621 + 0 => { 622 + self.title.clearRetainingCapacity(); 623 + try self.title.appendSlice(osc[semicolon + 1 ..]); 624 + self.event_queue.push(.title_change); 625 + }, 626 + else => std.log.err("unhandled osc: {s}", .{osc}), 627 + } 628 + }, 475 629 .apc => |apc| std.log.err("unhandled apc: {s}", .{apc}), 476 630 } 477 631 } ··· 552 706 // Move right the delta 553 707 self.back_screen.cursorRight(final - col); 554 708 } 709 + 710 + pub fn horizontalBackTab(self: *Terminal, n: usize) void { 711 + // Get the current cursor position 712 + const col = self.back_screen.cursor.col; 713 + 714 + // Find the index of the next backtab 715 + const idx = for (self.tab_stops.items, 0..) |ts, i| { 716 + if (ts <= col) continue; 717 + break i; 718 + } else self.tab_stops.items.len - 1; 719 + 720 + const final = if (self.mode.origin) 721 + @max(self.tab_stops.items[idx -| (n -| 1)], self.back_screen.scrolling_region.left) 722 + else 723 + self.tab_stops.items[idx -| (n -| 1)]; 724 + 725 + // Move left the delta 726 + self.back_screen.cursorLeft(final - col); 727 + }