From 8b8dcce24e60e39fdb9c65724e8d0f3304de1176 Mon Sep 17 00:00:00 2001 From: Jeeves Date: Fri, 5 Apr 2024 15:05:14 -0600 Subject: [PATCH] first --- .gitattributes | 1 + .gitignore | 18 +++ build.zig | 91 +++++++++++ build.zig.zon | 62 ++++++++ flake.lock | 78 ++++++++++ flake.nix | 82 ++++++++++ src/main.zig | 108 +++++++++++++ src/root.zig | 10 ++ src/terminfo.zig | 386 +++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 836 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/main.zig create mode 100644 src/root.zig create mode 100644 src/terminfo.zig diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..feda423 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# This file is for zig-specific build artifacts. +# If you have OS-specific or editor-specific files to ignore, +# such as *.swp or .DS_Store, put those in your global +# ~/.gitignore and put this in your ~/.gitconfig: +# +# [core] +# excludesfile = ~/.gitignore +# +# Cheers! +# -andrewrk + +zig-cache/ +zig-out/ +/release/ +/debug/ +/build/ +/build-*/ +/docgen_tmp/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..60f2034 --- /dev/null +++ b/build.zig @@ -0,0 +1,91 @@ +const std = @import("std"); + +// Although this function looks imperative, note that its job is to +// declaratively construct a build graph that will be executed by an external +// runner. +pub fn build(b: *std.Build) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard optimization options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not + // set a preferred release mode, allowing the user to decide how to optimize. + const optimize = b.standardOptimizeOption(.{}); + + const lib = b.addStaticLibrary(.{ + .name = "master", + // In this case the main source file is merely a path, however, in more + // complicated build scripts, this could be a generated file. + .root_source_file = .{ .path = "src/root.zig" }, + .target = target, + .optimize = optimize, + }); + + // This declares intent for the library to be installed into the standard + // location when the user invokes the "install" step (the default step when + // running `zig build`). + b.installArtifact(lib); + + const exe = b.addExecutable(.{ + .name = "master", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + + // This declares intent for the executable to be installed into the + // standard location when the user invokes the "install" step (the default + // step when running `zig build`). + b.installArtifact(exe); + + // This *creates* a Run step in the build graph, to be executed when another + // step is evaluated that depends on it. The next line below will establish + // such a dependency. + const run_cmd = b.addRunArtifact(exe); + + // By making the run step depend on the install step, it will be run from the + // installation directory rather than directly from within the cache directory. + // This is not necessary, however, if the application depends on other installed + // files, this ensures they will be present and in the expected location. + run_cmd.step.dependOn(b.getInstallStep()); + + // This allows the user to pass arguments to the application in the build + // command itself, like this: `zig build run -- arg1 arg2 etc` + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // This creates a build step. It will be visible in the `zig build --help` menu, + // and can be selected like this: `zig build run` + // This will evaluate the `run` step rather than the default, which is "install". + 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/root.zig" }, + .target = target, + .optimize = optimize, + }); + + const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + + const exe_unit_tests = b.addTest(.{ + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + + const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + + // Similar to creating the run step earlier, this exposes a `test` step to + // the `zig build --help` menu, providing a way for the user to request + // running the unit tests. + 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); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..8085bd1 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,62 @@ +.{ + .name = "master", + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.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", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .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/flake.lock b/flake.lock new file mode 100644 index 0000000..125ebce --- /dev/null +++ b/flake.lock @@ -0,0 +1,78 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1706063522, + "narHash": "sha256-o1m9en7ovSjyktXgX3n/6GJEwG06WYa/9Mfx5hTTf5g=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "95c1439b205d507f3cb88aae76e02cd6a01ac504", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "zig2nix": "zig2nix" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "zig2nix": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1712106553, + "narHash": "sha256-p7xlViVQh/916u28pTtPw3L+s2PkkgA5MAMk/Vfibac=", + "owner": "Cloudef", + "repo": "zig2nix", + "rev": "32bae779ad1712a83919e8a61da1ea8e60e328e1", + "type": "github" + }, + "original": { + "owner": "Cloudef", + "repo": "zig2nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..68605ae --- /dev/null +++ b/flake.nix @@ -0,0 +1,82 @@ +{ + description = "Zig project flake"; + + inputs = { + zig2nix.url = "github:Cloudef/zig2nix"; + }; + + outputs = { zig2nix, ... }: let + flake-utils = zig2nix.inputs.flake-utils; + in (flake-utils.lib.eachDefaultSystem (system: let + # Zig flake helper + # Check the flake.nix in zig2nix project for more options: + # + env = zig2nix.outputs.zig-env.${system} { zig = zig2nix.outputs.packages.${system}.zig.master.bin; }; + system-triple = env.lib.zigTripleFromString system; + in with builtins; with env.lib; with env.pkgs.lib; rec { + # nix build .#target.{zig-target} + # e.g. nix build .#target.x86_64-linux-gnu + packages.target = genAttrs allTargetTriples (target: env.packageForTarget target ({ + src = cleanSource ./.; + + nativeBuildInputs = with env.pkgs; []; + buildInputs = with env.pkgsForTarget target; []; + + # Smaller binaries and avoids shipping glibc. + zigPreferMusl = true; + + # This disables LD_LIBRARY_PATH mangling, binary patching etc... + # The package won't be usable inside nix. + zigDisableWrap = true; + } // optionalAttrs (!pathExists ./build.zig.zon) { + pname = "my-zig-project"; + version = "0.0.0"; + })); + + # nix build . + packages.default = packages.target.${system-triple}.override { + # Prefer nix friendly settings. + zigPreferMusl = false; + zigDisableWrap = false; + }; + + # For bundling with nix bundle for running outside of nix + # example: https://github.com/ralismark/nix-appimage + apps.bundle.target = genAttrs allTargetTriples (target: let + pkg = packages.target.${target}; + in { + type = "app"; + program = "${pkg}/bin/master"; + }); + + # default bundle + apps.bundle.default = apps.bundle.target.${system-triple}; + + # nix run . + apps.default = env.app [] "zig build run -- \"$@\""; + + # nix run .#build + apps.build = env.app [] "zig build \"$@\""; + + # nix run .#test + apps.test = env.app [] "zig build test -- \"$@\""; + + # nix run .#docs + apps.docs = env.app [] "zig build docs -- \"$@\""; + + # nix run .#deps + apps.deps = env.showExternalDeps; + + # nix run .#zon2json + apps.zon2json = env.app [env.zon2json] "zon2json \"$@\""; + + # nix run .#zon2json-lock + apps.zon2json-lock = env.app [env.zon2json-lock] "zon2json-lock \"$@\""; + + # nix run .#zon2nix + apps.zon2nix = env.app [env.zon2nix] "zon2nix \"$@\""; + + # nix develop + devShells.default = env.mkShell {}; + })); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..b75bbd1 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,108 @@ +const std = @import("std"); +const os = std.os; +const io = std.io; +const fs = std.fs; +const mem = std.mem; +const heap = std.heap; +const fmt = std.fmt; + +pub fn main() !void { + var gpa = heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + var term = try Terminal.init(allocator); + defer term.deinit(); + + // var info = try Terminal.Info.init(allocator); + // defer info.deinit(); + + // var seq = Terminal.Info.Sequence.init(allocator, &info); + // defer seq.deinit(); + // try seq.cursorLeft(); + try term.print("poopoo", .{}); + try term.cursorLeft(); + try term.cursorLeft(); + try term.print("ee", .{}); + // try seq.writeOut(term.tty.writer()); //io.AnyWriter{ .context = &term.tty, .writeFn = &fs.File.write }); +} + +pub const Terminal = struct { + tty: fs.File, + original_termios: os.linux.termios, + info: Info, + allocator: mem.Allocator, + + pub fn init(allocator: mem.Allocator) !Terminal { + var term = Terminal{ + .tty = try fs.openFileAbsolute("/dev/tty", .{ .mode = .read_write }), + .original_termios = undefined, + .info = try Info.init(allocator), + .allocator = allocator, + }; + errdefer term.tty.close(); + errdefer term.info.deinit(); + term.uncook(); + return term; + } + + pub fn deinit(self: *Terminal) void { + self.cook(); + self.tty.close(); + self.info.deinit(); + } + + // pub fn poll(self: *Terminal) void {} + + fn uncook(self: *Terminal) void { + _ = os.linux.tcgetattr(self.tty.handle, &self.original_termios); + var raw = self.original_termios; + + raw.lflag.ECHO = false; + raw.lflag.ICANON = false; + raw.lflag.ISIG = false; + raw.lflag.IEXTEN = false; + + raw.iflag.IXON = false; + raw.iflag.ICRNL = false; + raw.iflag.BRKINT = false; + raw.iflag.INPCK = false; + raw.iflag.ISTRIP = false; + + raw.oflag.OPOST = false; + + raw.cc[@intFromEnum(os.linux.V.TIME)] = 0; + raw.cc[@intFromEnum(os.linux.V.MIN)] = 1; + + _ = os.linux.tcsetattr(self.tty.handle, .FLUSH, &raw); + } + + fn cook(self: *Terminal) void { + _ = os.linux.tcsetattr(self.tty.handle, .FLUSH, &self.original_termios); + } + + pub fn print(self: *Terminal, comptime format: []const u8, args: anytype) !void { + const formatted = try fmt.allocPrint(self.allocator, format, args); + defer self.allocator.free(formatted); + try self.tty.writeAll(formatted); + } + + pub fn cursorLeft(self: *Terminal) !void { + try self.info.cursorLeft(self.tty.writer()); + } + + pub const Info = @import("terminfo.zig"); + + pub const SpecialKey = enum(u16) { + home, + end, + page_up, + page_down, + delete, + backspace, + arrow_left, + arrow_right, + arrow_up, + arrow_down, + }; +}; diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..ecfeade --- /dev/null +++ b/src/root.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const testing = std.testing; + +export fn add(a: i32, b: i32) i32 { + return a + b; +} + +test "basic add functionality" { + try testing.expect(add(3, 7) == 10); +} diff --git a/src/terminfo.zig b/src/terminfo.zig new file mode 100644 index 0000000..b1b5025 --- /dev/null +++ b/src/terminfo.zig @@ -0,0 +1,386 @@ +const std = @import("std"); +const mem = std.mem; +const fmt = std.fmt; +const io = std.io; + +const Self = @This(); + +pub fn init(allocator: mem.Allocator) !Self { + const result = try std.ChildProcess.run(.{ + .allocator = allocator, + .argv = &[_][]const u8{ "infocmp", "-x" }, + }); + defer allocator.free(result.stdout); + defer allocator.free(result.stderr); + return parse(allocator, result.stdout); +} + +pub fn parse(allocator: mem.Allocator, entry: []const u8) !Self { + var self = Self{ + .allocator = allocator, + .entry = try allocator.dupe(u8, entry), + .names = undefined, + .bools = std.StringHashMap(bool).init(allocator), + .ints = std.StringHashMap(u32).init(allocator), + .strings = std.StringHashMap([]const u8).init(allocator), + }; + + var num_fields: usize = 0; + var line_it = mem.splitScalar(u8, self.entry, '\n'); + while (line_it.next()) |line| { + if (mem.startsWith(u8, line, "#")) continue; + + var field_it = FieldIterator{ .buffer = line, .index = 0 }; + while (field_it.next()) |field| : (num_fields += 1) { + if (num_fields == 0) { + self.names = field; + continue; + } + // std.debug.print("'{s}' (len {d}): ", .{ arg, arg.len }); + if (mem.indexOfScalar(u8, field, '=')) |idx| { + // string + try self.strings.put(field[0..idx], try unescapeString(allocator, field[idx + 1 ..])); + // std.debug.print("string {s} {s}\n", .{ field[0..idx], field[idx + 1 ..] }); + } else if (mem.indexOfScalar(u8, field, '#')) |idx| { + // int + try self.ints.put(field[0..idx], try fmt.parseInt(u32, field[idx + 1 ..], 0)); + // std.debug.print("int {s} {s}\n", .{ field[0..idx], field[idx + 1 ..] }); + } else { + // bool + // std.debug.print("bool {s}\n", .{field}); + try self.bools.put(field, true); + } + } + } + + // var it = self.bools.keyIterator(); + // while (it.next()) |k| std.debug.print("{s}\n", .{k.*}); + + return self; +} + +fn unescapeString(allocator: mem.Allocator, input: []const u8) ![]u8 { + var output = try allocator.alloc(u8, input.len); + errdefer allocator.free(output); + + var i: usize = 0; + var slide: usize = 0; + var size = input.len; + while (slide < input.len) { + if (startsWithNoEscape(input, slide, "\\E") or startsWithNoEscape(input, slide, "\\e")) { + output[i] = '\x1b'; + i += 1; + slide += 2; + size -= 1; + } else if (input[slide] == '^' and (slide == 0 or input[slide - 1] != '\\') and std.ascii.isUpper(input[slide + 1])) { + output[i] = input[slide + 1] - 64; // convert to control character + i += 1; + slide += 2; + size -= 1; + } else if (startsWithNoEscape(input, slide, "\\n")) { + output[i] = '\n'; + i += 1; + slide += 2; + size -= 1; + } else if (startsWithNoEscape(input, slide, "\\l")) { + output[i] = '\x0a'; + i += 1; + slide += 2; + size -= 1; + } else if (startsWithNoEscape(input, slide, "\\r")) { + output[i] = '\r'; + i += 1; + slide += 2; + size -= 1; + } else if (startsWithNoEscape(input, slide, "\\t")) { + output[i] = '\t'; + i += 1; + slide += 2; + size -= 1; + } else if (startsWithNoEscape(input, slide, "\\b")) { + output[i] = '\x08'; + i += 1; + slide += 2; + size -= 1; + } else if (startsWithNoEscape(input, slide, "\\f")) { + output[i] = '\x0c'; + i += 1; + slide += 2; + size -= 1; + } else if (startsWithNoEscape(input, slide, "\\s")) { + output[i] = ' '; + i += 1; + slide += 2; + size -= 1; + } else { + output[i] = input[slide]; + i += 1; + slide += 1; + } + } + + // TODO: now do the reverse and make a startsWithEscape + + // const in = try allocator.dupe(u8, output); + // defer allocator.free(in); + // i = 0; + // slide = 0; + // while (slide < in.len) { + // if (in[slide] == '^' and (slide == 0 or in[slide - 1] != '\\') and std.ascii.isUpper(in[slide + 1])) { + // output[i] = in[slide + 1] - 64; // convert to control character + // i += 1; + // slide += 2; + // size -= 1; + // } else { + // output[i] = in[slide]; + // i += 1; + // slide += 1; + // } + // } + + return try allocator.realloc(output, size); +} + +fn startsWithNoEscape(haystack: []const u8, index: usize, needle: []const u8) bool { + return mem.startsWith(u8, haystack[index..], needle) and (index == 0 or haystack[index - 1] != '\\'); +} + +pub fn deinit(self: *Self) void { + self.bools.deinit(); + self.ints.deinit(); + var sit = self.strings.valueIterator(); + while (sit.next()) |s| self.allocator.free(s.*); + self.strings.deinit(); + self.allocator.free(self.entry); +} + +/// Iterates over terminfo fields, ignoring backslash-escaped commas +/// NOTE: Does not follow full iterator pattern! +const FieldIterator = struct { + buffer: []const u8, + index: ?usize, + + pub fn next(self: *FieldIterator) ?[]const u8 { + const start = self.index orelse return null; + const end = if (getEnd(self.buffer, start)) |delim_start| blk: { + self.index = delim_start + 1; + break :blk delim_start; + } else blk: { + self.index = null; + break :blk self.buffer.len; + }; + const trimmed = mem.trim(u8, self.buffer[start..end], &std.ascii.whitespace); + if (trimmed.len == 0) return null; + return trimmed; + } + + fn getEnd(buf: []const u8, start: usize) ?usize { + var comma = mem.indexOfScalarPos(u8, buf, start, ',') orelse return null; + while (buf[comma - 1] == '\\') comma = mem.indexOfScalarPos(u8, buf, comma + 1, ',') orelse return null; + return comma; + } +}; + +allocator: mem.Allocator, +entry: []u8, +names: []const u8, +bools: std.StringHashMap(bool), +ints: std.StringHashMap(u32), +strings: std.StringHashMap([]const u8), + +pub fn cursorLeft(self: *Self, writer: anytype) !void { + try writer.writeAll(self.strings.get("cub1") orelse "\x08"); +} + +// pub const Sequence = struct { +// info: *Self, +// bytes: std.ArrayList(u8), + +// pub fn init(allocator: mem.Allocator, info: *Self) Sequence { +// return .{ .info = info, .bytes = std.ArrayList(u8).init(allocator) }; +// } + +// pub fn deinit(self: *Sequence) void { +// self.bytes.deinit(); +// } + +// pub fn writeOut(self: *Sequence, writer: anytype) !void { +// try writer.writeAll(self.bytes.items); +// } + +// pub fn cursorLeft(self: *Sequence) !void { +// try self.bytes.appendSlice(self.info.strings.get("cub1") orelse "\x08"); +// } +// }; + +pub const Bool = enum { + auto_left_margin, + auto_right_margin, + back_color_erase, + can_change, + ceol_standout_glitch, + col_addr_glitch, + cpi_changes_res, + cr_cancels_micro_mode, + dest_tabs_magic_smso, + eat_newline_glitch, + erase_overstrike, + generic_type, + hard_copy, + hard_cursor, + has_meta_key, + has_print_wheel, + has_status_line, + hue_lightness_saturation, + insert_null_glitch, + lpi_changes_res, + memory_above, + memory_below, + move_insert_mode, + move_standout_mode, + needs_xon_xoff, + no_esc_ctlc, + no_pad_char, + non_dest_scroll_region, + non_rev_rmcup, + over_strike, + prtr_silent, + row_addr_glitch, + semi_auto_right_margin, + status_line_esc_ok, + tilde_glitch, + transparent_underline, + xon_xoff, + + pub fn toCapName(self: Bool) []const u8 { + return switch (self) { + .auto_left_margin => "bw", + .auto_right_margin => "am", + .back_color_erase => "bce", + .can_change => "ccc", + .ceol_standout_glitch => "xhp", + .col_addr_glitch => "xhpa", + .cpi_changes_res => "cpix", + .cr_cancels_micro_mode => "crxm", + .dest_tabs_magic_smso => "xt", + .eat_newline_glitch => "xenl", + .erase_overstrike => "eo", + .generic_type => "gn", + .hard_copy => "hc", + .hard_cursor => "chts", + .has_meta_key => "km", + .has_print_wheel => "daisy", + .has_status_line => "hs", + .hue_lightness_saturation => "hls", + .insert_null_glitch => "in", + .lpi_changes_res => "lpix", + .memory_above => "da", + .memory_below => "db", + .move_insert_mode => "mir", + .move_standout_mode => "msgr", + .needs_xon_xoff => "nxon", + .no_esc_ctlc => "xsb", + .no_pad_char => "npc", + .non_dest_scroll_region => "ndscr", + .non_rev_rmcup => "nrrmc", + .over_strike => "os", + .prtr_silent => "mc5i", + .row_addr_glitch => "xvpa", + .semi_auto_right_margin => "sam", + .status_line_esc_ok => "eslok", + .tilde_glitch => "hz", + .transparent_underline => "ul", + .xon_xoff => "xon", + }; + } +}; + +pub const Ints = enum { + columns, + init_tabs, + label_height, + label_width, + lines, + lines_of_memory, + magic_cookie_glitch, + max_attributes, + max_colors, + max_pairs, + maximum_windows, + no_color_video, + num_labels, + padding_baud_rate, + virtual_terminal, + width_status_line, +}; + +pub const Strings = enum { + acs_chars, + back_tab_bell, + carriage_return, + change_char_pitch, + change_line_pitch, + change_res_horz, + change_res_vert, + change_scroll_region, + char_padding, + clear_all_tabs, +}; + +test "parse terminfo" { + var terminfo = try parse(std.testing.allocator, + \\# Reconstructed via infocmp from file: /nix/store/c1d0bgq6whz4khqxncmqikpdsxmr1szw-kitty-0.32.2/lib/kitty/terminfo/x/xterm-kitty + \\xterm-kitty|KovIdTTY, + \\ am, ccc, hs, km, mc5i, mir, msgr, npc, xenl, + \\ colors#0x100, cols#80, it#8, lines#24, pairs#0x7fff, + \\ acsc=++\,\,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, + \\ bel=^G, bold=\E[1m, cbt=\E[Z, civis=\E[?25l, + \\ clear=\E[H\E[2J, cnorm=\E[?12h\E[?25h, cr=\r, + \\ csr=\E[%i%p1%d;%p2%dr, cub=\E[%p1%dD, cub1=^H, + \\ cud=\E[%p1%dB, cud1=\n, cuf=\E[%p1%dC, cuf1=\E[C, + \\ cup=\E[%i%p1%d;%p2%dH, cuu=\E[%p1%dA, cuu1=\E[A, + \\ cvvis=\E[?12;25h, dch=\E[%p1%dP, dch1=\E[P, dim=\E[2m, + \\ dl=\E[%p1%dM, dl1=\E[M, dsl=\E]2;\E\\, ech=\E[%p1%dX, + \\ ed=\E[J, el=\E[K, el1=\E[1K, flash=\E[?5h$<100/>\E[?5l, + \\ fsl=^G, home=\E[H, hpa=\E[%i%p1%dG, ht=^I, hts=\EH, + \\ ich=\E[%p1%d@, il=\E[%p1%dL, il1=\E[L, ind=\n, + \\ indn=\E[%p1%dS, + \\ initc=\E]4;%p1%d;rgb:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, + \\ kBEG=\E[1;2E, kDC=\E[3;2~, kEND=\E[1;2F, kHOM=\E[1;2H, + \\ kIC=\E[2;2~, kLFT=\E[1;2D, kNXT=\E[6;2~, kPRV=\E[5;2~, + \\ kRIT=\E[1;2C, ka1=, ka3=, kbeg=\EOE, kbs=^?, kc1=, kc3=, + \\ kcbt=\E[Z, kcub1=\EOD, kcud1=\EOB, kcuf1=\EOC, kcuu1=\EOA, + \\ kdch1=\E[3~, kend=\EOF, kf1=\EOP, kf10=\E[21~, kf11=\E[23~, + \\ kf12=\E[24~, kf13=\E[1;2P, kf14=\E[1;2Q, kf15=\E[13;2~, + \\ kf16=\E[1;2S, kf17=\E[15;2~, kf18=\E[17;2~, + \\ kf19=\E[18;2~, kf2=\EOQ, kf20=\E[19;2~, kf21=\E[20;2~, + \\ kf22=\E[21;2~, kf23=\E[23;2~, kf24=\E[24;2~, + \\ kf25=\E[1;5P, kf26=\E[1;5Q, kf27=\E[13;5~, kf28=\E[1;5S, + \\ kf29=\E[15;5~, kf3=\EOR, kf30=\E[17;5~, kf31=\E[18;5~, + \\ kf32=\E[19;5~, kf33=\E[20;5~, kf34=\E[21;5~, + \\ kf35=\E[23;5~, kf36=\E[24;5~, kf37=\E[1;6P, kf38=\E[1;6Q, + \\ kf39=\E[13;6~, kf4=\EOS, kf40=\E[1;6S, kf41=\E[15;6~, + \\ kf42=\E[17;6~, kf43=\E[18;6~, kf44=\E[19;6~, + \\ kf45=\E[20;6~, kf46=\E[21;6~, kf47=\E[23;6~, + \\ kf48=\E[24;6~, kf49=\E[1;3P, kf5=\E[15~, kf50=\E[1;3Q, + \\ kf51=\E[13;3~, kf52=\E[1;3S, kf53=\E[15;3~, + \\ kf54=\E[17;3~, kf55=\E[18;3~, kf56=\E[19;3~, + \\ kf57=\E[20;3~, kf58=\E[21;3~, kf59=\E[23;3~, kf6=\E[17~, + \\ kf60=\E[24;3~, kf61=\E[1;4P, kf62=\E[1;4Q, kf63=\E[13;4~, + \\ kf7=\E[18~, kf8=\E[19~, kf9=\E[20~, khlp=, khome=\EOH, + \\ kich1=\E[2~, kind=\E[1;2B, kmous=\E[M, knp=\E[6~, + \\ kpp=\E[5~, kri=\E[1;2A, kund=, oc=\E]104\007, op=\E[39;49m, + \\ rc=\E8, rep=%p1%c\E[%p2%{1}%-%db, rev=\E[7m, ri=\EM, + \\ rin=\E[%p1%dT, ritm=\E[23m, rmacs=\E(B, rmam=\E[?7l, + \\ rmcup=\E[?1049l, rmir=\E[4l, rmkx=\E[?1l, rmso=\E[27m, + \\ rmul=\E[24m, rs1=\E]\E\\\Ec, sc=\E7, + \\ setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, + \\ setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, + \\ sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, + \\ sgr0=\E(B\E[m, sitm=\E[3m, smacs=\E(0, smam=\E[?7h, + \\ smcup=\E[?1049h, smir=\E[4h, smkx=\E[?1h, smso=\E[7m, + \\ smul=\E[4m, tbc=\E[3g, tsl=\E]2;, u6=\E[%i%d;%dR, u7=\E[6n, + \\ u8=\E[?%[;0123456789]c, u9=\E[c, vpa=\E[%i%p1%dd, + ); + defer terminfo.deinit(); +}