diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..515471b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.zig text eol=lf +*.zon text eol=lf diff --git a/README.md b/README.md index e8e722c..3527e73 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ const border = vaxis.widgets.border; const log = std.log.scoped(.main); -// Our EventType. This can contain internal events as well as Vaxis events. +// This can contain internal events as well as Vaxis events. // Internal events can be posted into the same queue as vaxis events to allow // for a single event loop with exhaustive switching. Booya const Event = union(enum) { @@ -102,12 +102,12 @@ pub fn main() !void { // The main event loop. Vaxis provides a thread safe, blocking, buffered // queue which can serve as the primary event queue for an application - outer: while (true) { + while (true) { // nextEvent blocks until an event is in the queue const event = vx.nextEvent(); - log.debug("event: {}\r\n", .{event}); - // exhaustive switching ftw. Vaxis will send events if your EventType - // enum has the fields for those events (ie "key_press", "winsize") + log.debug("event: {}", .{event}); + // exhaustive switching ftw. Vaxis will send events if your Event enum + // has the fields for those events (ie "key_press", "winsize") switch (event) { .key_press => |key| { color_idx = switch (color_idx) { @@ -115,7 +115,7 @@ pub fn main() !void { else => color_idx + 1, }; if (key.matches('c', .{ .ctrl = true })) { - break :outer; + break; } else if (key.matches('l', .{ .ctrl = true })) { vx.queueRefresh(); } else { diff --git a/build.zig b/build.zig index 33f1ea1..de44bdf 100644 --- a/build.zig +++ b/build.zig @@ -3,52 +3,61 @@ const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const root_source_file = std.Build.LazyPath.relative("src/main.zig"); - const vaxis = b.addModule("vaxis", .{ .root_source_file = .{ .path = "src/main.zig" } }); - - const ziglyph = b.dependency("ziglyph", .{ + // Dependencies + const ziglyph_dep = b.dependency("ziglyph", .{ .optimize = optimize, .target = target, }); - vaxis.addImport("ziglyph", ziglyph.module("ziglyph")); - - const zigimg = b.dependency("zigimg", .{ + const zigimg_dep = b.dependency("zigimg", .{ .optimize = optimize, .target = target, }); - vaxis.addImport("zigimg", zigimg.module("zigimg")); - const exe = b.addExecutable(.{ - .name = "vaxis", - .root_source_file = .{ .path = "examples/pathological.zig" }, + // Module + const vaxis_mod = b.addModule("vaxis", .{ .root_source_file = root_source_file }); + vaxis_mod.addImport("ziglyph", ziglyph_dep.module("ziglyph")); + vaxis_mod.addImport("zigimg", zigimg_dep.module("zigimg")); + + // Examples + const example_step = b.step("example", "Run examples"); + + const example = b.addExecutable(.{ + .name = "vaxis_pathological_example", + .root_source_file = std.Build.LazyPath.relative("examples/pathological.zig"), .target = target, .optimize = optimize, }); - exe.root_module.addImport("vaxis", vaxis); + example.root_module.addImport("vaxis", vaxis_mod); - const run_cmd = b.addRunArtifact(exe); + const example_run = b.addRunArtifact(example); + example_step.dependOn(&example_run.step); + b.default_step.dependOn(example_step); - run_cmd.step.dependOn(b.getInstallStep()); + // Tests + const tests_step = b.step("test", "Run tests"); - if (b.args) |args| { - run_cmd.addArgs(args); - } - - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); - - // Creates a step for unit testing. This only builds the test executable - // but does not run it. - const lib_unit_tests = b.addTest(.{ - .root_source_file = .{ .path = "src/main.zig" }, + const tests = b.addTest(.{ + .root_source_file = root_source_file, .target = target, .optimize = optimize, }); - lib_unit_tests.root_module.addImport("ziglyph", ziglyph.module("ziglyph")); - lib_unit_tests.root_module.addImport("zigimg", zigimg.module("zigimg")); + tests.root_module.addImport("ziglyph", ziglyph_dep.module("ziglyph")); + tests.root_module.addImport("zigimg", zigimg_dep.module("zigimg")); - const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + const tests_run = b.addRunArtifact(tests); + tests_step.dependOn(&tests_run.step); + b.default_step.dependOn(tests_step); - const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&run_lib_unit_tests.step); + // Lints + const lints_step = b.step("lint", "Run lints"); + + const lints = b.addFmt(.{ + .paths = &.{ "src", "build.zig" }, + .check = true, + }); + + lints_step.dependOn(&lints.step); + b.default_step.dependOn(lints_step); } diff --git a/build.zig.zon b/build.zig.zon index 1a56789..7f420d0 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,36 +1,15 @@ .{ .name = "vaxis", - // This is a [Semantic Version](https://semver.org/). - // In a future version of Zig it will be used for package deduplication. .version = "0.1.0", - - // This field is optional. - // This is currently advisory only; Zig does not yet do anything - // with this value. - //.minimum_zig_version = "0.11.0", - + .paths = .{""}, .dependencies = .{ .ziglyph = .{ - .url = "https://codeberg.org/dude_the_builder/ziglyph/archive/ac50ab06c91d2dd632ff4573c035dafe3b374aba.tar.gz", - .hash = "1220e097fbfb3a15a6f3484cf507f1f10ab571d1bcf519c3b5447ca727782b7a5264", + .url = "https://codeberg.org/dude_the_builder/ziglyph/archive/ac50ab06c9.tar.gz", + .hash = "1220e097fbfb3a15a6f3484cf507f1f10ab571d1bcf519c3b5447ca727782b7a5264", + }, + .zigimg = .{ + .url = "https://github.com/zigimg/zigimg/archive/2224f91.tar.gz", + .hash = "12207067e4892c48369415268648380859baa89c324748ae5bfda414a12868c9fc8b", }, - .zigimg = .{ - .url = "https://github.com/zigimg/zigimg/archive/f6998808f283f8d3c2ef34e8b4af423bc1786f32.tar.gz", - .hash = "12202ee5d22ade0c300e9e7eae4c1951bda3d5f236fe1a139eb3613b43e2f12a88db", - } - }, - - .paths = .{ - // This makes *all* files, recursively, included in this package. It is generally - // better to explicitly list the files and directories instead, to insure that - // fetching from tarballs, file system paths, and version control all result - // in the same contents hash. - "", - // For example... - //"build.zig", - //"build.zig.zon", - //"src", - //"LICENSE", - //"README.md", }, } diff --git a/examples/image.zig b/examples/image.zig index 8a0ae15..3a4abfd 100644 --- a/examples/image.zig +++ b/examples/image.zig @@ -56,7 +56,7 @@ pub fn main() !void { const img = imgs[n]; const dims = try img.cellSize(win); - const center = vaxis.alignment.center(win, dims.cols, dims.rows); + const center = vaxis.widgets.alignment.center(win, dims.cols, dims.rows); const scale = false; const z_index = 0; img.draw(center, scale, z_index); diff --git a/examples/main.zig b/examples/main.zig index 727a6d3..d6285e6 100644 --- a/examples/main.zig +++ b/examples/main.zig @@ -32,11 +32,11 @@ pub fn main() !void { // The main event loop. Vaxis provides a thread safe, blocking, buffered // queue which can serve as the primary event queue for an application - outer: while (true) { + while (true) { // nextEvent blocks until an event is in the queue const event = vx.nextEvent(); - log.debug("event: {}\r\n", .{event}); - // exhaustive switching ftw. Vaxis will send events if your EventType + log.debug("event: {}", .{event}); + // exhaustive switching ftw. Vaxis will send events if your Event // enum has the fields for those events (ie "key_press", "winsize") switch (event) { .key_press => |key| { @@ -45,7 +45,7 @@ pub fn main() !void { else => color_idx + 1, }; if (key.codepoint == 'c' and key.mods.ctrl) { - break :outer; + break; } }, .winsize => |ws| { @@ -87,7 +87,7 @@ pub fn main() !void { } } -// Our EventType. This can contain internal events as well as Vaxis events. +// Our Event. This can contain internal events as well as Vaxis events. // Internal events can be posted into the same queue as vaxis events to allow // for a single event loop with exhaustive switching. Booya const Event = union(enum) { diff --git a/examples/pathological.zig b/examples/pathological.zig index 3b1a073..f4354f0 100644 --- a/examples/pathological.zig +++ b/examples/pathological.zig @@ -19,12 +19,12 @@ pub fn main() !void { try vx.enterAltScreen(); try vx.queryTerminal(); - outer: while (true) { + while (true) { const event = vx.nextEvent(); switch (event) { .winsize => |ws| { try vx.resize(alloc, ws); - break :outer; + break; }, } } diff --git a/examples/text_input.zig b/examples/text_input.zig index ce79d44..0f350ee 100644 --- a/examples/text_input.zig +++ b/examples/text_input.zig @@ -6,7 +6,7 @@ const border = vaxis.widgets.border; const log = std.log.scoped(.main); -// Our EventType. This can contain internal events as well as Vaxis events. +// Our Event. This can contain internal events as well as Vaxis events. // Internal events can be posted into the same queue as vaxis events to allow // for a single event loop with exhaustive switching. Booya const Event = union(enum) { @@ -59,11 +59,11 @@ pub fn main() !void { // The main event loop. Vaxis provides a thread safe, blocking, buffered // queue which can serve as the primary event queue for an application - outer: while (true) { + while (true) { // nextEvent blocks until an event is in the queue const event = vx.nextEvent(); - log.debug("event: {}\r\n", .{event}); - // exhaustive switching ftw. Vaxis will send events if your EventType + log.debug("event: {}", .{event}); + // exhaustive switching ftw. Vaxis will send events if your Event // enum has the fields for those events (ie "key_press", "winsize") switch (event) { .key_press => |key| { @@ -72,7 +72,7 @@ pub fn main() !void { else => color_idx + 1, }; if (key.matches('c', .{ .ctrl = true })) { - break :outer; + break; } else if (key.matches('l', .{ .ctrl = true })) { vx.queueRefresh(); } else if (key.matches('n', .{ .ctrl = true })) { diff --git a/src/cell.zig b/src/Cell.zig similarity index 83% rename from src/cell.zig rename to src/Cell.zig index fba30bc..5e21db7 100644 --- a/src/cell.zig +++ b/src/Cell.zig @@ -1,11 +1,9 @@ const Image = @import("Image.zig"); -pub const Cell = struct { - char: Character = .{}, - style: Style = .{}, - link: Hyperlink = .{}, - image: ?Image.Placement = null, -}; +char: Character = .{}, +style: Style = .{}, +link: Hyperlink = .{}, +image: ?Image.Placement = null, /// Segment is a contiguous run of text that has a constant style pub const Segment = struct { @@ -17,7 +15,7 @@ pub const Segment = struct { pub const Character = struct { grapheme: []const u8 = " ", /// width should only be provided when the application is sure the terminal - /// will meeasure the same width. This can be ensure by using the gwidth method + /// will measure the same width. This can be ensure by using the gwidth method /// included in libvaxis. If width is 0, libvaxis will measure the glyph at /// render time width: usize = 1, diff --git a/src/InternalScreen.zig b/src/InternalScreen.zig index b5948ee..194d44e 100644 --- a/src/InternalScreen.zig +++ b/src/InternalScreen.zig @@ -1,7 +1,7 @@ const std = @import("std"); const assert = std.debug.assert; -const Style = @import("cell.zig").Style; -const Cell = @import("cell.zig").Cell; +const Style = @import("Cell.zig").Style; +const Cell = @import("Cell.zig"); const Shape = @import("Mouse.zig").Shape; const log = std.log.scoped(.internal_screen); diff --git a/src/Screen.zig b/src/Screen.zig index e5f35ff..48ed02d 100644 --- a/src/Screen.zig +++ b/src/Screen.zig @@ -1,7 +1,7 @@ const std = @import("std"); const assert = std.debug.assert; -const Cell = @import("cell.zig").Cell; +const Cell = @import("Cell.zig"); const Shape = @import("Mouse.zig").Shape; const Image = @import("Image.zig"); const Winsize = @import("Tty.zig").Winsize; diff --git a/src/Tty.zig b/src/Tty.zig index bcc7ff1..59885f9 100644 --- a/src/Tty.zig +++ b/src/Tty.zig @@ -1,11 +1,8 @@ const std = @import("std"); const builtin = @import("builtin"); const os = std.os; -const vaxis = @import("main.zig"); -const Vaxis = vaxis.Vaxis; -const Event = @import("event.zig").Event; +const Vaxis = @import("vaxis.zig").Vaxis; const Parser = @import("Parser.zig"); -const Key = vaxis.Key; const GraphemeCache = @import("GraphemeCache.zig"); const log = std.log.scoped(.tty); @@ -68,8 +65,8 @@ pub fn stop(self: *Tty) void { /// read input from the tty pub fn run( self: *Tty, - comptime EventType: type, - vx: *Vaxis(EventType), + comptime Event: type, + vx: *Vaxis(Event), ) !void { // create a pipe so we can signal to exit the run loop const pipe = try os.pipe(); @@ -78,7 +75,7 @@ pub fn run( // get our initial winsize const winsize = try getWinsize(self.fd); - if (@hasField(EventType, "winsize")) { + if (@hasField(Event, "winsize")) { vx.postEvent(.{ .winsize = winsize }); } @@ -91,10 +88,10 @@ pub fn run( const WinchHandler = struct { const Self = @This(); - var vx_winch: *Vaxis(EventType) = undefined; + var vx_winch: *Vaxis(Event) = undefined; var fd: os.fd_t = undefined; - fn init(vx_arg: *Vaxis(EventType), fd_arg: os.fd_t) !void { + fn init(vx_arg: *Vaxis(Event), fd_arg: os.fd_t) !void { vx_winch = vx_arg; fd = fd_arg; var act = os.Sigaction{ @@ -114,7 +111,7 @@ pub fn run( const ws = getWinsize(fd) catch { return; }; - if (@hasField(EventType, "winsize")) { + if (@hasField(Event, "winsize")) { vx_winch.postEvent(.{ .winsize = ws }); } } @@ -156,7 +153,7 @@ pub fn run( const event = result.event orelse continue; switch (event) { .key_press => |key| { - if (@hasField(EventType, "key_press")) { + if (@hasField(Event, "key_press")) { // HACK: yuck. there has to be a better way var mut_key = key; if (key.text) |text| { @@ -166,27 +163,27 @@ pub fn run( } }, .mouse => |mouse| { - if (@hasField(EventType, "mouse")) { + if (@hasField(Event, "mouse")) { vx.postEvent(.{ .mouse = mouse }); } }, .focus_in => { - if (@hasField(EventType, "focus_in")) { + if (@hasField(Event, "focus_in")) { vx.postEvent(.focus_in); } }, .focus_out => { - if (@hasField(EventType, "focus_out")) { + if (@hasField(Event, "focus_out")) { vx.postEvent(.focus_out); } }, .paste_start => { - if (@hasField(EventType, "paste_start")) { + if (@hasField(Event, "paste_start")) { vx.postEvent(.paste_start); } }, .paste_end => { - if (@hasField(EventType, "paste_end")) { + if (@hasField(Event, "paste_end")) { vx.postEvent(.paste_end); } }, diff --git a/src/Window.zig b/src/Window.zig index c54a056..eedd3b6 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -4,8 +4,8 @@ const WordIterator = ziglyph.WordIterator; const GraphemeIterator = ziglyph.GraphemeIterator; const Screen = @import("Screen.zig"); -const Cell = @import("cell.zig").Cell; -const Segment = @import("cell.zig").Segment; +const Cell = @import("Cell.zig"); +const Segment = @import("Cell.zig").Segment; const gw = @import("gwidth.zig"); const log = std.log.scoped(.window); @@ -118,7 +118,7 @@ pub fn wrap(self: Window, segments: []Segment) !void { var word_iter = try WordIterator.init(segment.text); while (word_iter.next()) |word| { // break lines when we need - if (isLineBreak(word.bytes)) { + if (word.bytes[0] == '\r' or word.bytes[0] == '\n') { row += 1; col = 0; wrapped = false; @@ -158,18 +158,6 @@ pub fn wrap(self: Window, segments: []Segment) !void { } } -fn isLineBreak(str: []const u8) bool { - if (std.mem.eql(u8, str, "\r\n")) { - return true; - } else if (std.mem.eql(u8, str, "\r")) { - return true; - } else if (std.mem.eql(u8, str, "\n")) { - return true; - } else { - return false; - } -} - test "Window size set" { var parent = Window{ .x_off = 0, diff --git a/src/main.zig b/src/main.zig index 308fd2b..fa790d0 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,40 +1,21 @@ +const std = @import("std"); + pub const Vaxis = @import("vaxis.zig").Vaxis; pub const Options = @import("Options.zig"); -const cell = @import("cell.zig"); -pub const Cell = cell.Cell; -pub const Style = cell.Style; -pub const Segment = cell.Segment; -pub const Color = cell.Color; - pub const Key = @import("Key.zig"); +pub const Cell = @import("Cell.zig"); +pub const Image = @import("Image.zig"); pub const Mouse = @import("Mouse.zig"); pub const Winsize = @import("Tty.zig").Winsize; -pub const widgets = @import("widgets/main.zig"); -pub const alignment = widgets.alignment; -pub const border = widgets.border; - -pub const Image = @import("Image.zig"); +pub const widgets = @import("widgets.zig"); /// Initialize a Vaxis application. -pub fn init(comptime EventType: type, opts: Options) !Vaxis(EventType) { - return Vaxis(EventType).init(opts); +pub fn init(comptime Event: type, opts: Options) !Vaxis(Event) { + return Vaxis(Event).init(opts); } test { - _ = @import("GraphemeCache.zig"); - _ = @import("Key.zig"); - _ = @import("Mouse.zig"); - _ = @import("Options.zig"); - _ = @import("Parser.zig"); - _ = @import("Screen.zig"); - _ = @import("Tty.zig"); - _ = @import("Window.zig"); - _ = @import("cell.zig"); - _ = @import("ctlseqs.zig"); - _ = @import("event.zig"); - _ = @import("gwidth.zig"); - _ = @import("queue.zig"); - _ = @import("vaxis.zig"); + std.testing.refAllDecls(@This()); } diff --git a/src/vaxis.zig b/src/vaxis.zig index 5a39231..45d95b9 100644 --- a/src/vaxis.zig +++ b/src/vaxis.zig @@ -11,8 +11,8 @@ const Screen = @import("Screen.zig"); const InternalScreen = @import("InternalScreen.zig"); const Window = @import("Window.zig"); const Options = @import("Options.zig"); -const Style = @import("cell.zig").Style; -const Hyperlink = @import("cell.zig").Hyperlink; +const Style = @import("Cell.zig").Style; +const Hyperlink = @import("Cell.zig").Hyperlink; const gwidth = @import("gwidth.zig"); const Shape = @import("Mouse.zig").Shape; const Image = @import("Image.zig"); @@ -34,7 +34,7 @@ pub fn Vaxis(comptime T: type) type { const log = std.log.scoped(.vaxis); - pub const EventType = T; + pub const Event = T; pub const Capabilities = struct { kitty_keyboard: bool = false, @@ -83,7 +83,7 @@ pub fn Vaxis(comptime T: type) type { /// Initialize Vaxis with runtime options pub fn init(_: Options) !Self { - return Self{ + return .{ .queue = .{}, .tty = null, .screen = .{}, @@ -151,7 +151,7 @@ pub fn Vaxis(comptime T: type) type { self.queue.push(event); } - /// resize allocates a slice of cellsequal to the number of cells + /// resize allocates a slice of cells equal to the number of cells /// required to display the screen (ie width x height). Any previous screen is /// freed when resizing pub fn resize(self: *Self, alloc: std.mem.Allocator, winsize: Winsize) !void { @@ -169,7 +169,7 @@ pub fn Vaxis(comptime T: type) type { /// returns a Window comprising of the entire terminal screen pub fn window(self: *Self) Window { - return Window{ + return .{ .x_off = 0, .y_off = 0, .width = self.screen.width, @@ -207,7 +207,7 @@ pub fn Vaxis(comptime T: type) type { if (std.mem.eql(u8, colorterm, "truecolor") or std.mem.eql(u8, colorterm, "24bit")) { - if (@hasField(EventType, "cap_rgb")) { + if (@hasField(Event, "cap_rgb")) { self.postEvent(.cap_rgb); } } @@ -348,7 +348,7 @@ pub fn Vaxis(comptime T: type) type { } } - // something is different, so let's loop throuugh everything and + // something is different, so let's loop through everything and // find out what // foreground @@ -642,7 +642,7 @@ pub fn Vaxis(comptime T: type) type { } } try tty.buffered_writer.flush(); - return Image{ + return .{ .id = id, .width = img.width, .height = img.height, diff --git a/src/widgets.zig b/src/widgets.zig new file mode 100644 index 0000000..0e18c98 --- /dev/null +++ b/src/widgets.zig @@ -0,0 +1,3 @@ +pub const border = @import("widgets/border.zig"); +pub const alignment = @import("widgets/alignment.zig"); +pub const TextInput = @import("widgets/TextInput.zig"); diff --git a/src/widgets/TextInput.zig b/src/widgets/TextInput.zig index c2b7077..1460b8c 100644 --- a/src/widgets/TextInput.zig +++ b/src/widgets/TextInput.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const Cell = @import("../cell.zig").Cell; const Key = @import("../Key.zig"); +const Cell = @import("../Cell.zig"); const Window = @import("../Window.zig"); const GraphemeIterator = @import("ziglyph").GraphemeIterator; diff --git a/src/widgets/align.zig b/src/widgets/alignment.zig similarity index 100% rename from src/widgets/align.zig rename to src/widgets/alignment.zig diff --git a/src/widgets/border.zig b/src/widgets/border.zig index dc1e73a..e652c30 100644 --- a/src/widgets/border.zig +++ b/src/widgets/border.zig @@ -1,7 +1,8 @@ +const Cell = @import("../Cell.zig"); const Window = @import("../Window.zig"); -const cell = @import("../cell.zig"); -const Character = cell.Character; -const Style = cell.Style; + +const Style = Cell.Style; +const Character = Cell.Character; const horizontal = Character{ .grapheme = "─", .width = 1 }; const vertical = Character{ .grapheme = "│", .width = 1 }; diff --git a/src/widgets/main.zig b/src/widgets/main.zig deleted file mode 100644 index 714b3bd..0000000 --- a/src/widgets/main.zig +++ /dev/null @@ -1,3 +0,0 @@ -pub const TextInput = @import("TextInput.zig"); -pub const border = @import("border.zig"); -pub const alignment = @import("align.zig");