diff options
Diffstat (limited to 'build.zig')
| -rw-r--r-- | build.zig | 188 |
1 files changed, 186 insertions, 2 deletions
@@ -682,11 +682,195 @@ pub fn build(b: *std.Build) void { .tables_path = uucode_tables, }).module("uucode"); - const vaxis_mod = b.dependency("vaxis", .{ + const vaxis_dep = b.dependency("vaxis", .{ .target = target, .optimize = optimize, .external_uucode = true, - }).module("vaxis"); + }); + const vaxis_mod = vaxis_dep.module("vaxis"); + // vaxis's input parser, patched here at build time like mvzr so that a + // fresh fetch keeps it: no reply, split read or malformed sequence may + // reach the editor as typed text, and none may panic or end input. + // - A cursor position report that no F3 could have sent is no key: the + // startup width queries' `CSI 1;1R` arrived as two F3s, which a shell + // pane took for text. + // - ESC before a CSI or SS3 key (rxvt's Alt+arrow) is Alt on that key, + // not Alt+Escape followed by `[A` as text. + // - An X10 mouse report and a kitty graphics reply cut short by a read + // wait for their rest: the first became three bytes of text, the + // second sliced past its input. + // - A malformed color or clipboard reply is dropped instead of failing + // the parse, which stopped input; so is a byte no UTF-8 starts with. + // - An OSC reply ends at its first terminator: one ended by BEL ran on to + // the next sequence's ESC and left what lay between as text. + // - A device report's fields are sought in the sequence, not past it. + // A bump that moves an anchor stops the build. + { + const parser_path = vaxis_dep.path("src/Parser.zig").getPath(b); + var src = std.Io.Dir.cwd().readFileAlloc(io, parser_path, b.allocator, .limited(1 << 20)) catch @panic("read vaxis Parser.zig"); + const edits = [_]struct { anchor: []const u8, with: []const u8, count: usize = 1 }{ + .{ + .anchor = + \\ 0x5F => return parseApc(input), + \\ else => { + , + .with = + \\ 0x5F => return parseApc(input), + \\ // pardes's patch (its build.zig): ESC before a key's sequence is Alt on the key. + \\ 0x1B => if (input.len > 2 and (input[2] == '[' or input[2] == 'O')) { + \\ var inner = if (input[2] == '[') parseCsi(input[1..], &self.buf) else parseSs3(input[1..]); + \\ if (inner.n == 0) return inner; + \\ if (inner.event) |*event| switch (event.*) { + \\ .key_press, .key_release => |*key| key.mods.alt = true, + \\ else => {}, + \\ }; + \\ inner.n += 1; + \\ return inner; + \\ } else return .{ .event = .{ .key_press = .{ .codepoint = Key.escape, .mods = .{ .alt = true } } }, .n = 2 }, + \\ else => { + , + }, + .{ + .anchor = " const first_cp = iter.next() orelse return error.InvalidUTF8;\n", + .with = " const first_cp = iter.next() orelse return .{ .event = null, .n = 1 }; // pardes's patch (its build.zig)\n", + }, + .{ + .anchor = " n = std.unicode.utf8CodepointSequenceLength(first_cp) catch return error.InvalidUTF8;\n", + .with = " n = std.unicode.utf8CodepointSequenceLength(first_cp) catch return .{ .event = null, .n = 1 }; // pardes's patch (its build.zig)\n", + }, + .{ + .anchor = + \\ const sequence = input[0 .. end + 1 + 1]; + \\ + \\ switch (input[2]) { + , + .with = + \\ // pardes's patch (its build.zig): an ESC that ends the input is half an ST. + \\ if (input.len < end + 2) return .{ .event = null, .n = 0 }; + \\ const sequence = input[0 .. end + 1 + 1]; + \\ + \\ switch (input[2]) { + , + }, + .{ + .anchor = + \\ const esc_result = skipUntilST(input); + \\ if (esc_result.n > 0) break :blk esc_result.n; + \\ + \\ // No escape, could be BEL terminated + \\ const bel = std.mem.indexOfScalarPos(u8, input, 2, 0x07) orelse return .{ + \\ .event = null, + \\ .n = 0, + \\ }; + \\ bel_terminated = true; + \\ break :blk bel + 1; + , + .with = + \\ // pardes's patch (its build.zig): whichever terminator comes first ends it. + \\ const esc = std.mem.indexOfScalarPos(u8, input, 2, 0x1b); + \\ const bel = std.mem.indexOfScalarPos(u8, input, 2, 0x07); + \\ if (bel != null and (esc == null or bel.? < esc.?)) { + \\ bel_terminated = true; + \\ break :blk bel.? + 1; + \\ } + \\ const esc_result = skipUntilST(input); + \\ if (esc_result.n > 0) break :blk esc_result.n; + \\ return .{ .event = null, .n = 0 }; + , + }, + .{ + .anchor = " const color = try Color.rgbFromSpec(color_spec);\n", + .with = " const color = Color.rgbFromSpec(color_spec) catch return null_event; // pardes's patch (its build.zig)\n", + .count = 2, + }, + .{ + .anchor = + \\ if (input[semicolon_idx + 1] != 'c') return null_event; + \\ const payload = if (bel_terminated) + \\ input[semicolon_idx + 3 .. sequence.len - 1] + \\ else + \\ input[semicolon_idx + 3 .. sequence.len - 2]; + \\ const decoder = std.base64.standard.Decoder; + \\ const text = try paste_allocator.?.alloc(u8, try decoder.calcSizeForSlice(payload)); + \\ try decoder.decode(text, payload); + , + .with = + \\ // pardes's patch (its build.zig): a malformed reply is dropped. + \\ const body_end = if (bel_terminated) sequence.len - 1 else sequence.len - 2; + \\ if (semicolon_idx + 3 > body_end or input[semicolon_idx + 1] != 'c') return null_event; + \\ const payload = input[semicolon_idx + 3 .. body_end]; + \\ const decoder = std.base64.standard.Decoder; + \\ const gpa = paste_allocator orelse return null_event; + \\ const text = gpa.alloc(u8, decoder.calcSizeForSlice(payload) catch return null_event) catch return null_event; + \\ decoder.decode(text, payload) catch { + \\ gpa.free(text); + \\ return null_event; + \\ }; + , + }, + .{ + .anchor = + \\ 'A', 'B', 'C', 'D', 'E', 'F', 'H', 'P', 'Q', 'R', 'S' => { + \\ + , + .with = + \\ 'A', 'B', 'C', 'D', 'E', 'F', 'H', 'P', 'Q', 'R', 'S' => { + \\ // pardes's patch (its build.zig): a cursor position report is no key. + \\ if (final == 'R' and cursorReport(sequence)) return null_event; + \\ + , + }, + .{ + .anchor = " std.debug.assert(sequence.len >= 4); // ESC [ ? c == 4 bytes\n", + .with = " if (sequence.len < 4) return null_event; // pardes's patch (its build.zig)\n", + }, + .{ + .anchor = "const delim_idx = std.mem.indexOfScalarPos(u8, input, 3, ';')", + .with = "const delim_idx = std.mem.indexOfScalarPos(u8, sequence, 3, ';')", + .count = 2, + }, + .{ + .anchor = "input[delim_idx + 1 .. sequence.len - 2]", + .with = "sequence[@min(delim_idx + 1, sequence.len - 2) .. sequence.len - 2]", + }, + .{ + .anchor = " if (input.len == 3 and (input[2] == 'M') and full_input.len >= 6) {\n", + .with = + \\ // pardes's patch (its build.zig): an X10 report still arriving is incomplete, not text. + \\ if (input.len == 3 and input[2] == 'M' and full_input.len < 6) return .{ .event = null, .n = 0 }; + \\ if (input.len == 3 and (input[2] == 'M') and full_input.len >= 6) { + \\ + , + }, + }; + for (edits) |edit| { + if (std.mem.count(u8, src, edit.anchor) != edit.count) std.debug.panic("vaxis's Parser.zig changed at `{s}`: redo its patch in build.zig", .{edit.anchor}); + src = std.mem.replaceOwned(u8, b.allocator, src, edit.anchor, edit.with) catch @panic("OOM"); + } + const cursor_report = + \\ + \\/// pardes's patch (its build.zig): whether `CSI ... R` answers a cursor + \\/// position query. Only `CSI 1 ; mods R` with mods past 1 is a modified F3 + \\/// (a bare F3 is `SS3 R`); a report has another row, or col 1 (mods 1). + \\fn cursorReport(sequence: []const u8) bool { + \\ const params = sequence[2 .. sequence.len - 1]; + \\ if (params.len > 0 and params[0] == '?') return true; + \\ var fields = std.mem.splitScalar(u8, params, ';'); + \\ const row = fields.first(); + \\ const col = fields.next() orelse return false; + \\ if (fields.next() != null or row.len == 0 or col.len == 0) return false; + \\ for (params) |c| if (c != ';' and !std.ascii.isDigit(c)) return false; + \\ return !std.mem.eql(u8, row, "1") or std.mem.eql(u8, col, "1"); + \\} + \\ + ; + const files = b.addWriteFiles(); + _ = files.add("Parser.zig", b.fmt("{s}{s}", .{ src, cursor_report })); + _ = files.addCopyFile(vaxis_dep.path("src/widgets/terminal/Parser.zig"), "widgets/terminal/Parser.zig"); + // Suffixes: this leaves out both Parser.zig files, written above. + _ = files.addCopyDirectory(vaxis_dep.path("src"), "", .{ .exclude_extensions = &.{"Parser.zig"} }); + vaxis_mod.root_source_file = files.getDirectory().path(b, "main.zig"); + } var ghostty_vt_for_snap: ?*std.Build.Module = null; const ghostty_simd = !freestanding_core and (!target.result.os.tag.isDarwin() or b.graph.host.result.os.tag.isDarwin()); |
