diff --git a/build.zig b/build.zig index 7673f2b..f2a5902 100644 --- a/build.zig +++ b/build.zig @@ -88,4 +88,9 @@ pub fn build(b: *std.Build) void { const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_lib_unit_tests.step); test_step.dependOn(&run_exe_unit_tests.step); + + // Configure zls to run this step on save for better error checking with zls + const check_step = b.step("check", "Check compilation status"); + check_step.dependOn(&exe.step); + check_step.dependOn(&lib.step); } diff --git a/src/pane.zig b/src/pane.zig index f55cf01..4ff882e 100644 --- a/src/pane.zig +++ b/src/pane.zig @@ -106,7 +106,7 @@ pub const Pane = struct { }; pub const WriterContext = struct { pane: *Pane, term_io: *TermIO }; - pub const Writer = std.io.Writer(WriterContext, std.fs.File.WriteError, write); + pub const Writer = std.io.Writer(WriterContext, anyerror, write); pub fn ReDraw(self: *Pane, term_io: *TermIO, parentDims: dim.CalculatedDimensions) !void { if (!self.should_draw) { @@ -231,7 +231,7 @@ pub const Pane = struct { }; } - pub fn write(self: WriterContext, bytes: []const u8) std.fs.File.WriteError!usize { + pub fn write(self: WriterContext, bytes: []const u8) !usize { if (self.pane != focused_pane) { self.pane.focus(self.term_io); } diff --git a/src/term.zig b/src/term.zig index f442c4e..43e389e 100644 --- a/src/term.zig +++ b/src/term.zig @@ -4,6 +4,7 @@ const posix = std.posix; const borders = @import("borders.zig"); const dim = @import("dimensions.zig"); const color = @import("colors.zig"); +const term_test = @import("test/term.zig"); var orig_termios: posix.termios = undefined; @@ -226,8 +227,10 @@ pub const TermFormat = packed struct { }; pub const TermIO = struct { - stdin: fs.File.Reader, - stdout: std.io.BufferedWriter(4096, fs.File.Writer).Writer, + stdin: std.io.AnyReader, + stdout: std.io.BufferedWriter(4096, std.io.AnyWriter).Writer, + //stdin: std.fs.File.Reader, + //stdout: std.io.BufferedWriter(4096, std.fs.File.Writer).Writer, tty_file: fs.File, current_format: TermFormat = .{}, current_background: color.Color = color.Default, @@ -520,9 +523,9 @@ pub fn getTermSize(term_io: *const TermIO) dim.CalculatedDimensions { } pub fn getTermIO() !TermIO { - var bw = std.io.bufferedWriter(std.io.getStdOut().writer()); + var bw = std.io.bufferedWriter(std.io.getStdOut().writer().any()); return TermIO{ - .stdin = std.io.getStdIn().reader(), + .stdin = std.io.getStdIn().reader().any(), .stdout = bw.writer(), .tty_file = try fs.cwd().openFile("/dev/tty", .{ .mode = .read_write }), }; @@ -540,3 +543,24 @@ fn decipherEscapeSequence(sequence: []u8) Key { return .{ .type = KeyType.SEQUENCE, .value = @intFromEnum(SequenceKey.UNKNOWN) }; } + +test "cool test" { + term_test.term_io.print("Hello {}", .{123}); + try term_test.term_io.flush(); + try std.testing.expectEqualStrings("Hello 123", term_test.readAllTestStdout()); +} + +test "stdin" { + try term_test.writeTestStdin(&[_]u8{ 'a', '\x1b', '[', 'A' }); + + var key = term_test.term_io.getKey(false); + try std.testing.expect(key.type == .ASCII); + try std.testing.expect(key.value == 'a'); + + key = term_test.term_io.getKey(false); + try std.testing.expect(key.type == .SEQUENCE); + try std.testing.expect(@as(SequenceKey, @enumFromInt(key.value)) == SequenceKey.UP); + + key = term_test.term_io.getKey(false); + try std.testing.expect(key.type == .NO_KEY); +} diff --git a/src/test/term.zig b/src/test/term.zig new file mode 100644 index 0000000..51af852 --- /dev/null +++ b/src/test/term.zig @@ -0,0 +1,93 @@ +const std = @import("std"); +const TermIO = @import("../term.zig").TermIO; + +var StdinBuf: [4096]u8 = undefined; +var StdinStart: usize = 0; +var StdinEnd: usize = 0; + +var StdoutBuf: [4096]u8 = undefined; +var StdoutStart: usize = 0; +var StdoutEnd: usize = 0; + +var bw = std.io.bufferedWriter(TestStdout.writer().any()); +pub var term_io: TermIO = .{ + .stdin = TestStdin.reader().any(), + .stdout = bw.writer(), + .tty_file = undefined, +}; + +const TestStdin = struct { + const ReadError = error{}; + const Reader = std.io.Reader(TestStdin, ReadError, read); + + fn reader() Reader { + return .{ + .context = .{}, + }; + } + + fn read(self: TestStdin, buffer: []u8) ReadError!usize { + _ = self; + const bytesAvailable = StdinEnd - StdinStart; + if (bytesAvailable == 0) { + //std.debug.print("Buffer is empty: {} to {}\n", .{ StdinStart, StdinEnd }); + return 0; + } else if (buffer.len >= bytesAvailable) { + std.mem.copyForwards(u8, buffer, StdinBuf[StdinStart..StdinEnd]); + StdinStart = 0; + StdinEnd = 0; + //std.debug.print("{x}\n", .{buffer}); + return bytesAvailable; + } else { + std.mem.copyForwards(u8, buffer, StdinBuf[StdinStart .. StdinStart + buffer.len]); + StdinStart += buffer.len; + //std.debug.print("{x}\n", .{buffer}); + return buffer.len; + } + } +}; + +const TestStdout = struct { + const WriteError = error{}; + const Writer = std.io.Writer(TestStdout, WriteError, write); + + fn writer() Writer { + return .{ + .context = .{}, + }; + } + + fn write(self: TestStdout, buffer: []const u8) WriteError!usize { + _ = self; + const c = @min(buffer.len, StdoutBuf.len - StdoutEnd); + std.mem.copyForwards(u8, StdoutBuf[StdoutEnd .. StdoutEnd + c], buffer[0..c]); + StdoutEnd += c; + return c; + } +}; + +pub fn getTestTermIO() TermIO {} + +pub fn writeTestStdin(input: []const u8) !void { + if (StdinEnd + input.len < StdinBuf.len) { + std.mem.copyForwards(u8, StdinBuf[StdinEnd .. StdinEnd + input.len], input); + StdinEnd += input.len; + //std.debug.print("{x}\n", .{StdinBuf[StdinStart..StdinEnd]}); + } else { + return error.BufferFull; + } +} + +pub fn readTestStdout(count: usize) []u8 { + const c = @min(count, StdoutEnd - StdoutStart); + const buf = StdoutBuf[StdoutStart .. StdoutStart + c]; + StdoutStart += c; + return buf; +} + +pub fn readAllTestStdout() []u8 { + const buf = StdoutBuf[StdoutStart..StdoutEnd]; + StdoutStart = 0; + StdoutEnd = 0; + return buf; +}