refactor: zigify imports and correct minor mistakes

This commit is contained in:
2025-05-20 18:23:44 +02:00
parent 50adf32f14
commit aa4adf20f9
26 changed files with 311 additions and 330 deletions

View File

@@ -1,5 +1,3 @@
const std = @import("std");
pub fn build(b: *std.Build) void { pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
@@ -36,7 +34,7 @@ pub fn build(b: *std.Build) void {
// library // library
const lib = b.addModule("zterm", .{ const lib = b.addModule("zterm", .{
.root_source_file = b.path("src/zterm.zig"), .root_source_file = b.path("src/root.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
@@ -184,7 +182,7 @@ pub fn build(b: *std.Build) void {
// zig build test // zig build test
const lib_unit_tests = b.addTest(.{ const lib_unit_tests = b.addTest(.{
.root_source_file = b.path("src/zterm.zig"), .root_source_file = b.path("src/root.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
@@ -196,3 +194,5 @@ pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Run unit tests"); const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step); test_step.dependOn(&run_lib_unit_tests.step);
} }
const std = @import("std");

View File

@@ -24,7 +24,7 @@
// This is a [Semantic Version](https://semver.org/). // This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication. // In a future version of Zig it will be used for package deduplication.
.version = "0.2.0", .version = "0.3.0",
// Tracks the earliest Zig version that the package considers to be a // Tracks the earliest Zig version that the package considers to be a
// supported use case. // supported use case.

View File

@@ -1,11 +1,3 @@
const std = @import("std");
const zterm = @import("zterm");
const input = zterm.input;
const App = zterm.App(union(enum) {});
const log = std.log.scoped(.default);
const QuitText = struct { const QuitText = struct {
const text = "Press ctrl+c to quit. Press ctrl+n to launch helix."; const text = "Press ctrl+c to quit. Press ctrl+n to launch helix.";
@@ -158,6 +150,7 @@ pub fn main() !void {
if (key.eql(.{ .cp = 'n', .mod = .{ .ctrl = true } })) { if (key.eql(.{ .cp = 'n', .mod = .{ .ctrl = true } })) {
try app.interrupt(); try app.interrupt();
renderer.size = .{}; // reset size, such that next resize will cause a full re-draw!
defer app.start() catch @panic("could not start app event loop"); defer app.start() catch @panic("could not start app event loop");
var child = std.process.Child.init(&.{"hx"}, allocator); var child = std.process.Child.init(&.{"hx"}, allocator);
_ = child.spawnAndWait() catch |err| app.postEvent(.{ _ = child.spawnAndWait() catch |err| app.postEvent(.{
@@ -188,10 +181,16 @@ pub fn main() !void {
else => {}, else => {},
} }
try renderer.resize(); container.resize(try renderer.resize());
container.resize(renderer.size);
container.reposition(.{}); container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }
} }
const log = std.log.scoped(.default);
const std = @import("std");
const zterm = @import("zterm");
const input = zterm.input;
const App = zterm.App(union(enum) {});

View File

@@ -1,12 +1,3 @@
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {
click: [:0]const u8,
});
const log = std.log.scoped(.default);
const QuitText = struct { const QuitText = struct {
const text = "Press ctrl+c to quit."; const text = "Press ctrl+c to quit.";
@@ -139,10 +130,17 @@ pub fn main() !void {
else => {}, else => {},
} }
try renderer.resize(); container.resize(try renderer.resize());
container.resize(renderer.size);
container.reposition(.{}); container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }
} }
const log = std.log.scoped(.default);
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {
click: [:0]const u8,
});

View File

@@ -1,12 +1,3 @@
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {
accept: []u21,
});
const log = std.log.scoped(.default);
const QuitText = struct { const QuitText = struct {
const text = "Press ctrl+c to quit."; const text = "Press ctrl+c to quit.";
@@ -219,10 +210,17 @@ pub fn main() !void {
else => {}, else => {},
} }
try renderer.resize(); container.resize(try renderer.resize());
container.resize(renderer.size);
container.reposition(.{}); container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }
} }
const log = std.log.scoped(.default);
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {
accept: []u21,
});

View File

@@ -1,11 +1,3 @@
const std = @import("std");
const zterm = @import("zterm");
const input = zterm.input;
const App = zterm.App(union(enum) {});
const log = std.log.scoped(.default);
const QuitText = struct { const QuitText = struct {
const text = "Press ctrl+c to quit."; const text = "Press ctrl+c to quit.";
@@ -190,10 +182,16 @@ pub fn main() !void {
else => {}, else => {},
} }
try renderer.resize(); container.resize(try renderer.resize());
container.resize(renderer.size);
container.reposition(.{}); container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }
} }
const log = std.log.scoped(.default);
const std = @import("std");
const zterm = @import("zterm");
const input = zterm.input;
const App = zterm.App(union(enum) {});

View File

@@ -1,10 +1,3 @@
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {});
const log = std.log.scoped(.default);
const QuitText = struct { const QuitText = struct {
const text = "Press ctrl+c to quit."; const text = "Press ctrl+c to quit.";
@@ -150,10 +143,15 @@ pub fn main() !void {
else => {}, else => {},
} }
try renderer.resize(); container.resize(try renderer.resize());
container.resize(renderer.size);
container.reposition(.{}); container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }
} }
const log = std.log.scoped(.default);
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {});

View File

@@ -1,10 +1,3 @@
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {});
const log = std.log.scoped(.default);
const QuitText = struct { const QuitText = struct {
const text = "Press ctrl+c to quit."; const text = "Press ctrl+c to quit.";
@@ -106,10 +99,15 @@ pub fn main() !void {
else => {}, else => {},
} }
try renderer.resize(); container.resize(try renderer.resize());
container.resize(renderer.size);
container.reposition(.{}); container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }
} }
const log = std.log.scoped(.default);
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {});

View File

@@ -1,10 +1,3 @@
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {});
const log = std.log.scoped(.default);
const QuitText = struct { const QuitText = struct {
const text = "Press ctrl+c to quit."; const text = "Press ctrl+c to quit.";
@@ -98,10 +91,15 @@ pub fn main() !void {
else => {}, else => {},
} }
try renderer.resize(); container.resize(try renderer.resize());
container.resize(renderer.size);
container.reposition(.{}); container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }
} }
const log = std.log.scoped(.default);
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {});

View File

@@ -1,10 +1,3 @@
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {});
const log = std.log.scoped(.default);
const QuitText = struct { const QuitText = struct {
const text = "Press ctrl+c to quit."; const text = "Press ctrl+c to quit.";
@@ -114,10 +107,15 @@ pub fn main() !void {
else => {}, else => {},
} }
try renderer.resize(); container.resize(try renderer.resize());
container.resize(renderer.size);
container.reposition(.{}); container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }
} }
const log = std.log.scoped(.default);
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {});

View File

@@ -1,10 +1,3 @@
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {});
const log = std.log.scoped(.default);
const QuitText = struct { const QuitText = struct {
const text = "Press ctrl+c to quit."; const text = "Press ctrl+c to quit.";
@@ -97,10 +90,15 @@ pub fn main() !void {
else => {}, else => {},
} }
try renderer.resize(); container.resize(try renderer.resize());
container.resize(renderer.size);
container.reposition(.{}); container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }
} }
const log = std.log.scoped(.default);
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {});

View File

@@ -1,10 +1,3 @@
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {});
const log = std.log.scoped(.default);
const QuitText = struct { const QuitText = struct {
const text = "Press ctrl+c to quit."; const text = "Press ctrl+c to quit.";
@@ -93,10 +86,15 @@ pub fn main() !void {
else => {}, else => {},
} }
try renderer.resize(); container.resize(try renderer.resize());
container.resize(renderer.size);
container.reposition(.{}); container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }
} }
const log = std.log.scoped(.default);
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {});

View File

@@ -1,10 +1,3 @@
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {});
const log = std.log.scoped(.default);
const QuitText = struct { const QuitText = struct {
const text = "Press ctrl+c to quit."; const text = "Press ctrl+c to quit.";
@@ -148,10 +141,15 @@ pub fn main() !void {
else => {}, else => {},
} }
try renderer.resize(); container.resize(try renderer.resize());
container.resize(renderer.size);
container.reposition(.{}); container.reposition(.{});
try renderer.render(@TypeOf(container), &container); try renderer.render(@TypeOf(container), &container);
try renderer.flush(); try renderer.flush();
} }
} }
const log = std.log.scoped(.default);
const std = @import("std");
const zterm = @import("zterm");
const App = zterm.App(union(enum) {});

View File

@@ -1,19 +1,4 @@
//! Application type for TUI-applications //! Application type for TUI-applications
const std = @import("std");
const code_point = @import("code_point");
const event = @import("event.zig");
const input = @import("input.zig");
const terminal = @import("terminal.zig");
const queue = @import("queue.zig");
const mergeTaggedUnions = event.mergeTaggedUnions;
const isTaggedUnion = event.isTaggedUnion;
const Mouse = input.Mouse;
const Key = input.Key;
const Point = @import("point.zig").Point;
const log = std.log.scoped(.app);
/// Create the App Type with the associated user events _E_ which describes /// Create the App Type with the associated user events _E_ which describes
/// an tagged union for all the user events that can be send through the /// an tagged union for all the user events that can be send through the
@@ -39,18 +24,10 @@ pub fn App(comptime E: type) type {
@compileError("Provided user event `E` for `App(comptime E: type)` is not of type `union(enum)`."); @compileError("Provided user event `E` for `App(comptime E: type)` is not of type `union(enum)`.");
} }
return struct { return struct {
pub const Event = mergeTaggedUnions(event.SystemEvent, E);
pub const Container = @import("container.zig").Container(Event);
const element = @import("element.zig");
pub const Element = element.Element(Event);
pub const Scrollable = element.Scrollable(Event);
pub const Exec = element.Exec(Event, Queue);
pub const Queue = queue.Queue(Event, 256);
queue: Queue, queue: Queue,
thread: ?std.Thread = null, thread: ?Thread = null,
quit_event: std.Thread.ResetEvent, quit_event: Thread.ResetEvent,
termios: ?std.posix.termios = null, termios: ?posix.termios = null,
pub const SignalHandler = struct { pub const SignalHandler = struct {
context: *anyopaque, context: *anyopaque,
@@ -69,9 +46,9 @@ pub fn App(comptime E: type) type {
this.postEvent(.init); this.postEvent(.init);
this.quit_event.reset(); this.quit_event.reset();
this.thread = try std.Thread.spawn(.{}, @This().run, .{this}); this.thread = try Thread.spawn(.{}, @This().run, .{this});
var termios: std.posix.termios = undefined; var termios: posix.termios = undefined;
try terminal.enableRawMode(&termios); try terminal.enableRawMode(&termios);
if (this.termios) |_| {} else this.termios = termios; if (this.termios) |_| {} else this.termios = termios;
@@ -192,9 +169,9 @@ pub fn App(comptime E: type) type {
// CSI number ~ // CSI number ~
// CSI number ; modifier ~ // CSI number ; modifier ~
// CSI number ; modifier:event_type ; text_as_codepoint ~ // CSI number ; modifier:event_type ; text_as_codepoint ~
var field_iter = std.mem.splitScalar(u8, sequence[2 .. sequence.len - 1], ';'); var field_iter = mem.splitScalar(u8, sequence[2 .. sequence.len - 1], ';');
const number_buf = field_iter.next() orelse unreachable; // always will have one field const number_buf = field_iter.next() orelse unreachable; // always will have one field
const number = std.fmt.parseUnsigned(u16, number_buf, 10) catch break; const number = fmt.parseUnsigned(u16, number_buf, 10) catch break;
const key: Key = .{ const key: Key = .{
.cp = switch (number) { .cp = switch (number) {
@@ -231,11 +208,11 @@ pub fn App(comptime E: type) type {
std.debug.assert(sequence.len >= 4); std.debug.assert(sequence.len >= 4);
if (sequence[2] != '<') break; if (sequence[2] != '<') break;
const delim1 = std.mem.indexOfScalarPos(u8, sequence, 3, ';') orelse break; const delim1 = mem.indexOfScalarPos(u8, sequence, 3, ';') orelse break;
const button_mask = std.fmt.parseUnsigned(u16, sequence[3..delim1], 10) catch break; const button_mask = fmt.parseUnsigned(u16, sequence[3..delim1], 10) catch break;
const delim2 = std.mem.indexOfScalarPos(u8, sequence, delim1 + 1, ';') orelse break; const delim2 = mem.indexOfScalarPos(u8, sequence, delim1 + 1, ';') orelse break;
const px = std.fmt.parseUnsigned(u16, sequence[delim1 + 1 .. delim2], 10) catch break; const px = fmt.parseUnsigned(u16, sequence[delim1 + 1 .. delim2], 10) catch break;
const py = std.fmt.parseUnsigned(u16, sequence[delim2 + 1 .. sequence.len - 1], 10) catch break; const py = fmt.parseUnsigned(u16, sequence[delim2 + 1 .. sequence.len - 1], 10) catch break;
const mouse_bits = packed struct { const mouse_bits = packed struct {
const motion: u8 = 0b00100000; const motion: u8 = 0b00100000;
@@ -280,9 +257,9 @@ pub fn App(comptime E: type) type {
't' => { 't' => {
// XTWINOPS // XTWINOPS
// Split first into fields delimited by ';' // Split first into fields delimited by ';'
var iter = std.mem.splitScalar(u8, sequence[2 .. sequence.len - 1], ';'); var iter = mem.splitScalar(u8, sequence[2 .. sequence.len - 1], ';');
const ps = iter.first(); const ps = iter.first();
if (std.mem.eql(u8, "48", ps)) { if (mem.eql(u8, "48", ps)) {
// in band window resize // in band window resize
// CSI 48 ; height ; width ; height_pix ; width_pix t // CSI 48 ; height ; width ; height_pix ; width_pix t
const width_char = iter.next() orelse break; const width_char = iter.next() orelse break;
@@ -291,8 +268,8 @@ pub fn App(comptime E: type) type {
_ = width_char; _ = width_char;
_ = height_char; _ = height_char;
// this.postEvent(.{ .size = .{ // this.postEvent(.{ .size = .{
// .x = std.fmt.parseUnsigned(u16, width_char, 10) catch break, // .x = fmt.parseUnsigned(u16, width_char, 10) catch break,
// .y = std.fmt.parseUnsigned(u16, height_char, 10) catch break, // .y = fmt.parseUnsigned(u16, height_char, 10) catch break,
// } }); // } });
} }
}, },
@@ -338,5 +315,31 @@ pub fn App(comptime E: type) type {
break; break;
} }
} }
const element = @import("element.zig");
pub const Event = mergeTaggedUnions(event.SystemEvent, E);
pub const Container = @import("container.zig").Container(Event);
pub const Element = element.Element(Event);
pub const Scrollable = element.Scrollable(Event);
pub const Exec = element.Exec(Event, Queue);
pub const Queue = queue.Queue(Event, 256);
}; };
} }
const log = std.log.scoped(.app);
const std = @import("std");
const mem = std.mem;
const fmt = std.fmt;
const posix = std.posix;
const Thread = std.Thread;
const code_point = @import("code_point");
const event = @import("event.zig");
const input = @import("input.zig");
const terminal = @import("terminal.zig");
const queue = @import("queue.zig");
const mergeTaggedUnions = event.mergeTaggedUnions;
const isTaggedUnion = event.isTaggedUnion;
const Mouse = input.Mouse;
const Key = input.Key;
const Point = @import("point.zig").Point;

View File

@@ -1,11 +1,8 @@
const std = @import("std"); //! Cell type containing content and formatting for each character in the terminal screen.
const Style = @import("style.zig");
pub const Cell = @This();
style: Style = .{ .emphasis = &.{} },
// TODO embrace `zg` dependency more due to utf-8 encoding // TODO embrace `zg` dependency more due to utf-8 encoding
cp: u21 = ' ', cp: u21 = ' ',
style: Style = .{ .emphasis = &.{} },
pub fn eql(this: Cell, other: Cell) bool { pub fn eql(this: Cell, other: Cell) bool {
return this.cp == other.cp and this.style.eql(other.style); return this.cp == other.cp and this.style.eql(other.style);
@@ -20,6 +17,10 @@ pub fn value(this: Cell, writer: anytype) !void {
try this.style.value(writer, this.cp); try this.style.value(writer, this.cp);
} }
const std = @import("std");
const Style = @import("style.zig");
const Cell = @This();
test "ascii styled text" { test "ascii styled text" {
const cells: [4]Cell = .{ const cells: [4]Cell = .{
.{ .cp = 'Y', .style = .{ .fg = .green, .bg = .grey, .emphasis = &.{} } }, .{ .cp = 'Y', .style = .{ .fg = .green, .bg = .grey, .emphasis = &.{} } },

View File

@@ -1,5 +1,3 @@
const std = @import("std");
pub const Color = enum(u8) { pub const Color = enum(u8) {
default = 0, default = 0,
black = 16, black = 16,
@@ -25,16 +23,19 @@ pub const Color = enum(u8) {
pub inline fn write(this: Color, writer: anytype, comptime coloring: enum { fg, bg, ul }) !void { pub inline fn write(this: Color, writer: anytype, comptime coloring: enum { fg, bg, ul }) !void {
if (this == .default) { if (this == .default) {
switch (coloring) { switch (coloring) {
.fg => try std.fmt.format(writer, "39", .{}), .fg => try format(writer, "39", .{}),
.bg => try std.fmt.format(writer, "49", .{}), .bg => try format(writer, "49", .{}),
.ul => try std.fmt.format(writer, "59", .{}), .ul => try format(writer, "59", .{}),
} }
} else { } else {
switch (coloring) { switch (coloring) {
.fg => try std.fmt.format(writer, "38;5;{d}", .{@intFromEnum(this)}), .fg => try format(writer, "38;5;{d}", .{@intFromEnum(this)}),
.bg => try std.fmt.format(writer, "48;5;{d}", .{@intFromEnum(this)}), .bg => try format(writer, "48;5;{d}", .{@intFromEnum(this)}),
.ul => try std.fmt.format(writer, "58;5;{d}", .{@intFromEnum(this)}), .ul => try format(writer, "58;5;{d}", .{@intFromEnum(this)}),
} }
} }
} }
}; };
const std = @import("std");
const format = std.fmt.format;

View File

@@ -1,22 +1,5 @@
const std = @import("std");
const input = @import("input.zig");
const isTaggedUnion = @import("event.zig").isTaggedUnion;
const Cell = @import("cell.zig");
const Color = @import("color.zig").Color;
const Point = @import("point.zig").Point;
const Style = @import("style.zig");
const Error = @import("error.zig").Error;
const log = std.log.scoped(.container);
/// Border configuration struct /// Border configuration struct
pub const Border = packed struct { pub const Border = packed struct {
// corners:
const rounded_border: [6]u21 = .{ '╭', '─', '╮', '│', '╰', '╯' };
const squared_border: [6]u21 = .{ '┌', '─', '┐', '│', '└', '┘' };
/// Color to use for the border /// Color to use for the border
color: Color = .default, color: Color = .default,
/// Configure the corner type to be used for the border /// Configure the corner type to be used for the border
@@ -40,13 +23,12 @@ pub const Border = packed struct {
} = .{}, } = .{},
pub fn content(this: @This(), cells: []Cell, size: Point) void { pub fn content(this: @This(), cells: []Cell, size: Point) void {
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
const frame = switch (this.corners) { const frame: [6]u21 = switch (this.corners) {
.rounded => Border.rounded_border, .rounded => .{ '╭', '─', '╮', '│', '╰', '╯' },
.squared => Border.squared_border, .squared => .{ '┌', '─', '┐', '│', '└', '┘' },
}; };
std.debug.assert(frame.len == 6);
// render top and bottom border // render top and bottom border
if (this.sides.top or this.sides.bottom) { if (this.sides.top or this.sides.bottom) {
@@ -156,7 +138,7 @@ pub const Rectangle = packed struct {
// NOTE caller owns `Cells` slice and ensures that `cells.len == size.x * size.y` // NOTE caller owns `Cells` slice and ensures that `cells.len == size.x * size.y`
pub fn content(this: @This(), cells: []Cell, size: Point) void { pub fn content(this: @This(), cells: []Cell, size: Point) void {
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
for (0..size.y) |row| { for (0..size.y) |row| {
for (0..size.x) |col| { for (0..size.x) |col| {
@@ -300,11 +282,6 @@ pub const Rectangle = packed struct {
/// Layout configuration struct /// Layout configuration struct
pub const Layout = packed struct { pub const Layout = packed struct {
// separator.line:
const line: [2]u21 = .{ '│', '─' };
const dotted: [2]u21 = .{ '┆', '┄' };
const double: [2]u21 = .{ '║', '═' };
/// control the direction in which child elements are laid out /// control the direction in which child elements are laid out
direction: enum(u1) { horizontal, vertical } = .horizontal, direction: enum(u1) { horizontal, vertical } = .horizontal,
/// Padding outside of the child elements /// Padding outside of the child elements
@@ -343,13 +320,13 @@ pub const Layout = packed struct {
} = .{}, } = .{},
pub fn content(this: @This(), comptime C: type, cells: []Cell, origin: Point, size: Point, children: []const C) void { pub fn content(this: @This(), comptime C: type, cells: []Cell, origin: Point, size: Point, children: []const C) void {
std.debug.assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
if (this.separator.enabled and children.len > 1) { if (this.separator.enabled and children.len > 1) {
const line_cps: [2]u21 = switch (this.separator.line) { const line_cps: [2]u21 = switch (this.separator.line) {
.line => line, .line => .{ '│', '─' },
.dotted => dotted, .dotted => .{ '┆', '┄' },
.double => double, .double => .{ '║', '═' },
}; };
const gap: u16 = (this.gap + 1) / 2; const gap: u16 = (this.gap + 1) / 2;
@@ -574,7 +551,7 @@ pub fn Container(comptime Event: type) type {
const Element = @import("element.zig").Element(Event); const Element = @import("element.zig").Element(Event);
return struct { return struct {
allocator: std.mem.Allocator, allocator: Allocator,
origin: Point, origin: Point,
size: Point, size: Point,
properties: Properties, properties: Properties,
@@ -592,7 +569,7 @@ pub fn Container(comptime Event: type) type {
}; };
pub fn init( pub fn init(
allocator: std.mem.Allocator, allocator: Allocator,
properties: Properties, properties: Properties,
element: Element, element: Element,
) !@This() { ) !@This() {
@@ -872,7 +849,7 @@ pub fn Container(comptime Event: type) type {
switch (event) { switch (event) {
.mouse => |mouse| if (mouse.in(this.origin, this.size)) { .mouse => |mouse| if (mouse.in(this.origin, this.size)) {
// the element receives the mouse event with relative position // the element receives the mouse event with relative position
std.debug.assert(mouse.x >= this.origin.x and mouse.y >= this.origin.y); assert(mouse.x >= this.origin.x and mouse.y >= this.origin.y);
var relative_mouse: input.Mouse = mouse; var relative_mouse: input.Mouse = mouse;
relative_mouse.x -= this.origin.x; relative_mouse.x -= this.origin.x;
relative_mouse.y -= this.origin.y; relative_mouse.y -= this.origin.y;
@@ -907,10 +884,21 @@ pub fn Container(comptime Event: type) type {
}; };
} }
const log = std.log.scoped(.container);
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const input = @import("input.zig");
const isTaggedUnion = @import("event.zig").isTaggedUnion;
const Cell = @import("cell.zig");
const Color = @import("color.zig").Color;
const Point = @import("point.zig").Point;
const Style = @import("style.zig");
const Error = @import("error.zig").Error;
test { test {
_ = Border; @import("std").testing.refAllDeclsRecursive(@This());
_ = Layout;
_ = Rectangle;
} }
test "Container Fixed and Grow Size Vertical" { test "Container Fixed and Grow Size Vertical" {

View File

@@ -1,11 +1,4 @@
//! Interface for Element's which describe the contents of a `Container`. //! Interface for Element's which describe the contents of a `Container`.
const std = @import("std");
const input = @import("input.zig");
const Container = @import("container.zig").Container;
const Cell = @import("cell.zig");
const Mouse = input.Mouse;
const Point = @import("point.zig").Point;
pub fn Element(Event: type) type { pub fn Element(Event: type) type {
return struct { return struct {
@@ -188,8 +181,14 @@ pub fn Scrollable(Event: type) type {
}; };
} }
const std = @import("std");
const input = @import("input.zig");
const Container = @import("container.zig").Container;
const Cell = @import("cell.zig");
const Mouse = input.Mouse;
const Point = @import("point.zig").Point;
// TODO nested scrollable `Container`s?' // TODO nested scrollable `Container`s?'
// TODO reaction only for when the event is actually pushed to the corresponding `Container` rendered container
test "scrollable vertical" { test "scrollable vertical" {
const event = @import("event.zig"); const event = @import("event.zig");

View File

@@ -1,12 +1,5 @@
//! Events which are defined by the library. They might be extended by user //! Events which are defined by the library. They might be extended by user
//! events. See `App` for more details about user defined events. //! events. See `App` for more details about user defined events.
const std = @import("std");
const input = @import("input.zig");
const terminal = @import("terminal.zig");
const Key = input.Key;
const Mouse = input.Mouse;
const Point = @import("point.zig").Point;
/// System events available to every `zterm.App` /// System events available to every `zterm.App`
pub const SystemEvent = union(enum) { pub const SystemEvent = union(enum) {
@@ -17,6 +10,7 @@ pub const SystemEvent = union(enum) {
quit, quit,
/// Error event to notify other containers about a recoverable error /// Error event to notify other containers about a recoverable error
err: struct { err: struct {
/// actual error
err: anyerror, err: anyerror,
/// associated error message /// associated error message
msg: []const u8, msg: []const u8,
@@ -92,3 +86,10 @@ pub fn isTaggedUnion(comptime E: type) bool {
} }
return true; return true;
} }
const std = @import("std");
const input = @import("input.zig");
const terminal = @import("terminal.zig");
const Key = input.Key;
const Mouse = input.Mouse;
const Point = @import("point.zig").Point;

View File

@@ -1,7 +1,4 @@
//! Input module for `zterm`. Contains structs to represent key events and mouse events. //! Input module for `zterm`. Contains structs to represent key events and mouse events.
const std = @import("std");
const Point = @import("point.zig").Point;
pub const Mouse = packed struct { pub const Mouse = packed struct {
x: u16, x: u16,
@@ -32,7 +29,7 @@ pub const Mouse = packed struct {
}; };
pub fn eql(this: @This(), other: @This()) bool { pub fn eql(this: @This(), other: @This()) bool {
return std.meta.eql(this, other); return meta.eql(this, other);
} }
pub fn in(this: @This(), origin: Point, size: Point) bool { pub fn in(this: @This(), origin: Point, size: Point) bool {
@@ -65,7 +62,7 @@ pub const Key = packed struct {
/// } /// }
/// ``` /// ```
pub fn eql(this: @This(), other: @This()) bool { pub fn eql(this: @This(), other: @This()) bool {
return std.meta.eql(this, other); return meta.eql(this, other);
} }
// TODO might be useful to use the std.ascii stuff! // TODO might be useful to use the std.ascii stuff!
@@ -92,24 +89,25 @@ pub const Key = packed struct {
} }
test "isAscii with ascii character" { test "isAscii with ascii character" {
try std.testing.expectEqual(true, isAscii(.{ .cp = 'c' })); try testing.expectEqual(true, isAscii(.{ .cp = 'c' }));
try std.testing.expectEqual(false, isAscii(.{ .cp = 'c', .mod = .{ .ctrl = true } })); try testing.expectEqual(false, isAscii(.{ .cp = 'c', .mod = .{ .ctrl = true } }));
try std.testing.expectEqual(false, isAscii(.{ .cp = 'c', .mod = .{ .alt = true } })); try testing.expectEqual(false, isAscii(.{ .cp = 'c', .mod = .{ .alt = true } }));
try std.testing.expectEqual(false, isAscii(.{ .cp = 'c', .mod = .{ .alt = true, .ctrl = true } })); try testing.expectEqual(false, isAscii(.{ .cp = 'c', .mod = .{ .alt = true, .ctrl = true } }));
} }
test "isAscii with non-ascii character" { test "isAscii with non-ascii character" {
try std.testing.expectEqual(false, isAscii(.{ .cp = Escape })); try testing.expectEqual(false, isAscii(.{ .cp = Escape }));
try std.testing.expectEqual(false, isAscii(.{ .cp = Enter })); try testing.expectEqual(false, isAscii(.{ .cp = Enter }));
try std.testing.expectEqual(false, isAscii(.{ .cp = Enter, .mod = .{ .alt = true } })); try testing.expectEqual(false, isAscii(.{ .cp = Enter, .mod = .{ .alt = true } }));
} }
test "isAscii with excluded input.Delete" { test "isAscii with excluded input.Delete" {
try std.testing.expectEqual(false, isAscii(.{ .cp = Delete })); try testing.expectEqual(false, isAscii(.{ .cp = Delete }));
try std.testing.expectEqual(false, isAscii(.{ .cp = Delete, .mod = .{ .alt = false, .ctrl = false } })); try testing.expectEqual(false, isAscii(.{ .cp = Delete, .mod = .{ .alt = false, .ctrl = false } }));
} }
}; };
// TODO: std.ascii has the escape codes too!
// codepoints for keys // codepoints for keys
pub const Tab: u21 = 0x09; pub const Tab: u21 = 0x09;
pub const Enter: u21 = 0x0D; pub const Enter: u21 = 0x0D;
@@ -225,3 +223,8 @@ pub const RightHyper: u21 = 57451;
pub const RightMeta: u21 = 57452; pub const RightMeta: u21 = 57452;
pub const IsoLevel3Shift: u21 = 57453; pub const IsoLevel3Shift: u21 = 57453;
pub const IsoLevel5Shift: u21 = 57454; pub const IsoLevel5Shift: u21 = 57454;
const std = @import("std");
const meta = std.meta;
const Point = @import("point.zig").Point;
const testing = std.testing;

View File

@@ -1,9 +1,7 @@
// taken from https://github.com/rockorager/libvaxis/blob/main/src/queue.zig (MIT-License) // taken from https://github.com/rockorager/libvaxis/blob/main/src/queue.zig (MIT-License)
// with slight modifications // with slight modifications
const std = @import("std");
const assert = std.debug.assert;
/// Thread safe. Fixed size. Blocking push and pop. /// Queue implementation. Thread safe. Fixed size. Blocking push and pop. Polling through tryPop and tryPush.
pub fn Queue(comptime T: type, comptime size: usize) type { pub fn Queue(comptime T: type, comptime size: usize) type {
return struct { return struct {
buf: [size]T = undefined, buf: [size]T = undefined,
@@ -135,8 +133,12 @@ pub fn Queue(comptime T: type, comptime size: usize) type {
}; };
} }
const std = @import("std");
const testing = std.testing; const testing = std.testing;
const assert = std.debug.assert;
const Thread = std.Thread;
const cfg = Thread.SpawnConfig{ .allocator = testing.allocator }; const cfg = Thread.SpawnConfig{ .allocator = testing.allocator };
test "Queue: simple push / pop" { test "Queue: simple push / pop" {
var queue: Queue(u8, 16) = .{}; var queue: Queue(u8, 16) = .{};
queue.push(1); queue.push(1);
@@ -146,7 +148,6 @@ test "Queue: simple push / pop" {
try testing.expectEqual(2, queue.pop()); try testing.expectEqual(2, queue.pop());
} }
const Thread = std.Thread;
fn testPushPop(q: *Queue(u8, 2)) !void { fn testPushPop(q: *Queue(u8, 2)) !void {
q.push(3); q.push(3);
try testing.expectEqual(2, q.pop()); try testing.expectEqual(2, q.pop());
@@ -199,7 +200,7 @@ fn sleepyPop(q: *Queue(u8, 2)) !void {
try Thread.yield(); try Thread.yield();
std.time.sleep(std.time.ns_per_s); std.time.sleep(std.time.ns_per_s);
// Finally, let that other thread go. // Finally, let that other thread go.
try std.testing.expectEqual(1, q.pop()); try testing.expectEqual(1, q.pop());
// This won't continue until the other thread has had a chance to // This won't continue until the other thread has had a chance to
// put at least one item in the queue. // put at least one item in the queue.
@@ -218,7 +219,7 @@ fn sleepyPop(q: *Queue(u8, 2)) !void {
std.time.sleep(std.time.ns_per_s / 2); std.time.sleep(std.time.ns_per_s / 2);
// Pop that thing and we're done. // Pop that thing and we're done.
try std.testing.expectEqual(2, q.pop()); try testing.expectEqual(2, q.pop());
} }
test "Fill, block, fill, block" { test "Fill, block, fill, block" {
@@ -238,15 +239,15 @@ test "Fill, block, fill, block" {
// Just to make sure the sleeps are yielding to this thread, make // Just to make sure the sleeps are yielding to this thread, make
// sure it took at least 900ms to do the push. // sure it took at least 900ms to do the push.
try std.testing.expect(then - now > 900); try testing.expect(then - now > 900);
// This should block again, waiting for the other thread. // This should block again, waiting for the other thread.
queue.push(4); queue.push(4);
// And once that push has gone through, the other thread's done. // And once that push has gone through, the other thread's done.
thread.join(); thread.join();
try std.testing.expectEqual(3, queue.pop()); try testing.expectEqual(3, queue.pop());
try std.testing.expectEqual(4, queue.pop()); try testing.expectEqual(4, queue.pop());
} }
fn sleepyPush(q: *Queue(u8, 1)) !void { fn sleepyPush(q: *Queue(u8, 1)) !void {
@@ -284,8 +285,8 @@ test "Drain, block, drain, block" {
var queue: Queue(u8, 1) = .{}; var queue: Queue(u8, 1) = .{};
const thread = try Thread.spawn(cfg, sleepyPush, .{&queue}); const thread = try Thread.spawn(cfg, sleepyPush, .{&queue});
try std.testing.expectEqual(1, queue.pop()); try testing.expectEqual(1, queue.pop());
try std.testing.expectEqual(2, queue.pop()); try testing.expectEqual(2, queue.pop());
thread.join(); thread.join();
} }

View File

@@ -1,18 +1,14 @@
const std = @import("std"); //! Renderer for `zterm`.
const terminal = @import("terminal.zig");
const Cell = @import("cell.zig");
const Point = @import("point.zig").Point;
/// Double-buffered intermediate rendering pipeline /// Double-buffered intermediate rendering pipeline
pub const Buffered = struct { pub const Buffered = struct {
allocator: std.mem.Allocator, allocator: Allocator,
created: bool, created: bool,
size: Point, size: Point,
screen: []Cell, screen: []Cell,
virtual_screen: []Cell, virtual_screen: []Cell,
pub fn init(allocator: std.mem.Allocator) @This() { pub fn init(allocator: Allocator) @This() {
return .{ return .{
.allocator = allocator, .allocator = allocator,
.created = false, .created = false,
@@ -29,9 +25,9 @@ pub const Buffered = struct {
} }
} }
pub fn resize(this: *@This()) !void { pub fn resize(this: *@This()) !Point {
const size = terminal.getTerminalSize(); const size = terminal.getTerminalSize();
if (std.meta.eql(this.size, size)) return; if (meta.eql(this.size, size)) return this.size;
this.size = size; this.size = size;
const n = @as(usize, this.size.x) * @as(usize, this.size.y); const n = @as(usize, this.size.x) * @as(usize, this.size.y);
@@ -52,6 +48,7 @@ pub const Buffered = struct {
@memset(this.virtual_screen, .{}); @memset(this.virtual_screen, .{});
} }
try this.clear(); try this.clear();
return size;
} }
/// Clear the entire screen and reset the screen buffer, to force a re-draw with the next `flush` call. /// Clear the entire screen and reset the screen buffer, to force a re-draw with the next `flush` call.
@@ -118,3 +115,10 @@ pub const Buffered = struct {
} }
} }
}; };
const std = @import("std");
const meta = std.meta;
const Allocator = std.mem.Allocator;
const terminal = @import("terminal.zig");
const Cell = @import("cell.zig");
const Point = @import("point.zig").Point;

View File

@@ -17,7 +17,6 @@ pub const Renderer = @import("render.zig");
// Container Configurations // Container Configurations
pub const Border = container.Border; pub const Border = container.Border;
pub const Rectangle = container.Rectangle; pub const Rectangle = container.Rectangle;
pub const Scroll = container.Scroll;
pub const Layout = container.Layout; pub const Layout = container.Layout;
pub const Cell = @import("cell.zig"); pub const Cell = @import("cell.zig");

View File

@@ -7,11 +7,13 @@
// taken from https://github.com/rockorager/libvaxis/blob/main/src/Cell.zig (MIT-License) // taken from https://github.com/rockorager/libvaxis/blob/main/src/Cell.zig (MIT-License)
// with slight modifications // with slight modifications
const std = @import("std");
const Color = @import("color.zig").Color; fg: Color = .default,
bg: Color = .default,
pub const Style = @This(); ul: Color = .default,
cursor: bool = false,
ul_style: Underline = .off,
emphasis: []const Emphasis,
pub const Underline = enum { pub const Underline = enum {
off, off,
@@ -34,42 +36,43 @@ pub const Emphasis = enum(u8) {
strikethrough, strikethrough,
}; };
fg: Color = .default,
bg: Color = .default,
ul: Color = .default,
cursor: bool = false,
ul_style: Underline = .off,
emphasis: []const Emphasis,
pub fn eql(this: Style, other: Style) bool { pub fn eql(this: Style, other: Style) bool {
return std.meta.eql(this, other); return meta.eql(this, other);
} }
// TODO might be useful to use the std.ascii stuff! // TODO might be useful to use the std.ascii stuff!
pub fn value(this: Style, writer: anytype, cp: u21) !void { pub fn value(this: Style, writer: anytype, cp: u21) !void {
var buffer: [4]u8 = undefined; var buffer: [4]u8 = undefined;
const bytes = try std.unicode.utf8Encode(cp, &buffer); const bytes = try unicode.utf8Encode(cp, &buffer);
std.debug.assert(bytes > 0); assert(bytes > 0);
// build ansi sequence for 256 colors ... // build ansi sequence for 256 colors ...
// foreground // foreground
try std.fmt.format(writer, "\x1b[", .{}); try format(writer, "\x1b[", .{});
try this.fg.write(writer, .fg); try this.fg.write(writer, .fg);
// background // background
try std.fmt.format(writer, ";", .{}); try format(writer, ";", .{});
try this.bg.write(writer, .bg); try this.bg.write(writer, .bg);
// underline // underline
// FIX assert that if the underline property is set that the ul style and the attribute for underlining is available // FIX assert that if the underline property is set that the ul style and the attribute for underlining is available
try std.fmt.format(writer, ";", .{}); try format(writer, ";", .{});
try this.ul.write(writer, .ul); try this.ul.write(writer, .ul);
// append styles (aka attributes like bold, italic, strikethrough, etc.) // append styles (aka attributes like bold, italic, strikethrough, etc.)
for (this.emphasis) |attribute| try std.fmt.format(writer, ";{d}", .{@intFromEnum(attribute)}); for (this.emphasis) |attribute| try format(writer, ";{d}", .{@intFromEnum(attribute)});
try std.fmt.format(writer, "m", .{}); try format(writer, "m", .{});
// content // content
try std.fmt.format(writer, "{s}", .{buffer[0..bytes]}); try format(writer, "{s}", .{buffer[0..bytes]});
try std.fmt.format(writer, "\x1b[0m", .{}); try format(writer, "\x1b[0m", .{});
} }
// TODO implement helper functions for terminal capabilities: // TODO implement helper functions for terminal capabilities:
// - links / url display (osc 8) // - links / url display (osc 8)
// - show / hide cursor? // - show / hide cursor?
const std = @import("std");
const unicode = std.unicode;
const meta = std.meta;
const assert = std.debug.assert;
const format = std.fmt.format;
const Color = @import("color.zig").Color;
const Style = @This();

View File

@@ -1,15 +1,3 @@
const std = @import("std");
const code_point = @import("code_point");
const ctlseqs = @import("ctlseqs.zig");
const input = @import("input.zig");
const Key = input.Key;
const Point = @import("point.zig").Point;
const Size = @import("point.zig").Point;
const Cell = @import("cell.zig");
const log = std.log.scoped(.terminal);
// Ref: https://vt100.net/docs/vt510-rm/DECRPM.html // Ref: https://vt100.net/docs/vt510-rm/DECRPM.html
pub const ReportMode = enum { pub const ReportMode = enum {
not_recognized, not_recognized,
@@ -21,62 +9,62 @@ pub const ReportMode = enum {
/// Gets number of rows and columns in the terminal /// Gets number of rows and columns in the terminal
pub fn getTerminalSize() Size { pub fn getTerminalSize() Size {
var ws: std.posix.winsize = undefined; var ws: posix.winsize = undefined;
_ = std.posix.system.ioctl(std.posix.STDIN_FILENO, std.posix.T.IOCGWINSZ, @intFromPtr(&ws)); _ = posix.system.ioctl(posix.STDIN_FILENO, posix.T.IOCGWINSZ, @intFromPtr(&ws));
return .{ .x = ws.col, .y = ws.row }; return .{ .x = ws.col, .y = ws.row };
} }
pub fn saveScreen() !void { pub fn saveScreen() !void {
_ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.save_screen); _ = try posix.write(posix.STDIN_FILENO, ctlseqs.save_screen);
} }
pub fn restoreScreen() !void { pub fn restoreScreen() !void {
_ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.restore_screen); _ = try posix.write(posix.STDIN_FILENO, ctlseqs.restore_screen);
} }
pub fn enterAltScreen() !void { pub fn enterAltScreen() !void {
_ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.smcup); _ = try posix.write(posix.STDIN_FILENO, ctlseqs.smcup);
} }
pub fn exitAltScreen() !void { pub fn exitAltScreen() !void {
_ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.rmcup); _ = try posix.write(posix.STDIN_FILENO, ctlseqs.rmcup);
} }
pub fn clearScreen() !void { pub fn clearScreen() !void {
_ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.clear_screen); _ = try posix.write(posix.STDIN_FILENO, ctlseqs.clear_screen);
} }
pub fn hideCursor() !void { pub fn hideCursor() !void {
_ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.hide_cursor); _ = try posix.write(posix.STDIN_FILENO, ctlseqs.hide_cursor);
} }
pub fn showCursor() !void { pub fn showCursor() !void {
_ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.show_cursor); _ = try posix.write(posix.STDIN_FILENO, ctlseqs.show_cursor);
} }
pub fn setCursorPositionHome() !void { pub fn setCursorPositionHome() !void {
_ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.home); _ = try posix.write(posix.STDIN_FILENO, ctlseqs.home);
} }
pub fn enableMouseSupport() !void { pub fn enableMouseSupport() !void {
_ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.mouse_set); _ = try posix.write(posix.STDIN_FILENO, ctlseqs.mouse_set);
} }
pub fn disableMouseSupport() !void { pub fn disableMouseSupport() !void {
_ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.mouse_reset); _ = try posix.write(posix.STDIN_FILENO, ctlseqs.mouse_reset);
} }
pub fn read(buf: []u8) !usize { pub fn read(buf: []u8) !usize {
return try std.posix.read(std.posix.STDIN_FILENO, buf); return try posix.read(posix.STDIN_FILENO, buf);
} }
pub fn write(buf: []const u8) !usize { pub fn write(buf: []const u8) !usize {
return try std.posix.write(std.posix.STDIN_FILENO, buf); return try posix.write(posix.STDIN_FILENO, buf);
} }
fn contextWrite(context: @This(), data: []const u8) anyerror!usize { fn contextWrite(context: @This(), data: []const u8) anyerror!usize {
_ = context; _ = context;
return try std.posix.write(std.posix.STDOUT_FILENO, data); return try posix.write(posix.STDOUT_FILENO, data);
} }
const Writer = std.io.Writer( const Writer = std.io.Writer(
@@ -92,18 +80,18 @@ pub fn writer() Writer {
pub fn setCursorPosition(pos: Point) !void { pub fn setCursorPosition(pos: Point) !void {
var buf: [64]u8 = undefined; var buf: [64]u8 = undefined;
const value = try std.fmt.bufPrint(&buf, "\x1b[{d};{d}H", .{ pos.y + 1, pos.x + 1 }); const value = try std.fmt.bufPrint(&buf, "\x1b[{d};{d}H", .{ pos.y + 1, pos.x + 1 });
_ = try std.posix.write(std.posix.STDIN_FILENO, value); _ = try posix.write(posix.STDIN_FILENO, value);
} }
pub fn getCursorPosition() !Size.Position { pub fn getCursorPosition() !Size.Position {
// Needs Raw mode (no wait for \n) to work properly cause // Needs Raw mode (no wait for \n) to work properly cause
// control sequence will not be written without it. // control sequence will not be written without it.
_ = try std.posix.write(std.posix.STDIN_FILENO, "\x1b[6n"); _ = try posix.write(posix.STDIN_FILENO, "\x1b[6n");
var buf: [64]u8 = undefined; var buf: [64]u8 = undefined;
// format: \x1b, "[", R1,..., Rn, ";", C1, ..., Cn, "R" // format: \x1b, "[", R1,..., Rn, ";", C1, ..., Cn, "R"
const len = try std.posix.read(std.posix.STDIN_FILENO, &buf); const len = try posix.read(posix.STDIN_FILENO, &buf);
if (!isCursorPosition(buf[0..len])) { if (!isCursorPosition(buf[0..len])) {
return error.InvalidValueReturned; return error.InvalidValueReturned;
@@ -166,8 +154,8 @@ pub fn isCursorPosition(buf: []u8) bool {
/// ///
/// `bak`: pointer to store termios struct backup before /// `bak`: pointer to store termios struct backup before
/// altering, this is used to disable raw mode. /// altering, this is used to disable raw mode.
pub fn enableRawMode(bak: *std.posix.termios) !void { pub fn enableRawMode(bak: *posix.termios) !void {
var termios = try std.posix.tcgetattr(std.posix.STDIN_FILENO); var termios = try posix.tcgetattr(posix.STDIN_FILENO);
bak.* = termios; bak.* = termios;
// termios flags used by termios(3) // termios flags used by termios(3)
@@ -192,20 +180,20 @@ pub fn enableRawMode(bak: *std.posix.termios) !void {
termios.cflag.CSIZE = .CS8; termios.cflag.CSIZE = .CS8;
termios.cflag.PARENB = false; termios.cflag.PARENB = false;
termios.cc[@intFromEnum(std.posix.V.MIN)] = 1; termios.cc[@intFromEnum(posix.V.MIN)] = 1;
termios.cc[@intFromEnum(std.posix.V.TIME)] = 0; termios.cc[@intFromEnum(posix.V.TIME)] = 0;
try std.posix.tcsetattr( try posix.tcsetattr(
std.posix.STDIN_FILENO, posix.STDIN_FILENO,
.FLUSH, .FLUSH,
termios, termios,
); );
} }
/// Reverts `enableRawMode` to restore initial functionality. /// Reverts `enableRawMode` to restore initial functionality.
pub fn disableRawMode(bak: *std.posix.termios) !void { pub fn disableRawMode(bak: *posix.termios) !void {
try std.posix.tcsetattr( try posix.tcsetattr(
std.posix.STDIN_FILENO, posix.STDIN_FILENO,
.FLUSH, .FLUSH,
bak.*, bak.*,
); );
@@ -215,13 +203,13 @@ pub fn disableRawMode(bak: *std.posix.termios) !void {
pub fn canSynchornizeOutput() !bool { pub fn canSynchornizeOutput() !bool {
// Needs Raw mode (no wait for \n) to work properly cause // Needs Raw mode (no wait for \n) to work properly cause
// control sequence will not be written without it. // control sequence will not be written without it.
_ = try std.posix.write(std.posix.STDIN_FILENO, "\x1b[?2026$p"); _ = try posix.write(posix.STDIN_FILENO, "\x1b[?2026$p");
var buf: [64]u8 = undefined; var buf: [64]u8 = undefined;
// format: \x1b, "[", "?", "2", "0", "2", "6", ";", n, "$", "y" // format: \x1b, "[", "?", "2", "0", "2", "6", ";", n, "$", "y"
const len = try std.posix.read(std.posix.STDIN_FILENO, &buf); const len = try posix.read(posix.STDIN_FILENO, &buf);
if (!std.mem.eql(u8, buf[0..len], "\x1b[?2026;") or len < 9) { if (!mem.eql(u8, buf[0..len], "\x1b[?2026;") or len < 9) {
return false; return false;
} }
@@ -238,3 +226,16 @@ fn getReportMode(ps: u8) ReportMode {
else => ReportMode.not_recognized, else => ReportMode.not_recognized,
}; };
} }
const log = std.log.scoped(.terminal);
const std = @import("std");
const mem = std.mem;
const posix = std.posix;
const code_point = @import("code_point");
const ctlseqs = @import("ctlseqs.zig");
const input = @import("input.zig");
const Key = input.Key;
const Point = @import("point.zig").Point;
const Size = @import("point.zig").Point;
const Cell = @import("cell.zig");

View File

@@ -1,23 +1,12 @@
//! Testing namespace for `zterm` to provide testing capabilities for `Containers`, `Event` handling, `App`s and `Element` implementations. //! Testing namespace for `zterm` to provide testing capabilities for `Containers`, `Event` handling, `App`s and `Element` implementations.
const std = @import("std");
const event = @import("event.zig");
const Container = @import("container.zig").Container;
const Cell = @import("cell.zig");
const DisplayWidth = @import("DisplayWidth");
const Point = @import("point.zig").Point;
// TODO how would I describe the expected screens?
// - including styling?
// - compare generated strings instead? -> how would this be generated for the user?
/// Single-buffer test rendering pipeline for testing purposes. /// Single-buffer test rendering pipeline for testing purposes.
pub const Renderer = struct { pub const Renderer = struct {
allocator: std.mem.Allocator, allocator: Allocator,
size: Point, size: Point,
screen: []Cell, screen: []Cell,
pub fn init(allocator: std.mem.Allocator, size: Point) @This() { pub fn init(allocator: Allocator, size: Point) @This() {
const screen = allocator.alloc(Cell, @as(usize, size.x) * @as(usize, size.y)) catch @panic("testing.zig: Out of memory."); const screen = allocator.alloc(Cell, @as(usize, size.x) * @as(usize, size.y)) catch @panic("testing.zig: Out of memory.");
@memset(screen, .{}); @memset(screen, .{});
@@ -116,7 +105,7 @@ pub const Renderer = struct {
/// }, &container, @import("test/container/border.all.zon")); /// }, &container, @import("test/container/border.all.zon"));
/// ``` /// ```
pub fn expectContainerScreen(size: Point, container: *Container(event.SystemEvent), expected: []const Cell) !void { pub fn expectContainerScreen(size: Point, container: *Container(event.SystemEvent), expected: []const Cell) !void {
const allocator = std.testing.allocator; const allocator = testing.allocator;
var renderer: Renderer = .init(allocator, size); var renderer: Renderer = .init(allocator, size);
defer renderer.deinit(); defer renderer.deinit();
@@ -133,10 +122,10 @@ pub fn expectContainerScreen(size: Point, container: *Container(event.SystemEven
/// the contents of a given screen from the `zterm.testing.Renderer`. See /// the contents of a given screen from the `zterm.testing.Renderer`. See
/// `zterm.testing.expectContainerScreen` for an example usage. /// `zterm.testing.expectContainerScreen` for an example usage.
pub fn expectEqualCells(origin: Point, size: Point, expected: []const Cell, actual: []const Cell) !void { pub fn expectEqualCells(origin: Point, size: Point, expected: []const Cell, actual: []const Cell) !void {
const allocator = std.testing.allocator; const allocator = testing.allocator;
try std.testing.expectEqual(expected.len, actual.len); try testing.expectEqual(expected.len, actual.len);
try std.testing.expectEqual(expected.len, @as(usize, size.y) * @as(usize, size.x)); try testing.expectEqual(expected.len, @as(usize, size.y) * @as(usize, size.x));
var expected_cps = try std.ArrayList(Cell).initCapacity(allocator, size.x); var expected_cps = try std.ArrayList(Cell).initCapacity(allocator, size.x);
defer expected_cps.deinit(); defer expected_cps.deinit();
@@ -199,3 +188,12 @@ pub fn expectEqualCells(origin: Point, size: Point, expected: []const Cell, actu
try std_writer.writeAll(output.items); try std_writer.writeAll(output.items);
return error.TestExpectEqualCells; return error.TestExpectEqualCells;
} }
const std = @import("std");
const testing = std.testing;
const Allocator = std.mem.Allocator;
const event = @import("event.zig");
const Container = @import("container.zig").Container;
const Cell = @import("cell.zig");
const DisplayWidth = @import("DisplayWidth");
const Point = @import("point.zig").Point;