From 142d718dcbd7fabde8d30b6aa0e288f18b3563b5 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 7 Feb 2024 21:49:37 +0100 Subject: [PATCH] Initial release --- .github/workflows/release_tarball.yml | 44 ++ .gitignore | 3 + build.zig | 54 ++ build.zig.version | 1 + build.zig.zon | 37 ++ src/build-dest.zig | 14 + src/cbor.zig | 914 ++++++++++++++++++++++++++ src/compile.zig | 797 ++++++++++++++++++++++ src/default.json | 709 ++++++++++++++++++++ src/theme.zig | 30 + src/theme_files.zig | 34 + zig | 48 ++ 12 files changed, 2685 insertions(+) create mode 100644 .github/workflows/release_tarball.yml create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 build.zig.version create mode 100644 build.zig.zon create mode 100644 src/build-dest.zig create mode 100644 src/cbor.zig create mode 100644 src/compile.zig create mode 100644 src/default.json create mode 100644 src/theme.zig create mode 100644 src/theme_files.zig create mode 100755 zig diff --git a/.github/workflows/release_tarball.yml b/.github/workflows/release_tarball.yml new file mode 100644 index 0000000..a5dc8dc --- /dev/null +++ b/.github/workflows/release_tarball.yml @@ -0,0 +1,44 @@ +name: Release tarball +on: + push: + branches: + - master + +permissions: + contents: write + +jobs: + release_tarball: + runs-on: ubuntu-latest + steps: + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Make tarball + run: | + ./zig build + tar -czf flow-themes.tar.gz zig-out + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref_name }}-${{ github.sha }} + release_name: Tarball release + body: Tarball release + draft: false + prerelease: false + + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./flow-themes.tar.gz + asset_name: flow-themes.tar.gz + asset_content_type: application/gzip diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b910c50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.cache/ +/zig-*/ +/themes/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..92d376b --- /dev/null +++ b/build.zig @@ -0,0 +1,54 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + + const cbor_mod = b.createModule(.{ .root_source_file = .{ .path = "src/cbor.zig" } }); + const theme_mod = b.createModule(.{ .root_source_file = .{ .path = "src/theme.zig" } }); + const themes_compile = b.addExecutable(.{ + .name = "themes_compile", + .target = target, + .root_source_file = .{ .path = "src/compile.zig" }, + }); + add_themes(b, themes_compile); + themes_compile.root_module.addImport("cbor", cbor_mod); + themes_compile.root_module.addImport("theme", theme_mod); + // b.installArtifact(themes_compile); + const themes_compile_step = b.addRunArtifact(themes_compile); + const themes_compile_output = themes_compile_step.addOutputFileArg("themes.zig"); + b.getInstallStep().dependOn(&b.addInstallFileWithDir(themes_compile_output, .{ .custom = "src" }, "themes.zig").step); + b.installFile("src/theme.zig", "src/theme.zig"); + b.installFile("src/build-dest.zig", "./build.zig"); +} + +fn theme_file(b: *std.Build, exe: anytype, comptime dep_name: []const u8, comptime sub_path: []const u8) void { + const dep = b.dependency("theme_" ++ dep_name, .{}); + exe.root_module.addImport(sub_path, b.createModule(.{ .root_source_file = dep.path(sub_path) })); +} + +fn add_themes(b: *std.Build, exe: anytype) void { + theme_file(b, exe, "1984", "themes/1984-color-theme.json"); + theme_file(b, exe, "1984", "themes/1984-cyberpunk-color-theme.json"); + theme_file(b, exe, "1984", "themes/1984-orwell-color-theme.json"); + theme_file(b, exe, "1984", "themes/1984-light-color-theme.json"); + theme_file(b, exe, "cobalt2", "theme/cobalt2.json"); + theme_file(b, exe, "oldschool", "themes/oldschool-gray-color-theme.json"); + theme_file(b, exe, "oldschool", "themes/oldschool-terminal-green.json"); + theme_file(b, exe, "turbo_colors", "themes/Turbo Colors-color-theme.json"); + theme_file(b, exe, "vscode", "extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json"); + theme_file(b, exe, "vscode", "extensions/theme-monokai/themes/monokai-color-theme.json"); + theme_file(b, exe, "vscode", "extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json"); + theme_file(b, exe, "vscode", "extensions/theme-solarized-light/themes/solarized-light-color-theme.json"); + theme_file(b, exe, "vscode", "extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json"); + theme_file(b, exe, "vscode", "extensions/theme-defaults/themes/dark_modern.json"); + theme_file(b, exe, "vscode", "extensions/theme-defaults/themes/dark_plus.json"); + theme_file(b, exe, "vscode", "extensions/theme-defaults/themes/dark_vs.json"); + theme_file(b, exe, "vscode", "extensions/theme-defaults/themes/light_modern.json"); + theme_file(b, exe, "vscode", "extensions/theme-defaults/themes/light_plus.json"); + theme_file(b, exe, "vscode", "extensions/theme-defaults/themes/light_vs.json"); + theme_file(b, exe, "CRT", "themes/CRT-64-color-theme.json"); + theme_file(b, exe, "CRT", "themes/CRT-Amber-color-theme.json"); + theme_file(b, exe, "CRT", "themes/CRT-Gray-color-theme.json"); + theme_file(b, exe, "CRT", "themes/CRT-Green-color-theme.json"); + theme_file(b, exe, "CRT", "themes/CRT-Paper-color-theme.json"); +} diff --git a/build.zig.version b/build.zig.version new file mode 100644 index 0000000..f094249 --- /dev/null +++ b/build.zig.version @@ -0,0 +1 @@ +0.12.0-dev.2341+92211135f diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..07f2b48 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,37 @@ +.{ + .name = "flow-themes", + .version = "0.0.1", + + .dependencies = .{ + + // themes + .theme_1984 = .{ + .url = "https://github.com/juanmnl/vs-1984/archive/983c9bd674d2aedd9edb6f18b2d62c31a2f9eae7.tar.gz", + .hash = "1220fba79c5688d869aa2207e3e9b174a985bf298b9017f463e9e3a5fbb7668b86d9", + }, + .theme_cobalt2 = .{ + .url = "https://github.com/wesbos/cobalt2-vscode/archive/542ecdfcff52c5175b85438de237ba88e7f6c140.tar.gz", + .hash = "1220b860fa41d1d90450518e76096bcd9473d7e3e5e4ed01d3578ae4fe10175e9981", + }, + .theme_oldschool = .{ + .url = "https://github.com/EricsonWillians/oldschool-theme/archive/15f472028b56ac58495d6bf4ecd7d173320646f0.tar.gz", + .hash = "12207eb3983e2ecd7e91c299e7a9dc77b13d44fd5d6bada1928f2d6864c0036691cf", + }, + .theme_turbo_colors = .{ + .url = "https://github.com/metropolian/theme-turbo/archive/fc6765cad1d28a418666458f80f6b863090f0a49.tar.gz", + .hash = "122019d9e3f76e9b866131e1dc9cea2b77a12a278a5f253d5b8d6c9ea92c92e1fc06", + }, + .theme_vscode = .{ + .url = "https://github.com/microsoft/vscode/archive/fd2db103459c82eb77d36bb968d3b98137cda5b4.tar.gz", + .hash = "12207d7292daa0811be527c42e4071357bc248328691a426f9836369fa54cc9f7200", + }, + .theme_CRT = .{ + .url = "https://github.com/neurocyte/crt-themes/releases/download/master-4705c3761dd0fbc3b5ae85f1c39f0e9fe8f6ce5a/crt-themes.tar.gz", + .hash = "1220da8b8fe19481e05ca1678a97656532bfbba69b898d215d879e7f2b28fb903783", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + }, +} diff --git a/src/build-dest.zig b/src/build-dest.zig new file mode 100644 index 0000000..6a36efa --- /dev/null +++ b/src/build-dest.zig @@ -0,0 +1,14 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const theme_mod = b.addModule("theme", .{ + .root_source_file = .{ .path = "src/theme.zig" }, + }); + + _ = b.addModule("themes", .{ + .root_source_file = .{ .path = "src/themes.zig" }, + .imports = &.{ + .{ .name = "theme", .module = theme_mod }, + }, + }); +} diff --git a/src/cbor.zig b/src/cbor.zig new file mode 100644 index 0000000..8008533 --- /dev/null +++ b/src/cbor.zig @@ -0,0 +1,914 @@ +const std = @import("std"); + +const eql = std.mem.eql; +const bufPrint = std.fmt.bufPrint; +const fixedBufferStream = std.io.fixedBufferStream; +const maxInt = std.math.maxInt; +const minInt = std.math.minInt; +const json = std.json; +const fba = std.heap.FixedBufferAllocator; + +pub const CborError = error{ + CborIntegerTooLarge, + CborIntegerTooSmall, + CborInvalidType, + CborTooShort, + OutOfMemory, +}; + +pub const CborJsonError = error{ + BufferUnderrun, + CborIntegerTooLarge, + CborIntegerTooSmall, + CborInvalidType, + CborTooShort, + CborUnsupportedType, + NoSpaceLeft, + OutOfMemory, + Overflow, + SyntaxError, + UnexpectedEndOfInput, +}; + +const cbor_magic_null: u8 = 0xf6; +const cbor_magic_true: u8 = 0xf5; +const cbor_magic_false: u8 = 0xf4; + +const cbor_magic_type_array: u8 = 4; +const cbor_magic_type_map: u8 = 5; + +const value_type = enum(u8) { + number, + bytes, + string, + array, + map, + tag, + boolean, + null, + any, + more, + unknown, +}; +pub const number = value_type.number; +pub const bytes = value_type.bytes; +pub const string = value_type.string; +pub const array = value_type.array; +pub const map = value_type.map; +pub const tag = value_type.tag; +pub const boolean = value_type.boolean; +pub const null_ = value_type.null; +pub const any = value_type.any; +pub const more = value_type.more; + +const null_value_buf = [_]u8{0xF6}; +pub const null_value: []const u8 = &null_value_buf; + +pub fn isNull(val: []const u8) bool { + return eql(u8, val, null_value); +} + +fn isAny(value: anytype) bool { + return if (comptime @TypeOf(value) == value_type) value == value_type.any else false; +} + +fn isMore(value: anytype) bool { + return if (comptime @TypeOf(value) == value_type) value == value_type.more else false; +} + +fn write(writer: anytype, value: u8) @TypeOf(writer).Error!void { + _ = try writer.write(&[_]u8{value}); +} + +fn writeTypedVal(writer: anytype, type_: u8, value: u64) @TypeOf(writer).Error!void { + const t: u8 = type_ << 5; + if (value < 24) { + try write(writer, t | @as(u8, @truncate(value))); + } else if (value < 256) { + try write(writer, t | 24); + try write(writer, @as(u8, @truncate(value))); + } else if (value < 65536) { + try write(writer, t | 25); + try write(writer, @as(u8, @truncate(value >> 8))); + try write(writer, @as(u8, @truncate(value))); + } else if (value < 4294967296) { + try write(writer, t | 26); + try write(writer, @as(u8, @truncate(value >> 24))); + try write(writer, @as(u8, @truncate(value >> 16))); + try write(writer, @as(u8, @truncate(value >> 8))); + try write(writer, @as(u8, @truncate(value))); + } else { + try write(writer, t | 27); + try write(writer, @as(u8, @truncate(value >> 56))); + try write(writer, @as(u8, @truncate(value >> 48))); + try write(writer, @as(u8, @truncate(value >> 40))); + try write(writer, @as(u8, @truncate(value >> 32))); + try write(writer, @as(u8, @truncate(value >> 24))); + try write(writer, @as(u8, @truncate(value >> 16))); + try write(writer, @as(u8, @truncate(value >> 8))); + try write(writer, @as(u8, @truncate(value))); + } +} + +pub fn writeArrayHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void { + return writeTypedVal(writer, cbor_magic_type_array, sz); +} + +pub fn writeMapHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void { + return writeTypedVal(writer, cbor_magic_type_map, sz); +} + +pub fn writeArray(writer: anytype, args: anytype) @TypeOf(writer).Error!void { + const args_type_info = @typeInfo(@TypeOf(args)); + if (args_type_info != .Struct) @compileError("expected tuple or struct argument"); + const fields_info = args_type_info.Struct.fields; + try writeArrayHeader(writer, fields_info.len); + inline for (fields_info) |field_info| + try writeValue(writer, @field(args, field_info.name)); +} + +fn writeI64(writer: anytype, value: i64) @TypeOf(writer).Error!void { + return if (value < 0) + writeTypedVal(writer, 1, @as(u64, @bitCast(-(value + 1)))) + else + writeTypedVal(writer, 0, @as(u64, @bitCast(value))); +} + +fn writeU64(writer: anytype, value: u64) @TypeOf(writer).Error!void { + return writeTypedVal(writer, 0, value); +} + +fn writeString(writer: anytype, s: []const u8) @TypeOf(writer).Error!void { + try writeTypedVal(writer, 3, s.len); + _ = try writer.write(s); +} + +fn writeBool(writer: anytype, value: bool) @TypeOf(writer).Error!void { + return write(writer, if (value) cbor_magic_true else cbor_magic_false); +} + +fn writeNull(writer: anytype) @TypeOf(writer).Error!void { + return write(writer, cbor_magic_null); +} + +fn writeErrorset(writer: anytype, err: anyerror) @TypeOf(writer).Error!void { + var buf: [256]u8 = undefined; + const errmsg = try bufPrint(&buf, "error.{s}", .{@errorName(err)}); + return writeString(writer, errmsg); +} + +pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void { + const T = @TypeOf(value); + switch (@typeInfo(T)) { + .Int, .ComptimeInt => return if (T == u64) writeU64(writer, value) else writeI64(writer, @intCast(value)), + .Bool => return writeBool(writer, value), + .Optional => return if (value) |v| writeValue(writer, v) else writeNull(writer), + .ErrorUnion => return if (value) |v| writeValue(writer, v) else |err| writeValue(writer, err), + .ErrorSet => return writeErrorset(writer, value), + .Union => |info| { + if (info.tag_type) |TagType| { + comptime var v = void; + inline for (info.fields) |u_field| { + if (value == @field(TagType, u_field.name)) + v = @field(value, u_field.name); + } + try writeArray(writer, .{ + @typeName(T), + @tagName(@as(TagType, value)), + v, + }); + } else { + try writeArray(writer, .{@typeName(T)}); + } + }, + .Struct => |info| { + if (info.is_tuple) { + if (info.fields.len == 0) return writeNull(writer); + try writeArrayHeader(writer, info.fields.len); + inline for (info.fields) |f| + try writeValue(writer, @field(value, f.name)); + } else { + if (info.fields.len == 0) return writeNull(writer); + try writeMapHeader(writer, info.fields.len); + inline for (info.fields) |f| { + try writeString(writer, f.name); + try writeValue(writer, @field(value, f.name)); + } + } + }, + .Pointer => |ptr_info| switch (ptr_info.size) { + .One => return writeValue(writer, value.*), + .Many, .C => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"), + .Slice => { + if (ptr_info.child == u8) return writeString(writer, value); + if (value.len == 0) return writeNull(writer); + try writeArrayHeader(writer, value.len); + for (value) |elem| + try writeValue(writer, elem); + }, + }, + .Array => |info| { + if (info.child == u8) return writeString(writer, &value); + if (value.len == 0) return writeNull(writer); + try writeArrayHeader(writer, value.len); + for (value) |elem| + try writeValue(writer, elem); + }, + .Vector => |info| { + try writeArrayHeader(writer, info.len); + var i: usize = 0; + while (i < info.len) : (i += 1) { + try writeValue(writer, value[i]); + } + }, + .Null => try writeNull(writer), + else => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"), + } +} + +pub fn fmt(buf: []u8, value: anytype) []const u8 { + var stream = fixedBufferStream(buf); + writeValue(stream.writer(), value) catch unreachable; + return stream.getWritten(); +} + +const CborType = struct { type: u8, minor: u5, major: u3 }; + +pub fn decodeType(iter: *[]const u8) error{CborTooShort}!CborType { + if (iter.len < 1) + return error.CborTooShort; + const type_: u8 = iter.*[0]; + const bits: packed struct { minor: u5, major: u3 } = @bitCast(type_); + iter.* = iter.*[1..]; + return .{ .type = type_, .minor = bits.minor, .major = bits.major }; +} + +fn decodeUIntLengthRecurse(iter: *[]const u8, length: usize, acc: u64) !u64 { + if (iter.len < 1) + return error.CborTooShort; + const v: u8 = iter.*[0]; + iter.* = iter.*[1..]; + var i = acc | v; + if (length == 1) + return i; + i <<= 8; + // return @call(.always_tail, decodeUIntLengthRecurse, .{ iter, length - 1, i }); FIXME: @call(.always_tail) seems broken as of 0.11.0-dev.2964+e9cbdb2cf + return decodeUIntLengthRecurse(iter, length - 1, i); +} + +fn decodeUIntLength(iter: *[]const u8, length: usize) !u64 { + return decodeUIntLengthRecurse(iter, length, 0); +} + +fn decodePInt(iter: *[]const u8, minor: u5) !u64 { + if (minor < 24) return minor; + return switch (minor) { + 24 => decodeUIntLength(iter, 1), // 1 byte + 25 => decodeUIntLength(iter, 2), // 2 byte + 26 => decodeUIntLength(iter, 4), // 4 byte + 27 => decodeUIntLength(iter, 8), // 8 byte + else => error.CborInvalidType, + }; +} + +fn decodeNInt(iter: *[]const u8, minor: u5) CborError!i64 { + return -@as(i64, @intCast(try decodePInt(iter, minor) + 1)); +} + +pub fn decodeMapHeader(iter: *[]const u8) CborError!usize { + const t = try decodeType(iter); + if (t.type == cbor_magic_null) + return 0; + if (t.major != 5) + return error.CborInvalidType; + return decodePInt(iter, t.minor); +} + +pub fn decodeArrayHeader(iter: *[]const u8) CborError!usize { + const t = try decodeType(iter); + if (t.type == cbor_magic_null) + return 0; + if (t.major != 4) + return error.CborInvalidType; + return decodePInt(iter, t.minor); +} + +fn decodeString(iter_: *[]const u8, minor: u5) CborError![]const u8 { + var iter = iter_.*; + const len = try decodePInt(&iter, minor); + if (iter.len < len) + return error.CborTooShort; + const s = iter[0..len]; + iter = iter[len..]; + iter_.* = iter; + return s; +} + +fn decodeBytes(iter: *[]const u8, minor: u5) CborError![]const u8 { + return decodeString(iter, minor); +} + +fn decodeJsonArray(iter_: *[]const u8, minor: u5, arr: *json.Array) CborError!bool { + var iter = iter_.*; + var n = try decodePInt(&iter, minor); + while (n > 0) { + const value = try arr.addOne(); + if (!try matchJsonValue(&iter, value, arr.allocator)) + return false; + n -= 1; + } + iter_.* = iter; + return true; +} + +fn decodeJsonObject(iter_: *[]const u8, minor: u5, obj: *json.ObjectMap) CborError!bool { + var iter = iter_.*; + var n = try decodePInt(&iter, minor); + while (n > 0) { + var key: []u8 = undefined; + var value: json.Value = .null; + + if (!try matchString(&iter, &key)) + return false; + if (!try matchJsonValue(&iter, &value, obj.allocator)) + return false; + + _ = try obj.getOrPutValue(key, value); + n -= 1; + } + iter_.* = iter; + return true; +} + +pub fn matchInt(comptime T: type, iter_: *[]const u8, val: *T) CborError!bool { + var iter = iter_.*; + const t = try decodeType(&iter); + val.* = switch (t.major) { + 0 => blk: { // positive integer + const v = try decodePInt(&iter, t.minor); + if (v > maxInt(T)) + return error.CborIntegerTooLarge; + break :blk @intCast(v); + }, + 1 => blk: { // negative integer + const v = try decodeNInt(&iter, t.minor); + if (v < minInt(T)) + return error.CborIntegerTooSmall; + break :blk @intCast(v); + }, + + else => return false, + }; + iter_.* = iter; + return true; +} + +pub fn matchIntValue(comptime T: type, iter: *[]const u8, val: T) CborError!bool { + var v: T = 0; + return if (try matchInt(T, iter, &v)) v == val else false; +} + +pub fn matchBool(iter_: *[]const u8, v: *bool) CborError!bool { + var iter = iter_.*; + const t = try decodeType(&iter); + if (t.major == 7) { // special + if (t.type == cbor_magic_false) { + v.* = false; + iter_.* = iter; + return true; + } + if (t.type == cbor_magic_true) { + v.* = true; + iter_.* = iter; + return true; + } + } + return false; +} + +fn matchBoolValue(iter: *[]const u8, val: bool) CborError!bool { + var v: bool = false; + return if (try matchBool(iter, &v)) v == val else false; +} + +fn skipString(iter: *[]const u8, minor: u5) CborError!void { + const len = try decodePInt(iter, minor); + if (iter.len < len) + return error.CborTooShort; + iter.* = iter.*[len..]; +} + +fn skipBytes(iter: *[]const u8, minor: u5) CborError!void { + return skipString(iter, minor); +} + +fn skipArray(iter: *[]const u8, minor: u5) CborError!void { + var len = try decodePInt(iter, minor); + while (len > 0) { + try skipValue(iter); + len -= 1; + } +} + +fn skipMap(iter: *[]const u8, minor: u5) CborError!void { + var len = try decodePInt(iter, minor); + len *= 2; + while (len > 0) { + try skipValue(iter); + len -= 1; + } +} + +pub fn skipValue(iter: *[]const u8) CborError!void { + const t = try decodeType(iter); + try skipValueType(iter, t.major, t.minor); +} + +fn skipValueType(iter: *[]const u8, major: u3, minor: u5) CborError!void { + switch (major) { + 0 => { // positive integer + _ = try decodePInt(iter, minor); + }, + 1 => { // negative integer + _ = try decodeNInt(iter, minor); + }, + 2 => { // bytes + try skipBytes(iter, minor); + }, + 3 => { // string + try skipString(iter, minor); + }, + 4 => { // array + try skipArray(iter, minor); + }, + 5 => { // map + try skipMap(iter, minor); + }, + 6 => { // tag + return error.CborInvalidType; + }, + 7 => { // special + return; + }, + } +} + +fn matchType(iter_: *[]const u8, v: *value_type) CborError!bool { + var iter = iter_.*; + const t = try decodeType(&iter); + try skipValueType(&iter, t.major, t.minor); + switch (t.major) { + 0, 1 => v.* = value_type.number, // positive integer or negative integer + 2 => v.* = value_type.bytes, // bytes + 3 => v.* = value_type.string, // string + 4 => v.* = value_type.array, // array + 5 => v.* = value_type.map, // map + 7 => { // special + if (t.type == cbor_magic_null) { + v.* = value_type.null; + } else { + if (t.type == cbor_magic_false or t.type == cbor_magic_true) { + v.* = value_type.boolean; + } else { + return false; + } + } + }, + else => return false, + } + iter_.* = iter; + return true; +} + +fn matchValueType(iter: *[]const u8, t: value_type) CborError!bool { + var v: value_type = value_type.unknown; + return if (try matchType(iter, &v)) (t == value_type.any or t == v) else false; +} + +pub fn matchString(iter_: *[]const u8, val: *[]const u8) CborError!bool { + var iter = iter_.*; + const t = try decodeType(&iter); + val.* = switch (t.major) { + 2 => try decodeBytes(&iter, t.minor), // bytes + 3 => try decodeString(&iter, t.minor), // string + else => return false, + }; + iter_.* = iter; + return true; +} + +fn matchStringValue(iter: *[]const u8, lit: []const u8) CborError!bool { + var val: []const u8 = undefined; + return if (try matchString(iter, &val)) eql(u8, val, lit) else false; +} + +fn matchError(comptime T: type) noreturn { + @compileError("cannot match type '" ++ @typeName(T) ++ "' to cbor stream"); +} + +pub fn matchValue(iter: *[]const u8, value: anytype) CborError!bool { + if (@TypeOf(value) == value_type) + return matchValueType(iter, value); + const T = comptime @TypeOf(value); + if (comptime isExtractor(T)) + return value.extract(iter); + return switch (comptime @typeInfo(T)) { + .Int => return matchIntValue(T, iter, value), + .ComptimeInt => return matchIntValue(i64, iter, value), + .Bool => matchBoolValue(iter, value), + .Pointer => |info| switch (info.size) { + .One => matchValue(iter, value.*), + .Many, .C => matchError(T), + .Slice => if (info.child == u8) matchStringValue(iter, value) else matchArray(iter, value, info), + }, + .Struct => |info| if (info.is_tuple) + matchArray(iter, value, info) + else + matchError(T), + .Array => |info| if (info.child == u8) matchStringValue(iter, &value) else matchArray(iter, value, info), + else => @compileError("cannot match value type '" ++ @typeName(T) ++ "' to cbor stream"), + }; +} + +fn matchJsonValue(iter_: *[]const u8, v: *json.Value, a: std.mem.Allocator) CborError!bool { + var iter = iter_.*; + const t = try decodeType(&iter); + const ret = switch (t.major) { + 0 => ret: { // positive integer + v.* = json.Value{ .integer = @intCast(try decodePInt(&iter, t.minor)) }; + break :ret true; + }, + 1 => ret: { // negative integer + v.* = json.Value{ .integer = try decodeNInt(&iter, t.minor) }; + break :ret true; + }, + 2 => ret: { // bytes + break :ret false; + }, + 3 => ret: { // string + v.* = json.Value{ .string = try decodeString(&iter, t.minor) }; + break :ret true; + }, + 4 => ret: { // array + v.* = json.Value{ .array = json.Array.init(a) }; + break :ret try decodeJsonArray(&iter, t.minor, &v.array); + }, + 5 => ret: { // map + v.* = json.Value{ .object = json.ObjectMap.init(a) }; + break :ret try decodeJsonObject(&iter, t.minor, &v.object); + }, + 6 => ret: { // tag + break :ret false; + }, + 7 => ret: { // special + switch (t.type) { + cbor_magic_false => { + v.* = json.Value{ .bool = false }; + break :ret true; + }, + cbor_magic_true => { + v.* = json.Value{ .bool = true }; + break :ret true; + }, + cbor_magic_null => { + v.* = json.Value{ .null = {} }; + break :ret true; + }, + else => break :ret false, + } + }, + }; + if (ret) iter_.* = iter; + return ret; +} + +fn matchArrayMore(iter_: *[]const u8, n_: u64) CborError!bool { + var iter = iter_.*; + var n = n_; + while (n > 0) { + if (!try matchValue(&iter, value_type.any)) + return false; + n -= 1; + } + iter_.* = iter; + return true; +} + +fn matchArray(iter_: *[]const u8, arr: anytype, info: anytype) CborError!bool { + var iter = iter_.*; + var n = try decodeArrayHeader(&iter); + inline for (info.fields) |f| { + const value = @field(arr, f.name); + if (isMore(value)) + break; + } else if (info.fields.len != n) + return false; + inline for (info.fields) |f| { + const value = @field(arr, f.name); + if (isMore(value)) + return matchArrayMore(&iter, n); + if (n == 0) return false; + const matched = try matchValue(&iter, @field(arr, f.name)); + if (!matched) return false; + n -= 1; + } + if (n == 0) iter_.* = iter; + return n == 0; +} + +fn matchJsonObject(iter_: *[]const u8, obj: *json.ObjectMap) !bool { + var iter = iter_.*; + const t = try decodeType(&iter); + if (t.type == cbor_magic_null) + return true; + if (t.major != 5) + return error.CborInvalidType; + const ret = try decodeJsonObject(&iter, t.minor, obj); + if (ret) iter_.* = iter; + return ret; +} + +pub fn match(buf: []const u8, pattern: anytype) CborError!bool { + var iter: []const u8 = buf; + return matchValue(&iter, pattern); +} + +fn extractError(comptime T: type) noreturn { + @compileError("cannot extract type '" ++ @typeName(T) ++ "' from cbor stream"); +} + +fn hasExtractorTag(info: anytype) bool { + if (info.is_tuple) return false; + inline for (info.decls) |decl| { + if (comptime eql(u8, decl.name, "EXTRACTOR_TAG")) + return true; + } + return false; +} + +fn isExtractor(comptime T: type) bool { + return comptime switch (@typeInfo(T)) { + .Struct => |info| hasExtractorTag(info), + else => false, + }; +} + +const JsonValueExtractor = struct { + dest: *T, + const Self = @This(); + pub const EXTRACTOR_TAG = struct {}; + const T = json.Value; + + pub fn init(dest: *T) Self { + return .{ .dest = dest }; + } + + pub fn extract(self: Self, iter: *[]const u8) CborError!bool { + var null_heap_: [0]u8 = undefined; + var heap = fba.init(&null_heap_); + return matchJsonValue(iter, self.dest, heap.allocator()); + } +}; + +const JsonObjectExtractor = struct { + dest: *T, + const Self = @This(); + pub const EXTRACTOR_TAG = struct {}; + const T = json.ObjectMap; + + pub fn init(dest: *T) Self { + return .{ .dest = dest }; + } + + pub fn extract(self: Self, iter: *[]const u8) CborError!bool { + return matchJsonObject(iter, self.dest); + } +}; + +fn Extractor(comptime T: type) type { + if (T == json.Value) + return JsonValueExtractor; + if (T == json.ObjectMap) + return JsonObjectExtractor; + return struct { + dest: *T, + const Self = @This(); + pub const EXTRACTOR_TAG = struct {}; + + pub fn init(dest: *T) Self { + return .{ .dest = dest }; + } + + pub fn extract(self: Self, iter: *[]const u8) CborError!bool { + switch (comptime @typeInfo(T)) { + .Int, .ComptimeInt => return matchInt(T, iter, self.dest), + .Bool => return matchBool(iter, self.dest), + .Pointer => |ptr_info| switch (ptr_info.size) { + .Slice => { + if (ptr_info.child == u8) return matchString(iter, self.dest) else extractError(T); + }, + else => extractError(T), + }, + else => extractError(T), + } + } + }; +} + +fn ExtractorType(comptime T: type) type { + const T_type_info = @typeInfo(T); + if (T_type_info != .Pointer) @compileError("extract requires a pointer argument"); + return Extractor(T_type_info.Pointer.child); +} + +pub fn extract(dest: anytype) ExtractorType(@TypeOf(dest)) { + comptime { + if (!isExtractor(ExtractorType(@TypeOf(dest)))) + @compileError("isExtractor self check failed for " ++ @typeName(ExtractorType(@TypeOf(dest)))); + } + return ExtractorType(@TypeOf(dest)).init(dest); +} + +const CborExtractor = struct { + dest: *[]const u8, + const Self = @This(); + pub const EXTRACTOR_TAG = struct {}; + + pub fn init(dest: *[]const u8) Self { + return .{ .dest = dest }; + } + + pub fn extract(self: Self, iter: *[]const u8) CborError!bool { + const b = iter.*; + try skipValue(iter); + self.dest.* = b[0..(b.len - iter.len)]; + return true; + } +}; + +pub fn extract_cbor(dest: *[]const u8) CborExtractor { + return CborExtractor.init(dest); +} + +pub fn JsonStream(comptime T: type) type { + return struct { + const Writer = T.Writer; + const JsonWriter = json.WriteStream(Writer, .{ .checked_to_fixed_depth = 256 }); + + fn jsonWriteArray(w: *JsonWriter, iter: *[]const u8, minor: u5) !void { + var count = try decodePInt(iter, minor); + try w.beginArray(); + while (count > 0) : (count -= 1) { + try jsonWriteValue(w, iter); + } + try w.endArray(); + } + + fn jsonWriteMap(w: *JsonWriter, iter: *[]const u8, minor: u5) !void { + var count = try decodePInt(iter, minor); + try w.beginObject(); + while (count > 0) : (count -= 1) { + const t = try decodeType(iter); + if (t.major != 3) return error.CborInvalidType; + try w.objectField(try decodeString(iter, t.minor)); + try jsonWriteValue(w, iter); + } + try w.endObject(); + } + + pub fn jsonWriteValue(w: *JsonWriter, iter: *[]const u8) (CborJsonError || Writer.Error)!void { + const t = try decodeType(iter); + if (t.type == cbor_magic_false) + return w.write(false); + if (t.type == cbor_magic_true) + return w.write(true); + if (t.type == cbor_magic_null) + return w.write(null); + return switch (t.major) { + 0 => w.write(try decodePInt(iter, t.minor)), // positive integer + 1 => w.write(try decodeNInt(iter, t.minor)), // negative integer + 2 => error.CborUnsupportedType, // bytes + 3 => w.write(try decodeString(iter, t.minor)), // string + 4 => jsonWriteArray(w, iter, t.minor), // array + 5 => jsonWriteMap(w, iter, t.minor), // map + else => error.CborInvalidType, + }; + } + }; +} + +pub fn toJson(cbor_buf: []const u8, json_buf: []u8) CborJsonError![]const u8 { + var fbs = fixedBufferStream(json_buf); + var s = json.writeStream(fbs.writer(), .{}); + var iter: []const u8 = cbor_buf; + try JsonStream(@TypeOf(fbs)).jsonWriteValue(&s, &iter); + return fbs.getWritten(); +} + +pub fn toJsonPretty(cbor_buf: []const u8, json_buf: []u8) CborJsonError![]const u8 { + var fbs = fixedBufferStream(json_buf); + var s = json.writeStream(fbs.writer(), .{ .whitespace = .indent_1 }); + var iter: []const u8 = cbor_buf; + try JsonStream(@TypeOf(fbs)).jsonWriteValue(&s, &iter); + return fbs.getWritten(); +} + +fn writeJsonValue(writer: anytype, value: json.Value) !void { + try switch (value) { + .array => |_| unreachable, + .object => |_| unreachable, + .null => writeNull(writer), + .float => |_| error.CborUnsupportedType, + inline else => |v| writeValue(writer, v), + }; +} + +fn jsonScanUntil(writer: anytype, scanner: *json.Scanner, end_token: anytype) CborJsonError!usize { + var partial = try std.BoundedArray(u8, 4096).init(0); + var count: usize = 0; + + var token = try scanner.next(); + while (token != end_token) : (token = try scanner.next()) { + count += 1; + switch (token) { + .object_begin => try writeJsonObject(writer, scanner), + .array_begin => try writeJsonArray(writer, scanner), + + .true => try writeBool(writer, true), + .false => try writeBool(writer, false), + .null => try writeNull(writer), + + .number => |v| { + try partial.appendSlice(v); + try writeJsonValue(writer, json.Value.parseFromNumberSlice(partial.slice())); + try partial.resize(0); + }, + .partial_number => |v| { + try partial.appendSlice(v); + count -= 1; + }, + + .string => |v| { + try partial.appendSlice(v); + try writeString(writer, partial.slice()); + try partial.resize(0); + }, + .partial_string => |v| { + try partial.appendSlice(v); + count -= 1; + }, + .partial_string_escaped_1 => |v| { + try partial.appendSlice(&v); + count -= 1; + }, + .partial_string_escaped_2 => |v| { + try partial.appendSlice(&v); + count -= 1; + }, + .partial_string_escaped_3 => |v| { + try partial.appendSlice(&v); + count -= 1; + }, + .partial_string_escaped_4 => |v| { + try partial.appendSlice(&v); + count -= 1; + }, + + else => return error.SyntaxError, + } + } + return count; +} + +pub const local_heap_size = 4096 * 16; + +fn writeJsonArray(writer_: anytype, scanner: *json.Scanner) CborJsonError!void { + var buf: [local_heap_size]u8 = undefined; + var stream = fixedBufferStream(&buf); + const writer = stream.writer(); + const count = try jsonScanUntil(writer, scanner, .array_end); + try writeArrayHeader(writer_, count); + try writer_.writeAll(stream.getWritten()); +} + +fn writeJsonObject(writer_: anytype, scanner: *json.Scanner) CborJsonError!void { + var buf: [local_heap_size]u8 = undefined; + var stream = fixedBufferStream(&buf); + const writer = stream.writer(); + const count = try jsonScanUntil(writer, scanner, .object_end); + try writeMapHeader(writer_, count / 2); + try writer_.writeAll(stream.getWritten()); +} + +pub fn fromJson(json_buf: []const u8, cbor_buf: []u8) ![]const u8 { + var local_heap_: [local_heap_size]u8 = undefined; + var heap = fba.init(&local_heap_); + var stream = fixedBufferStream(cbor_buf); + const writer = stream.writer(); + + var scanner = json.Scanner.initCompleteInput(heap.allocator(), json_buf); + defer scanner.deinit(); + + _ = try jsonScanUntil(writer, &scanner, .end_of_document); + return stream.getWritten(); +} diff --git a/src/compile.zig b/src/compile.zig new file mode 100644 index 0000000..7cf9ac6 --- /dev/null +++ b/src/compile.zig @@ -0,0 +1,797 @@ +const cbor = @import("cbor"); +const std = @import("std"); +const eql = std.mem.eql; + +const theme = @import("theme"); +const theme_file = @import("theme_files.zig").theme_file; +var theme_files = @import("theme_files.zig").theme_files; + +const Color = theme.Color; +const Style = theme.Style; +const Token = theme.Token; +const Tokens = theme.Tokens; +const TokenMap = std.StringHashMap(Style); +const Scopes = [][]const u8; +const ScopeMap = std.StringHashMap(usize); + +fn get_include_json(file_name: []const u8) []const u8 { + for (&theme_files) |*tf| { + if (eql(u8, std.fs.path.basename(file_name), std.fs.path.basename(tf.file_name))) + return load_cbor(tf); + } + std.debug.print("failed to find include file: {s}\n", .{std.fs.path.basename(file_name)}); + unreachable; +} + +fn load_json(theme_: *theme_file) theme { + const file_name = theme_.file_name; + const cb = load_cbor(theme_); + + const basename = std.fs.path.basename(file_name); + const suffix = std.mem.lastIndexOf(u8, basename, "-color-theme"); + const ext = std.mem.lastIndexOf(u8, basename, ".json"); + const name: []const u8 = if (suffix) |pos| basename[0..pos] else if (ext) |pos| basename[0..pos] else basename; + var description: ?[]const u8 = null; + var theme_type: ?[]const u8 = null; + var iter = cb; + var len = cbor.decodeMapHeader(&iter) catch unreachable; + + while (len > 0) : (len -= 1) { + var field_name: []const u8 = undefined; + if (!(cbor.matchString(&iter, &field_name) catch unreachable)) unreachable; + if (eql(u8, "name", field_name)) { + var description_: []const u8 = undefined; + if (!(cbor.matchString(&iter, &description_) catch unreachable)) unreachable; + description = description_; + } else if (eql(u8, "type", field_name)) { + var theme_type_: []const u8 = undefined; + if (!(cbor.matchString(&iter, &theme_type_) catch unreachable)) unreachable; + theme_type = theme_type_; + } else { + cbor.skipValue(&iter) catch unreachable; + } + } + const type_idx: usize = if (theme_type) |t| if (eql(u8, t, "light")) 1 else 0 else 0; + return .{ + .name = name, + .description = description orelse name, + .type = theme_type orelse "dark", + .tokens = to_token_array(load_token_colors(file_name, cb)), + .editor = derive_style.editor(type_idx, cb), + .editor_cursor = derive_style.editor_cursor(type_idx, cb), + .editor_line_highlight = derive_style.editor_line_highlight(type_idx, cb), + .editor_error = derive_style.editor_error(type_idx, cb), + .editor_match = derive_style.editor_match(type_idx, cb), + .editor_selection = derive_style.editor_selection(type_idx, cb), + .editor_whitespace = derive_style.editor_whitespace(type_idx, cb), + .editor_gutter = derive_style.editor_gutter(type_idx, cb), + .editor_gutter_active = derive_style.editor_gutter_active(type_idx, cb), + .editor_gutter_modified = derive_style.editor_gutter_modified(type_idx, cb), + .editor_gutter_added = derive_style.editor_gutter_added(type_idx, cb), + .editor_gutter_deleted = derive_style.editor_gutter_deleted(type_idx, cb), + .statusbar = derive_style.statusbar(type_idx, cb), + .statusbar_hover = derive_style.statusbar_hover(type_idx, cb), + .scrollbar = derive_style.scrollbar(type_idx, cb), + .scrollbar_hover = derive_style.scrollbar_hover(type_idx, cb), + .scrollbar_active = derive_style.scrollbar_active(type_idx, cb), + .sidebar = derive_style.sidebar(type_idx, cb), + .panel = derive_style.panel(type_idx, cb), + }; +} + +fn load_cbor(theme_: *theme_file) []const u8 { + if (theme_.cbor) |cb| return cb; + const buf = allocator.alloc(u8, theme_.json.len) catch unreachable; + const cb = cbor.fromJson(theme_.json, buf) catch unreachable; + theme_.cbor = cb; + return cb; +} + +fn load_token_colors(file_name: []const u8, cb: []const u8) TokenMap { + var iter = cb; + var len = cbor.decodeMapHeader(&iter) catch unreachable; + var tokens_cb: ?[]const u8 = null; + var include: ?[]const u8 = null; + while (len > 0) : (len -= 1) { + var field_name: []const u8 = undefined; + if (!(cbor.matchString(&iter, &field_name) catch unreachable)) unreachable; + if (eql(u8, "tokenColors", field_name)) { + var value: []const u8 = undefined; + if (!(cbor.matchValue(&iter, cbor.extract_cbor(&value)) catch unreachable)) unreachable; + tokens_cb = value; + } else if (eql(u8, "include", field_name)) { + var value: []const u8 = undefined; + if (!(cbor.matchString(&iter, &value) catch unreachable)) unreachable; + include = value; + } else { + cbor.skipValue(&iter) catch unreachable; + } + } + var tokens = if (include) |inc| load_token_colors(file_name, get_include_json(inc)) else TokenMap.init(allocator); + if (tokens_cb) |cb_| load_token_colors_array(file_name, &tokens, cb_); + return tokens; +} + +fn load_token_colors_array(file_name: []const u8, tokens: *TokenMap, cb: []const u8) void { + var iter = cb; + var len = cbor.decodeArrayHeader(&iter) catch unreachable; + while (len > 0) : (len -= 1) { + var value: []const u8 = undefined; + if (!(cbor.matchValue(&iter, cbor.extract_cbor(&value)) catch unreachable)) unreachable; + load_token_object(file_name, tokens, value); + } +} + +fn load_token_object(file_name: []const u8, tokens: *TokenMap, cb: []const u8) void { + var iter = cb; + var len = cbor.decodeMapHeader(&iter) catch { + iter = cb; + const t = cbor.decodeType(&iter) catch unreachable; + std.debug.panic("unexpected type for token object: {any}", .{t}); + }; + var scopes_cb: ?[]const u8 = null; + var scopes_name: ?[]const u8 = null; + var style: ?Style = null; + while (len > 0) : (len -= 1) { + var field_name: []const u8 = undefined; + if (!(cbor.matchString(&iter, &field_name) catch unreachable)) unreachable; + if (eql(u8, "scope", field_name)) { + var value: []const u8 = undefined; + if (!(cbor.matchValue(&iter, cbor.extract_cbor(&value)) catch unreachable)) unreachable; + scopes_cb = value; + } else if (eql(u8, "settings", field_name)) { + var value: []const u8 = undefined; + if (!(cbor.matchValue(&iter, cbor.extract_cbor(&value)) catch unreachable)) unreachable; + style = load_token_settings_object(file_name, scopes_name, value); + } else if (eql(u8, "name", field_name)) { + var value: []const u8 = undefined; + if (!(cbor.matchString(&iter, &value) catch unreachable)) unreachable; + scopes_name = value; + } else { + cbor.skipValue(&iter) catch unreachable; + } + } + if (style) |sty| if (scopes_cb) |cb_| load_scopes(tokens, sty, cb_); +} + +fn load_token_settings_object(file_name: []const u8, scopes_name: ?[]const u8, cb: []const u8) Style { + var iter = cb; + var len = cbor.decodeMapHeader(&iter) catch unreachable; + var style: Style = .{}; + while (len > 0) : (len -= 1) { + var field_name: []const u8 = undefined; + if (!(cbor.matchString(&iter, &field_name) catch unreachable)) unreachable; + if (eql(u8, "foreground", field_name)) { + var value: []const u8 = undefined; + if (!(cbor.matchString(&iter, &value) catch unreachable)) unreachable; + style.fg = parse_color_value(value); + } else if (eql(u8, "background", field_name)) { + var value: []const u8 = undefined; + if (!(cbor.matchString(&iter, &value) catch unreachable)) unreachable; + style.bg = parse_color_value(value); + } else if (eql(u8, "fontStyle", field_name)) { + var value: []const u8 = undefined; + if (!(cbor.matchString(&iter, &value) catch unreachable)) unreachable; + if (eql(u8, "italic", value)) { + style.fs = .italic; + } else if (eql(u8, "bold", value)) { + style.fs = .bold; + } else if (eql(u8, "underline", value)) { + style.fs = .underline; + } else if (eql(u8, "italic underline", value)) { + style.fs = .italic; + style.fs = .underline; + } else if (eql(u8, "strikethrough", value)) { + style.fs = .strikethrough; + } else if (eql(u8, "", value)) { + style.fs = .normal; + } else { + std.debug.panic("unhandled fontStyle \"{s}\" in {s} -> {s}", .{ value, file_name, scopes_name orelse "unknown" }); + } + } else if (eql(u8, "caret", field_name)) { + cbor.skipValue(&iter) catch unreachable; + } else if (eql(u8, "invisibles", field_name)) { + cbor.skipValue(&iter) catch unreachable; + } else if (eql(u8, "lineHighlight", field_name)) { + cbor.skipValue(&iter) catch unreachable; + } else if (eql(u8, "selection", field_name)) { + cbor.skipValue(&iter) catch unreachable; + } else if (eql(u8, "findHighlight", field_name)) { + cbor.skipValue(&iter) catch unreachable; + } else if (eql(u8, "findHighlightForeground", field_name)) { + cbor.skipValue(&iter) catch unreachable; + } else if (eql(u8, "selectionBorder", field_name)) { + cbor.skipValue(&iter) catch unreachable; + } else if (eql(u8, "activeGuide", field_name)) { + cbor.skipValue(&iter) catch unreachable; + } else if (eql(u8, "bracketsForeground", field_name)) { + cbor.skipValue(&iter) catch unreachable; + } else if (eql(u8, "bracketsOptions", field_name)) { + cbor.skipValue(&iter) catch unreachable; + } else if (eql(u8, "bracketContentsForeground", field_name)) { + cbor.skipValue(&iter) catch unreachable; + } else if (eql(u8, "bracketContentsOptions", field_name)) { + cbor.skipValue(&iter) catch unreachable; + } else if (eql(u8, "tagsOptions", field_name)) { + cbor.skipValue(&iter) catch unreachable; + } else { + std.debug.panic("unhandled style case \"{s}\" in {s} -> {s}", .{ field_name, file_name, scopes_name orelse "unknown" }); + } + } + return style; +} + +fn load_scopes(tokens: *TokenMap, style: Style, cb: []const u8) void { + var iter = cb; + var len = cbor.decodeArrayHeader(&iter) catch { + iter = cb; + var value: []const u8 = undefined; + if (!(cbor.matchString(&iter, &value) catch unreachable)) unreachable; + load_scopes_string(tokens, style, value); + return; + }; + while (len > 0) : (len -= 1) { + var value: []const u8 = undefined; + if (!(cbor.matchString(&iter, &value) catch unreachable)) unreachable; + load_scopes_string(tokens, style, value); + } +} + +fn load_scopes_string(tokens: *TokenMap, style: Style, scopes_: []const u8) void { + var it = std.mem.splitScalar(u8, scopes_, ' '); + while (it.next()) |scope_| { + var it2 = std.mem.splitScalar(u8, scope_, ','); + while (it2.next()) |scope| + tokens.put(scope, style) catch unreachable; + } +} + +fn to_token_array(tokens: TokenMap) []Token { + var iter = tokens.iterator(); + var arr = std.ArrayList(Token).init(allocator); + while (iter.next()) |token| + (arr.addOne() catch unreachable).* = Token{ .id = to_scope_id(token.key_ptr.*), .style = token.value_ptr.* }; + const result = arr.toOwnedSlice() catch unreachable; + std.sort.pdq(Token, result, {}, compare_tokens); + return result; +} + +fn to_scope_id(scope: []const u8) usize { + if (scopes.get(scope)) |id| return id; + (scopes_vec.addOne() catch unreachable).* = scope; + const id = scopes_vec.items.len - 1; + scopes.put(scope, id) catch unreachable; + return id; +} + +fn compare_tokens(_: void, lhs: Token, rhs: Token) bool { + return lhs.id < rhs.id; +} + +fn find_color(name: []const u8, cb: []const u8) ?Color { + var iter = cb; + var len = cbor.decodeMapHeader(&iter) catch unreachable; + var include: ?[]const u8 = null; + var match: ?Color = null; + while (len > 0) : (len -= 1) { + var field_name: []const u8 = undefined; + if (!(cbor.matchString(&iter, &field_name) catch unreachable)) unreachable; + if (eql(u8, "colors", field_name)) { + match = find_in_colors(name, &iter); + } else if (eql(u8, "include", field_name)) { + var value: []const u8 = undefined; + if (!(cbor.matchString(&iter, &value) catch unreachable)) unreachable; + include = value; + } else { + cbor.skipValue(&iter) catch unreachable; + } + } + return if (match) |v| v else if (include) |inc| find_color(name, get_include_json(inc)) else null; +} + +fn find_in_colors(name: []const u8, iter: *[]const u8) ?Color { + var len = cbor.decodeMapHeader(iter) catch unreachable; + while (len > 0) : (len -= 1) { + var field_name: []const u8 = undefined; + var value: []const u8 = undefined; + if (!(cbor.matchString(iter, &field_name) catch unreachable)) unreachable; + if (!(cbor.matchString(iter, &value) catch unreachable)) unreachable; + if (eql(u8, field_name, name)) + return parse_color_value(value); + } + return null; +} + +fn parse_color_value_checked(s: []const u8) !Color { + const parseInt = @import("std").fmt.parseInt; + if (s[0] != '#' or s.len < 7 or s.len > 9) return error.Failed; + var r = try parseInt(u32, s[1..3], 16); + var b = try parseInt(u32, s[3..5], 16); + var g = try parseInt(u32, s[5..7], 16); + if (s.len > 7) { + const a = try parseInt(u8, s[7..], 16); + r = (r * a) / 256; + b = (b * a) / 256; + g = (g * a) / 256; + } + return @truncate((r << 16) + (b << 8) + g); +} + +fn parse_color_value(s: []const u8) Color { + if (4 <= s.len and s.len <= 5) return parse_color_value_4bit(s); + return parse_color_value_checked(s) catch { + std.debug.print("failed to parse color value: {s}\n", .{s}); + unreachable; + }; +} + +fn parse_color_value_4bit(s: []const u8) Color { + const color = "#" ++ s[1..2] ++ s[1..2] ++ s[2..3] ++ s[2..3] ++ s[3..4] ++ s[3..4]; + const expanded = if (s.len > 4) color ++ s[4..5] ++ s[4..5] else color; + return parse_color_value_checked(expanded) catch { + std.debug.print("failed to parse expanded color value: {s} {s}\n", .{ s, expanded }); + unreachable; + }; +} + +fn apply_alpha_value(c: Color, a: u8) Color { + var r: u32 = @as(u8, @truncate((c >> 16))); + var b: u32 = @as(u8, @truncate((c >> 8))); + var g: u32 = @as(u8, @truncate(c)); + r = (r * a) / 256; + b = (b * a) / 256; + g = (g * a) / 256; + return @truncate((r << 16) + (b << 8) + g); +} + +fn apply_alpha_value_opt(c: ?Color, a: u8) ?Color { + return if (c) |v| apply_alpha_value(v, a) else c; +} + +fn apply_alpha_value_fg(sty_: Style, a: u8) Style { + var sty = sty_; + sty.fg = apply_alpha_value_opt(sty.fg, a); + return sty; +} + +fn apply_alpha_value_bg(sty_: Style, a: u8) Style { + var sty = sty_; + sty.bg = apply_alpha_value_opt(sty.bg, a); + return sty; +} + +const derive_style = struct { + fn editor(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("editor.foreground", cb)) |col| col else defaults.@"editor.foreground"(type_idx, cb), + .bg = if (find_color("editor.background", cb)) |col| col else defaults.@"editor.background"(type_idx, cb), + }; + } + + fn editor_cursor(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("editorCursor.background", cb)) |col| + col + else if (find_color("terminalCursor.background", cb)) |col| + col + else + defaults.@"editorCursor.background"(type_idx, cb), + .bg = if (find_color("editorCursor.foreground", cb)) |col| + col + else if (find_color("terminalCursor.foreground", cb)) |col| + col + else + defaults.@"editorCursor.foreground"(type_idx, cb), + }; + } + + fn editor_line_highlight(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("editor.foreground", cb)) |col| col else defaults.@"editor.foreground"(type_idx, cb), + .bg = if (find_color("editor.lineHighlightBackground", cb)) |col| col else defaults.@"editor.lineHighlightBackground"(type_idx, cb), + }; + } + + fn editor_error(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("editorError.foreground", cb)) |col| col else defaults.@"editorError.foreground"(type_idx, cb), + .bg = if (find_color("editorError.background", cb)) |col| col else defaults.@"editorError.background"(type_idx, cb), + }; + } + + fn editor_match(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("editor.foreground", cb)) |col| col else defaults.@"editor.foreground"(type_idx, cb), + .bg = if (find_color("editor.findMatchBackground", cb)) |col| col else defaults.@"editor.findMatchBackground"(type_idx, cb), + }; + } + + fn editor_selection(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("editor.selectionForeground", cb)) |col| col else defaults.@"editor.selectionForeground"(type_idx, cb), + .bg = if (find_color("editor.selectionBackground", cb)) |col| col else defaults.@"editor.selectionBackground"(type_idx, cb), + }; + } + + fn editor_whitespace(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("editorWhitespace.foreground", cb)) |col| col else defaults.@"editorWhitespace.foreground"(type_idx, cb), + .bg = if (find_color("editor.background", cb)) |col| col else defaults.@"editor.background"(type_idx, cb), + }; + } + + fn editor_gutter(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("editorLineNumber.foreground", cb)) |col| col else defaults.@"editorLineNumber.foreground"(type_idx, cb), + .bg = if (find_color("editorGutter.background", cb)) |col| col else defaults.@"editorGutter.background"(type_idx, cb), + }; + } + + fn editor_gutter_active(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("editorLineNumber.activeForeground", cb)) |col| col else defaults.@"editorLineNumber.activeForeground"(type_idx, cb), + .bg = if (find_color("editorGutter.background", cb)) |col| col else defaults.@"editorGutter.background"(type_idx, cb), + }; + } + + fn editor_gutter_modified(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("editorGutter.modifiedBackground", cb)) |col| col else defaults.@"editorGutter.modifiedBackground"(type_idx, cb), + .bg = if (find_color("editorGutter.background", cb)) |col| col else defaults.@"editorGutter.background"(type_idx, cb), + }; + } + + fn editor_gutter_added(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("editorGutter.addedBackground", cb)) |col| col else defaults.@"editorGutter.addedBackground"(type_idx, cb), + .bg = if (find_color("editorGutter.background", cb)) |col| col else defaults.@"editorGutter.background"(type_idx, cb), + }; + } + + fn editor_gutter_deleted(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("editorGutter.deletedBackground", cb)) |col| col else defaults.@"editorGutter.deletedBackground"(type_idx, cb), + .bg = if (find_color("editorGutter.background", cb)) |col| col else defaults.@"editorGutter.background"(type_idx, cb), + }; + } + + fn statusbar(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("statusBar.foreground", cb)) |col| col else defaults.@"statusBar.foreground"(type_idx, cb), + .bg = if (find_color("statusBar.background", cb)) |col| col else defaults.@"statusBar.background"(type_idx, cb), + }; + } + + fn statusbar_hover(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("statusBarItem.hoverForeground", cb)) |col| col else defaults.@"statusBarItem.hoverForeground"(type_idx, cb), + .bg = if (find_color("statusBarItem.hoverBackground", cb)) |col| col else defaults.@"statusBarItem.hoverBackground"(type_idx, cb), + }; + } + + fn scrollbar(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("scrollbarSlider.background", cb)) |col| col else defaults.@"scrollbarSlider.background"(type_idx, cb), + .bg = editor(type_idx, cb).bg, + }; + } + + fn scrollbar_hover(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("scrollbarSlider.hoverBackground", cb)) |col| col else defaults.@"scrollbarSlider.hoverBackground"(type_idx, cb), + .bg = editor(type_idx, cb).bg, + }; + } + + fn scrollbar_active(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("scrollbarSlider.activeBackground", cb)) |col| col else defaults.@"scrollbarSlider.activeBackground"(type_idx, cb), + .bg = editor(type_idx, cb).bg, + }; + } + + fn sidebar(type_idx: usize, cb: []const u8) Style { + return .{ + .fg = if (find_color("sideBar.foreground", cb)) |col| col else defaults.@"sideBar.foreground"(type_idx, cb), + .bg = if (find_color("sideBar.background", cb)) |col| col else defaults.@"sideBar.background"(type_idx, cb), + }; + } + + fn panel(type_idx: usize, cb: []const u8) Style { + const editor_style = editor(type_idx, cb); + return .{ + .fg = editor_style.fg, + .bg = if (find_color("panel.background", cb)) |col| col else editor_style.bg, + }; + } +}; + +const defaults = struct { + cb: []const u8, + + // registerColor('foreground', { dark: '#CCCCCC', light: '#616161', hcDark: '#FFFFFF', hcLight: '#292929' }, nls.localize('foreground', "Overall foreground color. This color is only used if not overridden by a component.")); + fn foreground(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0xCCCCCC, 0x616161 })[type_idx]; + } + + // registerColor('editor.foreground', { light: '#333333', dark: '#BBBBBB', hcDark: Color.white, hcLight: foreground }, nls.localize('editorForeground', "Editor default foreground color.")); + fn @"editor.foreground"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0xBBBBBB, 0x333333 })[type_idx]; + } + + // registerColor('editor.background', { light: '#ffffff', dark: '#1E1E1E', hcDark: Color.black, hcLight: Color.white }, nls.localize('editorBackground', "Editor background color.")); + fn @"editor.background"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0x1E1E1E, 0xffffff })[type_idx]; + } + + // registerColor('editorCursor.foreground', { dark: '#AEAFAD', light: Color.black, hcDark: Color.white, hcLight: '#0F4A85' }, nls.localize('caret', 'Color of the editor cursor.')); + fn @"editorCursor.foreground"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0xAEAFAD, 0x000000 })[type_idx]; + } + + // registerColor('editorCursor.background', null, nls.localize('editorCursorBackground', 'The background color of the editor cursor. Allows customizing the color of a character overlapped by a block cursor.')); + fn @"editorCursor.background"(type_idx: usize, cb: []const u8) ?Color { + return derive_style.editor(type_idx, cb).bg; + } + + // registerColor('editor.lineHighlightBackground', { dark: null, light: null, hcDark: null, hcLight: null }, nls.localize('lineHighlight', 'Background color for the highlight of line at the cursor position.')); + fn @"editor.lineHighlightBackground"(type_idx: usize, cb: []const u8) ?Color { + return derive_style.editor(type_idx, cb).bg; + } + + // registerColor('editorError.foreground', { dark: '#F14C4C', light: '#E51400', hcDark: '#F48771', hcLight: '#B5200D' }, nls.localize('editorError.foreground', 'Foreground color of error squigglies in the editor.')); + fn @"editorError.foreground"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0xF14C4C, 0xE51400 })[type_idx]; + } + + // registerColor('editorError.background', { dark: null, light: null, hcDark: null, hcLight: null }, nls.localize('editorError.background', 'Background color of error text in the editor. The color must not be opaque so as not to hide underlying decorations.'), true); + fn @"editorError.background"(type_idx: usize, cb: []const u8) ?Color { + return derive_style.editor(type_idx, cb).bg; + } + + // registerColor('editor.findMatchBackground', { light: '#A8AC94', dark: '#515C6A', hcDark: null, hcLight: null }, nls.localize('editorFindMatch', "Color of the current search match.")); + fn @"editor.findMatchBackground"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0x515C6A, 0xA8AC94 })[type_idx]; + } + + // registerColor('editor.findMatchHighlightBackground', { light: '#EA5C0055', dark: '#EA5C0055', hcDark: null, hcLight: null }, nls.localize('findMatchHighlight', "Color of the other search matches. The color must not be opaque so as not to hide underlying decorations."), true); + fn @"editor.findMatchHighlightBackground"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0xEA5C00, 0xEA5C00 })[type_idx]; //FIXME: alpha? + } + + // registerColor('editor.selectionForeground', { light: null, dark: null, hcDark: '#000000', hcLight: Color.white }, nls.localize('editorSelectionForeground', "Color of the selected text for high contrast.")); + fn @"editor.selectionForeground"(type_idx: usize, cb: []const u8) ?Color { + return derive_style.editor(type_idx, cb).fg; + } + + // registerColor('editor.selectionBackground', { light: '#ADD6FF', dark: '#264F78', hcDark: '#f3f518', hcLight: '#0F4A85' }, nls.localize('editorSelectionBackground', "Color of the editor selection.")); + fn @"editor.selectionBackground"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0x264F78, 0xADD6FF })[type_idx]; + } + + // registerColor('sideBar.foreground', { dark: null, light: null, hcDark: null, hcLight: null }, localize('sideBarForeground', "Side bar foreground color. The side bar is the container for views like explorer and search.")); + fn @"sideBar.foreground"(type_idx: usize, cb: []const u8) ?Color { + return derive_style.editor(type_idx, cb).fg; + } + + // registerColor('sideBar.background', { dark: '#252526', light: '#F3F3F3', hcDark: '#000000', hcLight: '#FFFFFF' }, localize('sideBarBackground', "Side bar background color. The side bar is the container for views like explorer and search.")); + fn @"sideBar.background"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0x252526, 0xF3F3F3 })[type_idx]; + } + + // registerColor('scrollbar.shadow', { dark: '#000000', light: '#DDDDDD', hcDark: null, hcLight: null }, nls.localize('scrollbarShadow', "Scrollbar shadow to indicate that the view is scrolled.")); + fn @"scrollbar.shadow"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0x000000, 0xDDDDDD })[type_idx]; + } + + // registerColor('scrollbarSlider.background', { dark: Color.fromHex('#797979').transparent(0.4), light: Color.fromHex('#646464').transparent(0.4), hcDark: transparent(contrastBorder, 0.6), hcLight: transparent(contrastBorder, 0.4) }, nls.localize('scrollbarSliderBackground', "Scrollbar slider background color.")); + fn @"scrollbarSlider.background"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0x797979, 0x646464 })[type_idx]; //FIXME: alpha? + } + + // registerColor('scrollbarSlider.hoverBackground', { dark: Color.fromHex('#646464').transparent(0.7), light: Color.fromHex('#646464').transparent(0.7), hcDark: transparent(contrastBorder, 0.8), hcLight: transparent(contrastBorder, 0.8) }, nls.localize('scrollbarSliderHoverBackground', "Scrollbar slider background color when hovering.")); + fn @"scrollbarSlider.hoverBackground"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0x646464, 0x646464 })[type_idx]; //FIXME: alpha? + } + + // registerColor('scrollbarSlider.activeBackground', { dark: Color.fromHex('#BFBFBF').transparent(0.4), light: Color.fromHex('#000000').transparent(0.6), hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('scrollbarSliderActiveBackground', "Scrollbar slider background color when clicked on.")); + fn @"scrollbarSlider.activeBackground"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0xBFBFBF, 0x000000 })[type_idx]; //FIXME: alpha? + } + + // registerColor('statusBar.foreground', { dark: '#FFFFFF', light: '#FFFFFF', hcDark: '#FFFFFF', hcLight: editorForeground }, localize('statusBarForeground', "Status bar foreground color when a workspace or folder is opened. The status bar is shown in the bottom of the window.")); + fn @"statusBar.foreground"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0xFFFFFF, 0xFFFFFF })[type_idx]; + } + + // registerColor('statusBar.background', { dark: '#007ACC', light: '#007ACC', hcDark: null, hcLight: null, }, localize('statusBarBackground', "Status bar background color when a workspace or folder is opened. The status bar is shown in the bottom of the window.")); + fn @"statusBar.background"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0x007ACC, 0x007ACC })[type_idx]; + } + + // registerColor('statusBarItem.hoverForeground', { dark: STATUS_BAR_FOREGROUND, light: STATUS_BAR_FOREGROUND, hcDark: STATUS_BAR_FOREGROUND, hcLight: STATUS_BAR_FOREGROUND }, localize('statusBarItemHoverForeground', "Status bar item foreground color when hovering. The status bar is shown in the bottom of the window.")); + fn @"statusBarItem.hoverForeground"(type_idx: usize, cb: []const u8) ?Color { + return derive_style.statusbar(type_idx, cb).fg; + } + + // registerColor('statusBarItem.hoverBackground', { dark: Color.white.transparent(0.12), light: Color.white.transparent(0.12), hcDark: Color.white.transparent(0.12), hcLight: Color.black.transparent(0.12) }, localize('statusBarItemHoverBackground', "Status bar item background color when hovering. The status bar is shown in the bottom of the window.")); + fn @"statusBarItem.hoverBackground"(type_idx: usize, cb: []const u8) ?Color { + return apply_alpha_value_bg(derive_style.statusbar(type_idx, cb), 256 * 12 / 100).bg; //FIXME: alpha? + } + + // registerColor('editorWhitespace.foreground', { dark: '#e3e4e229', light: '#33333333', hcDark: '#e3e4e229', hcLight: '#CCCCCC' }, nls.localize('editorWhitespaces', 'Color of whitespace characters in the editor.')); + fn @"editorWhitespace.foreground"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ apply_alpha_value(0xe3e4e2, 0x29), apply_alpha_value(0x333333, 0x33) })[type_idx]; //FIXME: alpha? + } + + // registerColor('editorLineNumber.foreground', { dark: '#858585', light: '#237893', hcDark: Color.white, hcLight: '#292929' }, nls.localize('editorLineNumbers', 'Color of editor line numbers.')); + fn @"editorLineNumber.foreground"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0x858585, 0x237893 })[type_idx]; + } + + // registerColor('editorLineNumber.activeForeground', { dark: deprecatedEditorActiveLineNumber, light: deprecatedEditorActiveLineNumber, hcDark: deprecatedEditorActiveLineNumber, hcLight: deprecatedEditorActiveLineNumber }, nls.localize('editorActiveLineNumber', 'Color of editor active line number')); + // registerColor('editorActiveLineNumber.foreground', { dark: '#c6c6c6', light: '#0B216F', hcDark: activeContrastBorder, hcLight: activeContrastBorder }, nls.localize('editorActiveLineNumber', 'Color of editor active line number'), false, nls.localize('deprecatedEditorActiveLineNumber', 'Id is deprecated. Use \'editorLineNumber.activeForeground\' instead.')); + fn @"editorLineNumber.activeForeground"(type_idx: usize, _: []const u8) Color { + return ([2]Color{ 0xc6c6c6, 0x0B216F })[type_idx]; + } + + // registerColor('editorGutter.background', { dark: editorBackground, light: editorBackground, hcDark: editorBackground, hcLight: editorBackground }, nls.localize('editorGutter', 'Background color of the editor gutter. The gutter contains the glyph margins and the line numbers.')); + fn @"editorGutter.background"(type_idx: usize, cb: []const u8) ?Color { + return derive_style.editor(type_idx, cb).bg; + } + + // registerColor('editorGutter.modifiedBackground', { dark: '#1B81A8', light: '#2090D3', hcDark: '#1B81A8', hcLight: '#2090D3'}, nls.localize('editorGutterModifiedBackground', "Editor gutter background color for lines that are modified.")); + fn @"editorGutter.modifiedBackground"(type_idx: usize, _: []const u8) ?Color { + return ([2]Color{ 0x1B81A8, 0x2090D3 })[type_idx]; + } + + // registerColor('editorGutter.addedBackground', { dark: '#487E02', light: '#48985D', hcDark: '#487E02', hcLight: '#48985D' }, nls.localize('editorGutterAddedBackground', "Editor gutter background color for lines that are added.")); + fn @"editorGutter.addedBackground"(type_idx: usize, _: []const u8) ?Color { + return ([2]Color{ 0x487E02, 0x48985D })[type_idx]; + } + + // registerColor('editorGutter.deletedBackground', { dark: editorErrorForeground, light: editorErrorForeground, hcDark: editorErrorForeground, hcLight: editorErrorForeground }, nls.localize('editorGutterDeletedBackground', "Editor gutter background color for lines that are deleted.")); + fn @"editorGutter.deletedBackground"(type_idx: usize, cb: []const u8) ?Color { + return @"editorError.foreground"(type_idx, cb); + } +}; + +const Writer = std.fs.File.Writer; + +fn write_field_string(writer: Writer, name: []const u8, value: []const u8) !void { + _ = try writer.print(" .@\"{s}\" = \"{s}\",\n", .{ name, value }); +} + +fn write_Style(writer: Writer, value: Style) !void { + _ = try writer.print(".{{ ", .{}); + if (value.fg) |fg| _ = try writer.print(".fg = 0x{x},", .{fg}); + if (value.bg) |bg| _ = try writer.print(".bg = 0x{x},", .{bg}); + if (value.fs) |fs| _ = try writer.print(".fs = .{s},", .{switch (fs) { + .normal => "normal", + .bold => "bold", + .italic => "italic", + .underline => "underline", + .strikethrough => "strikethrough", + }}); + _ = try writer.print("}}", .{}); +} + +fn write_field_Style(writer: Writer, name: []const u8, value: Style) !void { + _ = try writer.print(" .@\"{s}\" = ", .{name}); + try write_Style(writer, value); + _ = try writer.print(",\n", .{}); +} + +fn write_field_token_array(writer: Writer, name: []const u8, values: Tokens) !void { + _ = try writer.print(" .@\"{s}\" = &[_]theme.Token{{ \n", .{name}); + for (values) |value| { + _ = try writer.print(" .{{ .id = {d}, .style = ", .{value.id}); + try write_Style(writer, value.style); + _ = try writer.print("}},\n", .{}); + } + _ = try writer.print(" }},\n", .{}); +} + +fn write_field(writer: Writer, name: []const u8, value: anytype) !void { + return if (@TypeOf(value) == Style) + write_field_Style(writer, name, value) + else if (@TypeOf(value) == Tokens) + write_field_token_array(writer, name, value) + else + write_field_string(writer, name, value); +} + +fn write_theme(writer: Writer, item: theme) !void { + _ = try writer.write(" .{\n"); + + inline for (@typeInfo(theme).Struct.fields) |field_info| + try write_field(writer, field_info.name, @field(item, field_info.name)); + + _ = try writer.write(" },\n"); +} + +fn write_all_themes(writer: Writer) !void { + _ = try writer.write("const theme = @import(\"theme\");\n"); + _ = try writer.write("pub const themes = [_]theme{\n"); + for (&theme_files) |*file| { + try write_file("themes", std.fs.path.basename(file.file_name), file.json); + std.debug.print("theme: {s}\n", .{std.fs.path.basename(file.file_name)}); + file.json = try hjson(file.json); + // try write_file("cleaned", std.fs.path.basename(file.file_name), file.json); + const theme_ = load_json(file); + try write_theme(writer, theme_); + } + _ = try writer.write("};\n\n"); + _ = try writer.write("pub const scopes = [_][]const u8{\n"); + for (scopes_vec.items, 0..) |value, i| + _ = try writer.print(" \"{s}\", // {d}\n", .{ value, i }); + _ = try writer.write("};\n"); +} + +var allocator: std.mem.Allocator = undefined; +var scopes: ScopeMap = undefined; +var scopes_vec: std.ArrayList([]const u8) = undefined; + +pub fn main() !void { + var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + allocator = arena; + scopes = ScopeMap.init(allocator); + scopes_vec = std.ArrayList([]const u8).init(allocator); + + const args = try std.process.argsAlloc(arena); + + if (args.len != 2) fatal("wrong number of arguments", .{}); + + const output_file_path = args[1]; + + var output_file = std.fs.cwd().createFile(output_file_path, .{}) catch |err| { + fatal("unable to open '{s}': {s}", .{ output_file_path, @errorName(err) }); + }; + defer output_file.close(); + try write_all_themes(output_file.writer()); + return std.process.cleanExit(); +} + +fn fatal(comptime format: []const u8, args: anytype) noreturn { + std.debug.print(format, args); + std.process.exit(1); +} + +fn hjson(data: []const u8) ![]const u8 { + const cmd = [_][]const u8{ "hjson", "-j" }; // Replace with your shell command + var child = std.ChildProcess.init(&cmd, allocator); + child.stdin_behavior = .Pipe; + child.stdout_behavior = .Pipe; + child.stderr_behavior = .Pipe; + try child.spawn(); + try child.stdin.?.writeAll(data); + child.stdin.?.close(); + child.stdin = null; + var out = std.ArrayList(u8).init(allocator); + var writer = out.writer(); + var buffer: [256]u8 = undefined; + while (true) { + const bytesRead = try child.stdout.?.read(&buffer); + if (bytesRead == 0) break; + try writer.writeAll(buffer[0..bytesRead]); + } + const term = try child.wait(); + switch (term) { + std.ChildProcess.Term.Exited => |code| if (code == 0) return out.toOwnedSlice(), + else => {}, + } + std.debug.panic("Exited with code {any}", .{term}); +} + +fn write_file(dir: []const u8, file_name: []const u8, data: []const u8) !void { + const cwd = std.fs.cwd(); + cwd.makeDir(dir) catch |e| switch (e) { + error.PathAlreadyExists => {}, + else => return e, + }; + var file = try (try cwd.openDir(dir, .{})).createFile(file_name, .{ .truncate = true }); + defer file.close(); + try file.writeAll(data); +} diff --git a/src/default.json b/src/default.json new file mode 100644 index 0000000..7c2e118 --- /dev/null +++ b/src/default.json @@ -0,0 +1,709 @@ +{ + "name": "Default", + "type": "dark", + "colors": { + "actionBar.toggledBackground": "#383a49", + "activityBar.activeBorder": "#0078d4", + "activityBar.background": "#181818", + "activityBar.border": "#2b2b2b", + "activityBar.foreground": "#d7d7d7", + "activityBar.inactiveForeground": "#868686", + "activityBarBadge.background": "#0078d4", + "activityBarBadge.foreground": "#ffffff", + "badge.background": "#616161", + "badge.foreground": "#f8f8f8", + "button.background": "#0078d4", + "button.border": "#ffffff12", + "button.foreground": "#ffffff", + "button.hoverBackground": "#026ec1", + "button.secondaryBackground": "#313131", + "button.secondaryForeground": "#cccccc", + "button.secondaryHoverBackground": "#3c3c3c", + "chat.slashCommandBackground": "#34414b", + "chat.slashCommandForeground": "#40a6ff", + "checkbox.background": "#313131", + "checkbox.border": "#3c3c3c", + "debugToolBar.background": "#181818", + "descriptionForeground": "#9d9d9d", + "dropdown.background": "#313131", + "dropdown.border": "#3c3c3c", + "dropdown.foreground": "#cccccc", + "dropdown.listBackground": "#1f1f1f", + "editor.background": "#1f1f1f", + "editor.findMatchBackground": "#9e6a03", + "editor.foreground": "#cccccc", + "editor.inactiveSelectionBackground": "#3a3d41", + "editor.selectionHighlightBackground": "#add6ff26", + "editorGroup.border": "#ffffff17", + "editorGroupHeader.tabsBackground": "#181818", + "editorGroupHeader.tabsBorder": "#2b2b2b", + "editorGutter.addedBackground": "#2ea043", + "editorGutter.deletedBackground": "#f85149", + "editorGutter.modifiedBackground": "#0078d4", + "editorIndentGuide.activeBackground1": "#707070", + "editorIndentGuide.background1": "#404040", + "editorLineNumber.activeForeground": "#cccccc", + "editorLineNumber.foreground": "#6e7681", + "editorOverviewRuler.border": "#010409", + "editorWidget.background": "#202020", + "errorForeground": "#f85149", + "focusBorder": "#0078d4", + "foreground": "#cccccc", + "icon.foreground": "#cccccc", + "input.background": "#313131", + "input.border": "#3c3c3c", + "input.foreground": "#cccccc", + "input.placeholderForeground": "#818181", + "inputOption.activeBackground": "#2489db82", + "inputOption.activeBorder": "#2488db", + "keybindingLabel.foreground": "#cccccc", + "list.activeSelectionIconForeground": "#ffffff", + "list.dropBackground": "#383b3d", + "menu.background": "#1f1f1f", + "menu.border": "#454545", + "menu.foreground": "#cccccc", + "menu.separatorBackground": "#454545", + "notificationCenterHeader.background": "#1f1f1f", + "notificationCenterHeader.foreground": "#cccccc", + "notifications.background": "#1f1f1f", + "notifications.border": "#2b2b2b", + "notifications.foreground": "#cccccc", + "panel.background": "#181818", + "panel.border": "#2b2b2b", + "panelInput.border": "#2b2b2b", + "panelTitle.activeBorder": "#0078d4", + "panelTitle.activeForeground": "#cccccc", + "panelTitle.inactiveForeground": "#9d9d9d", + "peekViewEditor.background": "#1f1f1f", + "peekViewEditor.matchHighlightBackground": "#bb800966", + "peekViewResult.background": "#1f1f1f", + "peekViewResult.matchHighlightBackground": "#bb800966", + "pickerGroup.border": "#3c3c3c", + "ports.iconRunningProcessForeground": "#369432", + "progressBar.background": "#0078d4", + "quickInput.background": "#222222", + "quickInput.foreground": "#cccccc", + "settings.dropdownBackground": "#313131", + "settings.dropdownBorder": "#3c3c3c", + "settings.headerForeground": "#ffffff", + "settings.modifiedItemIndicator": "#bb800966", + "sideBar.background": "#181818", + "sideBar.border": "#2b2b2b", + "sideBar.foreground": "#cccccc", + "sideBarSectionHeader.background": "#181818", + "sideBarSectionHeader.border": "#2b2b2b", + "sideBarSectionHeader.foreground": "#cccccc", + "sideBarTitle.foreground": "#cccccc", + "statusBar.background": "#181818", + "statusBar.border": "#2b2b2b", + "statusBar.debuggingBackground": "#0078d4", + "statusBar.debuggingForeground": "#ffffff", + "statusBar.focusBorder": "#0078d4", + "statusBar.foreground": "#cccccc", + "statusBar.noFolderBackground": "#1f1f1f", + "statusBarItem.focusBorder": "#0078d4", + "statusBarItem.prominentBackground": "#6e768166", + "statusBarItem.remoteBackground": "#0078d4", + "statusBarItem.remoteForeground": "#ffffff", + "tab.activeBackground": "#1f1f1f", + "tab.activeBorder": "#1f1f1f", + "tab.activeBorderTop": "#0078d4", + "tab.activeForeground": "#ffffff", + "tab.border": "#2b2b2b", + "tab.hoverBackground": "#1f1f1f", + "tab.inactiveBackground": "#181818", + "tab.inactiveForeground": "#9d9d9d", + "tab.lastPinnedBorder": "#cccccc33", + "tab.unfocusedActiveBorder": "#1f1f1f", + "tab.unfocusedActiveBorderTop": "#2b2b2b", + "tab.unfocusedHoverBackground": "#1f1f1f", + "terminal.foreground": "#cccccc", + "terminal.inactiveSelectionBackground": "#3a3d41", + "terminal.tab.activeBorder": "#0078d4", + "textBlockQuote.background": "#2b2b2b", + "textBlockQuote.border": "#616161", + "textCodeBlock.background": "#2b2b2b", + "textLink.activeForeground": "#4daafc", + "textLink.foreground": "#4daafc", + "textPreformat.background": "#3c3c3c", + "textPreformat.foreground": "#d0d0d0", + "textSeparator.foreground": "#21262d", + "titleBar.activeBackground": "#181818", + "titleBar.activeForeground": "#cccccc", + "titleBar.border": "#2b2b2b", + "titleBar.inactiveBackground": "#1f1f1f", + "titleBar.inactiveForeground": "#9d9d9d", + "welcomePage.progress.foreground": "#0078d4", + "welcomePage.tileBackground": "#2b2b2b", + "widget.border": "#313131" + }, + "tokenColors": [ + { + "scope": [ + "meta.embedded", + "source.groovy.embedded", + "string meta.image.inline.markdown", + "variable.legacy.builtin.python" + ], + "settings": { + "foreground": "#D4D4D4" + } + }, + { + "scope": "emphasis", + "settings": { + "fontStyle": "italic" + } + }, + { + "scope": "strong", + "settings": { + "fontStyle": "bold" + } + }, + { + "scope": "header", + "settings": { + "foreground": "#000080" + } + }, + { + "scope": "comment", + "settings": { + "foreground": "#6A9955" + } + }, + { + "scope": "constant.language", + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": [ + "constant.numeric", + "variable.other.enummember", + "keyword.operator.plus.exponent", + "keyword.operator.minus.exponent" + ], + "settings": { + "foreground": "#B5CEA8" + } + }, + { + "scope": "constant.regexp", + "settings": { + "foreground": "#646695" + } + }, + { + "scope": "entity.name.tag", + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": "entity.name.tag.css", + "settings": { + "foreground": "#D7BA7D" + } + }, + { + "scope": "entity.other.attribute-name", + "settings": { + "foreground": "#9CDCFE" + } + }, + { + "scope": [ + "entity.other.attribute-name.class.css", + "entity.other.attribute-name.class.mixin.css", + "entity.other.attribute-name.id.css", + "entity.other.attribute-name.parent-selector.css", + "entity.other.attribute-name.pseudo-class.css", + "entity.other.attribute-name.pseudo-element.css", + "source.css.less entity.other.attribute-name.id", + "entity.other.attribute-name.scss" + ], + "settings": { + "foreground": "#D7BA7D" + } + }, + { + "scope": "invalid", + "settings": { + "foreground": "#F44747" + } + }, + { + "scope": "markup.underline", + "settings": { + "fontStyle": "underline" + } + }, + { + "scope": "markup.bold", + "settings": { + "foreground": "#569CD6", + "fontStyle": "bold" + } + }, + { + "scope": "markup.heading", + "settings": { + "foreground": "#569CD6", + "fontStyle": "bold" + } + }, + { + "scope": "markup.italic", + "settings": { + "fontStyle": "italic" + } + }, + { + "scope": "markup.strikethrough", + "settings": { + "fontStyle": "strikethrough" + } + }, + { + "scope": "markup.inserted", + "settings": { + "foreground": "#B5CEA8" + } + }, + { + "scope": "markup.deleted", + "settings": { + "foreground": "#CE9178" + } + }, + { + "scope": "markup.changed", + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": "punctuation.definition.quote.begin.markdown", + "settings": { + "foreground": "#6A9955" + } + }, + { + "scope": "punctuation.definition.list.begin.markdown", + "settings": { + "foreground": "#6796E6" + } + }, + { + "scope": "markup.inline.raw", + "settings": { + "foreground": "#CE9178" + } + }, + { + "scope": "punctuation.definition.tag", + "settings": { + "foreground": "#808080" + } + }, + { + "scope": [ + "meta.preprocessor", + "entity.name.function.preprocessor" + ], + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": "meta.preprocessor.string", + "settings": { + "foreground": "#CE9178" + } + }, + { + "scope": "meta.preprocessor.numeric", + "settings": { + "foreground": "#B5CEA8" + } + }, + { + "scope": "meta.structure.dictionary.key.python", + "settings": { + "foreground": "#9CDCFE" + } + }, + { + "scope": "meta.diff.header", + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": "storage", + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": "storage.type", + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": [ + "storage.modifier", + "keyword.operator.noexcept" + ], + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": [ + "string", + "meta.embedded.assembly" + ], + "settings": { + "foreground": "#CE9178" + } + }, + { + "scope": "string.tag", + "settings": { + "foreground": "#CE9178" + } + }, + { + "scope": "string.value", + "settings": { + "foreground": "#CE9178" + } + }, + { + "scope": "string.regexp", + "settings": { + "foreground": "#D16969" + } + }, + { + "scope": [ + "punctuation.definition.template-expression.begin", + "punctuation.definition.template-expression.end", + "punctuation.section.embedded" + ], + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": [ + "meta.template.expression" + ], + "settings": { + "foreground": "#D4D4D4" + } + }, + { + "scope": [ + "support.type.vendored.property-name", + "support.type.property-name", + "variable.css", + "variable.scss", + "variable.other.less", + "source.coffee.embedded" + ], + "settings": { + "foreground": "#9CDCFE" + } + }, + { + "scope": "keyword", + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": "keyword.control", + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": "keyword.operator", + "settings": { + "foreground": "#D4D4D4" + } + }, + { + "scope": [ + "keyword.operator.new", + "keyword.operator.expression", + "keyword.operator.cast", + "keyword.operator.sizeof", + "keyword.operator.alignof", + "keyword.operator.typeid", + "keyword.operator.alignas", + "keyword.operator.instanceof", + "keyword.operator.logical.python", + "keyword.operator.wordlike" + ], + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": "keyword.other.unit", + "settings": { + "foreground": "#B5CEA8" + } + }, + { + "scope": [ + "punctuation.section.embedded.begin.php", + "punctuation.section.embedded.end.php" + ], + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": "support.function.git-rebase", + "settings": { + "foreground": "#9CDCFE" + } + }, + { + "scope": "constant.sha.git-rebase", + "settings": { + "foreground": "#B5CEA8" + } + }, + { + "scope": [ + "storage.modifier.import.java", + "variable.language.wildcard.java", + "storage.modifier.package.java" + ], + "settings": { + "foreground": "#D4D4D4" + } + }, + { + "scope": "variable.language", + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": [ + "entity.name.function", + "support.function", + "support.constant.handlebars", + "source.powershell variable.other.member", + "entity.name.operator.custom-literal" + ], + "settings": { + "foreground": "#DCDCAA" + } + }, + { + "scope": [ + "support.class", + "support.type", + "entity.name.type", + "entity.name.namespace", + "entity.other.attribute", + "entity.name.scope-resolution", + "entity.name.class", + "storage.type.numeric.go", + "storage.type.byte.go", + "storage.type.boolean.go", + "storage.type.string.go", + "storage.type.uintptr.go", + "storage.type.error.go", + "storage.type.rune.go", + "storage.type.cs", + "storage.type.generic.cs", + "storage.type.modifier.cs", + "storage.type.variable.cs", + "storage.type.annotation.java", + "storage.type.generic.java", + "storage.type.java", + "storage.type.object.array.java", + "storage.type.primitive.array.java", + "storage.type.primitive.java", + "storage.type.token.java", + "storage.type.groovy", + "storage.type.annotation.groovy", + "storage.type.parameters.groovy", + "storage.type.generic.groovy", + "storage.type.object.array.groovy", + "storage.type.primitive.array.groovy", + "storage.type.primitive.groovy" + ], + "settings": { + "foreground": "#4EC9B0" + } + }, + { + "scope": [ + "meta.type.cast.expr", + "meta.type.new.expr", + "support.constant.math", + "support.constant.dom", + "support.constant.json", + "entity.other.inherited-class" + ], + "settings": { + "foreground": "#4EC9B0" + } + }, + { + "scope": [ + "keyword.control", + "source.cpp keyword.operator.new", + "keyword.operator.delete", + "keyword.other.using", + "keyword.other.directive.using", + "keyword.other.operator", + "entity.name.operator" + ], + "settings": { + "foreground": "#C586C0" + } + }, + { + "scope": [ + "variable", + "meta.definition.variable.name", + "support.variable", + "entity.name.variable", + "constant.other.placeholder" + ], + "settings": { + "foreground": "#9CDCFE" + } + }, + { + "scope": [ + "variable.other.constant", + "variable.other.enummember" + ], + "settings": { + "foreground": "#4FC1FF" + } + }, + { + "scope": [ + "meta.object-literal.key" + ], + "settings": { + "foreground": "#9CDCFE" + } + }, + { + "scope": [ + "support.constant.property-value", + "support.constant.font-name", + "support.constant.media-type", + "support.constant.media", + "constant.other.color.rgb-value", + "constant.other.rgb-value", + "support.constant.color" + ], + "settings": { + "foreground": "#CE9178" + } + }, + { + "scope": [ + "punctuation.definition.group.regexp", + "punctuation.definition.group.assertion.regexp", + "punctuation.definition.character-class.regexp", + "punctuation.character.set.begin.regexp", + "punctuation.character.set.end.regexp", + "keyword.operator.negation.regexp", + "support.other.parenthesis.regexp" + ], + "settings": { + "foreground": "#CE9178" + } + }, + { + "scope": [ + "constant.character.character-class.regexp", + "constant.other.character-class.set.regexp", + "constant.other.character-class.regexp", + "constant.character.set.regexp" + ], + "settings": { + "foreground": "#D16969" + } + }, + { + "scope": [ + "keyword.operator.or.regexp", + "keyword.control.anchor.regexp" + ], + "settings": { + "foreground": "#DCDCAA" + } + }, + { + "scope": "keyword.operator.quantifier.regexp", + "settings": { + "foreground": "#D7BA7D" + } + }, + { + "scope": [ + "constant.character", + "constant.other.option" + ], + "settings": { + "foreground": "#569CD6" + } + }, + { + "scope": "constant.character.escape", + "settings": { + "foreground": "#D7BA7D" + } + }, + { + "scope": "entity.name.label", + "settings": { + "foreground": "#C8C8C8" + } + }, + { + "scope": "token.info-token", + "settings": { + "foreground": "#6796E6" + } + }, + { + "scope": "token.warn-token", + "settings": { + "foreground": "#CD9731" + } + }, + { + "scope": "token.error-token", + "settings": { + "foreground": "#F44747" + } + }, + { + "scope": "token.debug-token", + "settings": { + "foreground": "#B267E6" + } + } + ] +} \ No newline at end of file diff --git a/src/theme.zig b/src/theme.zig new file mode 100644 index 0000000..c88fb41 --- /dev/null +++ b/src/theme.zig @@ -0,0 +1,30 @@ +name: []const u8, +description: []const u8, +type: []const u8, +tokens: Tokens, + +editor: Style, +editor_cursor: Style, +editor_line_highlight: Style, +editor_error: Style, +editor_match: Style, +editor_selection: Style, +editor_whitespace: Style, +editor_gutter: Style, +editor_gutter_active: Style, +editor_gutter_modified: Style, +editor_gutter_added: Style, +editor_gutter_deleted: Style, +statusbar: Style, +statusbar_hover: Style, +scrollbar: Style, +scrollbar_hover: Style, +scrollbar_active: Style, +sidebar: Style, +panel: Style, + +pub const FontStyle = enum { normal, bold, italic, underline, strikethrough }; +pub const Style = struct { fg: ?Color = null, bg: ?Color = null, fs: ?FontStyle = null }; +pub const Color = u24; +pub const Token = struct { id: usize, style: Style }; +pub const Tokens = []const Token; diff --git a/src/theme_files.zig b/src/theme_files.zig new file mode 100644 index 0000000..a4645b8 --- /dev/null +++ b/src/theme_files.zig @@ -0,0 +1,34 @@ +const std = @import("std"); +pub const theme_file = struct { file_name: []const u8, json: []const u8, cbor: ?[]const u8 = null }; + +pub const theme_files = [_]theme_file{ + THEME("default.json"), + THEME("themes/1984-color-theme.json"), + THEME("themes/1984-cyberpunk-color-theme.json"), + THEME("themes/1984-orwell-color-theme.json"), + THEME("theme/cobalt2.json"), + THEME("themes/oldschool-gray-color-theme.json"), + THEME("themes/oldschool-terminal-green.json"), + THEME("themes/Turbo Colors-color-theme.json"), + THEME("extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json"), + THEME("extensions/theme-monokai/themes/monokai-color-theme.json"), + THEME("extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json"), + THEME("extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json"), + THEME("themes/CRT-64-color-theme.json"), + THEME("themes/CRT-Amber-color-theme.json"), + THEME("themes/CRT-Gray-color-theme.json"), + THEME("themes/CRT-Green-color-theme.json"), + THEME("extensions/theme-defaults/themes/dark_vs.json"), + THEME("extensions/theme-defaults/themes/dark_plus.json"), + THEME("extensions/theme-defaults/themes/dark_modern.json"), + THEME("themes/1984-light-color-theme.json"), + THEME("extensions/theme-solarized-light/themes/solarized-light-color-theme.json"), + THEME("themes/CRT-Paper-color-theme.json"), + THEME("extensions/theme-defaults/themes/light_vs.json"), + THEME("extensions/theme-defaults/themes/light_plus.json"), + THEME("extensions/theme-defaults/themes/light_modern.json"), +}; + +fn THEME(comptime file_path: []const u8) theme_file { + return .{ .file_name = std.fs.path.basename(file_path), .json = @embedFile(file_path) }; +} diff --git a/zig b/zig new file mode 100755 index 0000000..262bc7f --- /dev/null +++ b/zig @@ -0,0 +1,48 @@ +#!/bin/bash +set -e + +ARCH=$(uname -m) + +BASEDIR="$(cd "$(dirname "$0")" && pwd)" +ZIGDIR=$BASEDIR/.cache/zig +VERSION=$(< build.zig.version) +ZIGVER="zig-linux-$ARCH-$VERSION" +ZIG=$ZIGDIR/$ZIGVER/zig + +if [ "$1" == "update" ] ; then + curl -L --silent https://ziglang.org/download/index.json | jq -r '.master | .version' > build.zig.version + NEWVERSION=$(< build.zig.version) + + if [ "$VERSION" != "$NEWVERSION" ] ; then + $0 cdb + exec $0 + fi + echo zig version $VERSION is up-to-date + exit 0 +fi + +get_zig() { + ( + mkdir -p "$ZIGDIR" + cd "$ZIGDIR" + TARBALL="https://ziglang.org/builds/$ZIGVER.tar.xz" + + if [ ! -d "$ZIGVER" ] ; then + curl "$TARBALL" | tar -xJ + fi + ) +} +get_zig + +if [ "$1" == "cdb" ] ; then + rm -rf zig-cache + rm -rf zig-bin + rm -rf .cache/cdb + + $ZIG build + + (echo \[ ; cat .cache/cdb/* ; echo {}\]) | perl -0777 -pe 's/,\n\{\}//igs' | jq . | grep -v 'no-default-config' > compile_commands.json + exit 0 +fi + +exec $ZIG "$@"