diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index fa1385d..0000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -* -text diff --git a/README.md b/README.md index 03ba453..eff2ee2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This is my Zig text editor. It is under active development, but usually stable and is my daily driver for most things coding related. -[![Announcement](https://img.youtube.com/vi/iwPg3sIxMGw/maxresdefault.jpg)](https://www.youtube.com/watch?v=iwPg3sIxMGw) +https://github.com/neurocyte/flow/assets/1552770/97aae817-c209-4c08-bc65-0a0bf1f2d4c6 # Requirements - A modern terminal with 24bit color and, ideally, kitty keyboard protocol support. Kitty, @@ -15,28 +15,14 @@ and is my daily driver for most things coding related. # Download / Install -Binary release builds are found here: [neurocyte/flow/releases](https://github.com/neurocyte/flow/releases/latest) - -Fetch and install the latest release to `/usr/local/bin` with the installation helper script: - ```shell curl -fsSL https://flow-control.dev/install | sh ``` +Binary release builds are found here: [neurocyte/flow/releases](https://github.com/neurocyte/flow/releases/latest) + Nightly binary builds are found here: [neurocyte/flow-nightly/releases](https://github.com/neurocyte/flow-nightly/releases/latest) -Install latest nightly build and (optionally) specify the installation destination: - -``` -curl -fsSL https://flow-control.dev/install | sh -s -- --nightly --dest ~/.local/bin -``` - -See all avalable options for the installer script: - -``` -curl -fsSL https://flow-control.dev/install | sh -s -- --help -``` - Or check your favorite local system package repository. [![Packaging status](https://repology.org/badge/vertical-allrepos/flow-control.svg)](https://repology.org/project/flow-control/versions) @@ -45,7 +31,7 @@ Or check your favorite local system package repository. Make sure your system meets the requirements listed above. -Flow builds with zig 0.14.1 at this time. Build with: +Flow builds with zig 0.13 at this time. Build with: ```shell zig build -Doptimize=ReleaseSafe @@ -65,8 +51,6 @@ zig build -Doptimize=ReleaseSafe -Dtarget=aarch64-linux-musl --prefix zig-out/aa When cross-compiling zig will build a binary with generic CPU support. -[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/neurocyte/flow) - # Running Flow Control The binary is: diff --git a/build.zig b/build.zig index c508451..ddc573b 100644 --- a/build.zig +++ b/build.zig @@ -17,13 +17,6 @@ pub fn build(b: *std.Build) void { const test_step = b.step("test", "Run unit tests"); const lint_step = b.step("lint", "Run lints"); - var version = std.ArrayList(u8).init(b.allocator); - defer version.deinit(); - gen_version(b, version.writer()) catch { - version.clearAndFree(); - version.appendSlice("unknown") catch {}; - }; - return (if (release) &build_release else &build_development)( b, run_step, @@ -36,7 +29,6 @@ pub fn build(b: *std.Build) void { use_llvm, pie, gui, - version.items, ); } @@ -52,7 +44,6 @@ fn build_development( use_llvm: ?bool, pie: ?bool, gui: bool, - version: []const u8, ) void { const target = b.standardTargetOptions(.{ .default_target = .{ .abi = if (builtin.os.tag == .linux and !tracy_enabled) .musl else null } }); const optimize = b.standardOptimizeOption(.{}); @@ -72,7 +63,6 @@ fn build_development( use_llvm, pie, gui, - version, ); } @@ -84,11 +74,10 @@ fn build_release( lint_step: *std.Build.Step, tracy_enabled: bool, use_tree_sitter: bool, - _: ?bool, //release builds control strip + strip: ?bool, use_llvm: ?bool, pie: ?bool, _: bool, //gui - version: []const u8, ) void { const targets: []const std.Target.Query = &.{ .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl }, @@ -100,11 +89,13 @@ fn build_release( .{ .cpu_arch = .x86_64, .os_tag = .windows }, .{ .cpu_arch = .aarch64, .os_tag = .windows }, }; - const optimize_release = .ReleaseFast; - const optimize_debug = .ReleaseSafe; + const optimize = .ReleaseFast; + var version = std.ArrayList(u8).init(b.allocator); + defer version.deinit(); + gen_version(b, version.writer()) catch unreachable; const write_file_step = b.addWriteFiles(); - const version_file = write_file_step.add("version", version); + const version_file = write_file_step.add("version", version.items); b.getInstallStep().dependOn(&b.addInstallFile(version_file, "version").step); for (targets) |t| { @@ -113,7 +104,6 @@ fn build_release( const arch = triple.next() orelse unreachable; const os = triple.next() orelse unreachable; const target_path = std.mem.join(b.allocator, "-", &[_][]const u8{ os, arch }) catch unreachable; - const target_path_debug = std.mem.join(b.allocator, "-", &[_][]const u8{ os, arch, "debug" }) catch unreachable; build_exe( b, @@ -122,36 +112,17 @@ fn build_release( test_step, lint_step, target, - optimize_release, + optimize, .{ .dest_dir = .{ .override = .{ .custom = target_path } } }, tracy_enabled, use_tree_sitter, - true, // strip release builds + strip orelse true, use_llvm, pie, false, //gui - version, ); - build_exe( - b, - run_step, - check_step, - test_step, - lint_step, - target, - optimize_debug, - .{ .dest_dir = .{ .override = .{ .custom = target_path_debug } } }, - tracy_enabled, - use_tree_sitter, - false, // don't strip debug builds - use_llvm, - pie, - false, //gui - version, - ); - - if (t.os_tag == .windows) { + if (t.os_tag == .windows) build_exe( b, run_step, @@ -159,35 +130,15 @@ fn build_release( test_step, lint_step, target, - optimize_release, + optimize, .{ .dest_dir = .{ .override = .{ .custom = target_path } } }, tracy_enabled, use_tree_sitter, - true, // strip release builds + strip orelse true, use_llvm, pie, true, //gui - version, ); - - build_exe( - b, - run_step, - check_step, - test_step, - lint_step, - target, - optimize_debug, - .{ .dest_dir = .{ .override = .{ .custom = target_path_debug } } }, - tracy_enabled, - use_tree_sitter, - false, // don't strip debug builds - use_llvm, - pie, - true, //gui - version, - ); - } } } @@ -206,7 +157,6 @@ pub fn build_exe( use_llvm: ?bool, pie: ?bool, gui: bool, - version: []const u8, ) void { const options = b.addOptions(); options.addOption(bool, "enable_tracy", tracy_enabled); @@ -233,8 +183,7 @@ pub fn build_exe( }; const wf = b.addWriteFiles(); - const version_file = wf.add("version", version); - const version_info_file = wf.add("version_info", version_info.items); + const version_info_file = wf.add("version", version_info.items); const vaxis_dep = b.dependency("vaxis", .{ .target = target, @@ -264,12 +213,7 @@ pub fn build_exe( }); const thespian_mod = thespian_dep.module("thespian"); - - const cbor_dep = thespian_dep.builder.dependency("cbor", .{ - .target = target, - .optimize = optimize_deps, - }); - const cbor_mod = cbor_dep.module("cbor"); + const cbor_mod = thespian_dep.module("cbor"); const tracy_dep = if (tracy_enabled) thespian_dep.builder.dependency("tracy", .{ .target = target, @@ -344,16 +288,12 @@ pub fn build_exe( .root_source_file = b.path("src/color.zig"), }); - const bin_path_mod = b.createModule(.{ - .root_source_file = b.path("src/bin_path.zig"), - }); - const Buffer_mod = b.createModule(.{ .root_source_file = b.path("src/buffer/Buffer.zig"), .imports = &.{ .{ .name = "cbor", .module = cbor_mod }, .{ .name = "thespian", .module = thespian_mod }, - .{ .name = "LetterCasing", .module = zg_dep.module("LetterCasing") }, + .{ .name = "CaseData", .module = zg_dep.module("CaseData") }, }, }); @@ -463,23 +403,12 @@ pub fn build_exe( }, }); - const git_mod = b.createModule(.{ - .root_source_file = b.path("src/git.zig"), - .imports = &.{ - .{ .name = "thespian", .module = thespian_mod }, - .{ .name = "cbor", .module = cbor_mod }, - .{ .name = "shell", .module = shell_mod }, - .{ .name = "bin_path", .module = bin_path_mod }, - }, - }); - const ripgrep_mod = b.createModule(.{ .root_source_file = b.path("src/ripgrep.zig"), .imports = &.{ .{ .name = "thespian", .module = thespian_mod }, .{ .name = "cbor", .module = cbor_mod }, .{ .name = "log", .module = log_mod }, - .{ .name = "bin_path", .module = bin_path_mod }, }, }); @@ -501,7 +430,6 @@ pub fn build_exe( .{ .name = "syntax", .module = syntax_mod }, .{ .name = "dizzy", .module = dizzy_dep.module("dizzy") }, .{ .name = "fuzzig", .module = fuzzig_dep.module("fuzzig") }, - .{ .name = "git", .module = git_mod }, }, }); @@ -541,7 +469,6 @@ pub fn build_exe( .{ .name = "Buffer", .module = Buffer_mod }, .{ .name = "keybind", .module = keybind_mod }, .{ .name = "shell", .module = shell_mod }, - .{ .name = "git", .module = git_mod }, .{ .name = "ripgrep", .module = ripgrep_mod }, .{ .name = "theme", .module = themes_dep.module("theme") }, .{ .name = "themes", .module = themes_dep.module("themes") }, @@ -584,8 +511,6 @@ pub fn build_exe( exe.root_module.addImport("input", input_mod); exe.root_module.addImport("syntax", syntax_mod); exe.root_module.addImport("color", color_mod); - exe.root_module.addImport("bin_path", bin_path_mod); - exe.root_module.addImport("version", b.createModule(.{ .root_source_file = version_file })); exe.root_module.addImport("version_info", b.createModule(.{ .root_source_file = version_info_file })); if (target.result.os.tag == .windows) { @@ -628,8 +553,6 @@ pub fn build_exe( check_exe.root_module.addImport("input", input_mod); check_exe.root_module.addImport("syntax", syntax_mod); check_exe.root_module.addImport("color", color_mod); - check_exe.root_module.addImport("bin_path", bin_path_mod); - check_exe.root_module.addImport("version", b.createModule(.{ .root_source_file = version_file })); check_exe.root_module.addImport("version_info", b.createModule(.{ .root_source_file = version_info_file })); check_step.dependOn(&check_exe.step); @@ -660,7 +583,7 @@ pub fn build_exe( }); lint_step.dependOn(&lints.step); - b.default_step.dependOn(lint_step); + // b.default_step.dependOn(lint_step); } fn gen_version_info( diff --git a/build.zig.version b/build.zig.version index 930e300..54d1a4f 100644 --- a/build.zig.version +++ b/build.zig.version @@ -1 +1 @@ -0.14.1 +0.13.0 diff --git a/build.zig.zon b/build.zig.zon index a9fe7eb..ed19001 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,42 +1,41 @@ .{ - .name = .flow, + .name = "flow", .version = "0.2.0", - .minimum_zig_version = "0.14.1", - .fingerprint = 0x52c0d670590aa80f, + .minimum_zig_version = "0.13.0", .dependencies = .{ .syntax = .{ .path = "src/syntax" }, .flags = .{ - .url = "https://github.com/n0s4/flags/archive/372501d1576b5723829bcba98e41361132c7b618.tar.gz", - .hash = "flags-0.8.0-AAAAAJV0AACuGBBnpUnHqZzAhoGTp4ibFROBQQQZGRqx", + .url = "https://github.com/n0s4/flags/archive/b3905aa990719ff567f1c5a2f89e6dd3292d8533.tar.gz", + .hash = "1220930a42f8da3fb7f723e3ad3f6dcc6db76327dd8d26274566423192d53e91b2bb", }, .dizzy = .{ .url = "https://github.com/neurocyte/dizzy/archive/455d18369cbb2a0458ba70be919cd378338d695e.tar.gz", - .hash = "dizzy-1.0.0-AAAAAM1wAAAiDbx_6RwcVEOBk8p2XOu8t9WPNc3K7kBK", + .hash = "1220220dbc7fe91c1c54438193ca765cebbcb7d58f35cdcaee404a9d2245a42a4362", }, .thespian = .{ - .url = "https://github.com/neurocyte/thespian/archive/829a8d33e92988a51a8c51d204ec766a28c7903d.tar.gz", - .hash = "thespian-0.0.1-owFOjs0TBgAAed7EtHDPtpB7NBn-riNjb7Rkc7a_Voow", + .url = "https://github.com/neurocyte/thespian/archive/9ca04ddfc715e0f7d29d3f6b39269ad9bf174230.tar.gz", + .hash = "1220b05b5949454bf155a802d5034c060431b8bf59f9d4d2d5241397e9fd201d78d9", }, .themes = .{ - .url = "https://github.com/neurocyte/flow-themes/releases/download/master-952f9f630ea9544088fd30293666ee0650b7a690/flow-themes.tar.gz", - .hash = "N-V-__8AAJiAIgDMVIi8CRb_xko9_qVQ-UiQzd5FTBBr0aPa", + .url = "https://github.com/neurocyte/flow-themes/releases/download/master-59bf204551bcb238faddd06779063570e7e6d431/flow-themes.tar.gz", + .hash = "12209a213a392ea80ea5c7dde125e7161bb0278ac4b99db9df2b7af783710bcb09f7", }, .fuzzig = .{ - .url = "https://github.com/fjebaker/fuzzig/archive/44c04733c7c0fee3db83672aaaaf4ed03e943156.tar.gz", - .hash = "fuzzig-0.1.1-AAAAALNIAQBmbHr-MPalGuR393Vem2pTQXI7_LXeNJgX", + .url = "https://github.com/fjebaker/fuzzig/archive/0fd156d5097365151e85a85eef9d8cf0eebe7b00.tar.gz", + .hash = "122019f077d09686b1ec47928ca2b4bf264422f3a27afc5b49dafb0129a4ceca0d01", }, .vaxis = .{ - .url = "https://github.com/neurocyte/libvaxis/archive/6137cb4c44a7350996f0946a069739e5075d1f23.tar.gz", - .hash = "vaxis-0.1.0-BWNV_HwOCQCw5wTV63hQGSc1QJzsNcytH6sGf1GBc0hP", + .url = "https://github.com/neurocyte/libvaxis/archive/e518e139417a9773f59624961b02e05b8fffff35.tar.gz", + .hash = "122045fec2cedf3f68c20f961c42f3ca1e901238aec5a0c7b3b632cd21ce3b621f76", }, .zeit = .{ .url = "https://github.com/rockorager/zeit/archive/8fd203f85f597f16e0a525c1f1ca1e0bffded809.tar.gz", - .hash = "zeit-0.0.0-AAAAACVbAgAiIzg1rccZU1qOfO_dKQKme7-37xmEQcqc", + .hash = "122022233835adc719535a8e7cefdd2902a67bbfb7ef198441ca9ce89c0593f488c2", }, .win32 = .{ - .url = "https://github.com/marlersoft/zigwin32/archive/e8739b32a33ce48a3286aba31918b26a9dfc6ef0.tar.gz", - .hash = "zigwin32-25.0.28-preview-AAAAAEEl_AMhnKSs-lgEyjmUX5JVTpNQewd8A2Bbekwc", + .url = "https://github.com/marlersoft/zigwin32/archive/259b6f353a48968d7e3171573db4fd898b046188.tar.gz", + .hash = "1220925614447b54ccc9894bbba8b202c6a8b750267890edab7732064867e46f3217", .lazy = true, }, }, diff --git a/contrib/test_race.sh b/contrib/test_race.sh deleted file mode 100755 index aa43f1e..0000000 --- a/contrib/test_race.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -set -e - -if [ "$1" == "--build" ]; then - shift - echo "building..." - zig build -freference-trace --prominent-compile-errors -fi - -for i in {1..60}; do - echo "running $i ..." - # flow --exec quit "$@" || exit 1 - strace -f -t \ - -e trace=open,openat,close,socket,pipe,pipe2,dup,dup2,dup3,fcntl,accept,accept4,epoll_create,epoll_create1,eventfd,timerfd_create,signalfd,execve,fork \ - -o trace.log \ - flow --exec quit --trace-level 9 "$@" -done diff --git a/src/EventHandler.zig b/src/EventHandler.zig index 879ba12..37b6bf1 100644 --- a/src/EventHandler.zig +++ b/src/EventHandler.zig @@ -16,7 +16,7 @@ pub const VTable = struct { pub fn to_owned(pimpl: anytype) Self { const impl = @typeInfo(@TypeOf(pimpl)); - const child: type = impl.pointer.child; + const child: type = impl.Pointer.child; return .{ .ptr = pimpl, .vtable = comptime &.{ @@ -56,7 +56,7 @@ var none = {}; pub fn to_unowned(pimpl: anytype) Self { const impl = @typeInfo(@TypeOf(pimpl)); - const child: type = impl.pointer.child; + const child: type = impl.Pointer.child; return .{ .ptr = pimpl, .vtable = comptime &.{ @@ -79,7 +79,7 @@ pub fn to_unowned(pimpl: anytype) Self { pub fn bind(pimpl: anytype, comptime f: *const fn (ctx: @TypeOf(pimpl), from: tp.pid_ref, m: tp.message) tp.result) Self { const impl = @typeInfo(@TypeOf(pimpl)); - const child: type = impl.pointer.child; + const child: type = impl.Pointer.child; return .{ .ptr = pimpl, .vtable = comptime &.{ diff --git a/src/LSP.zig b/src/LSP.zig index b224f4f..b14c5a5 100644 --- a/src/LSP.zig +++ b/src/LSP.zig @@ -15,7 +15,7 @@ const debug_lsp = true; const OutOfMemoryError = error{OutOfMemory}; const SendError = error{SendFailed}; -const SpawnError = error{ThespianSpawnFailed}; +const CallError = tp.CallError; pub fn open(allocator: std.mem.Allocator, project: []const u8, cmd: tp.message) (error{ ThespianSpawnFailed, InvalidLspCommand } || cbor.Error)!Self { return .{ .allocator = allocator, .pid = try Process.create(allocator, project, cmd) }; @@ -31,17 +31,12 @@ pub fn term(self: Self) void { self.pid.deinit(); } -pub fn send_request( - self: Self, - allocator: std.mem.Allocator, - method: []const u8, - m: anytype, - ctx: anytype, -) (OutOfMemoryError || SpawnError)!void { +pub fn send_request(self: Self, allocator: std.mem.Allocator, method: []const u8, m: anytype) CallError!tp.message { var cb = std.ArrayList(u8).init(self.allocator); defer cb.deinit(); try cbor.writeValue(cb.writer(), m); - return RequestContext(@TypeOf(ctx)).send(allocator, self.pid.ref(), ctx, tp.message.fmt(.{ "REQ", method, cb.items })); + const request_timeout: u64 = @intCast(std.time.ns_per_s * tp.env.get().num("lsp-request-timeout")); + return self.pid.call(allocator, request_timeout, .{ "REQ", method, cb.items }); } pub fn send_notification(self: Self, method: []const u8, m: anytype) (OutOfMemoryError || SendError)!void { @@ -59,57 +54,6 @@ pub fn close(self: *Self) void { self.deinit(); } -fn RequestContext(T: type) type { - return struct { - receiver: ReceiverT, - ctx: T, - to: tp.pid, - request: tp.message, - response: ?tp.message, - a: std.mem.Allocator, - - const Self = @This(); - const ReceiverT = tp.Receiver(*@This()); - - fn send(a: std.mem.Allocator, to: tp.pid_ref, ctx: T, request: tp.message) (OutOfMemoryError || SpawnError)!void { - const self = try a.create(@This()); - self.* = .{ - .receiver = undefined, - .ctx = if (@hasDecl(T, "clone")) ctx.clone() else ctx, - .to = to.clone(), - .request = try request.clone(std.heap.c_allocator), - .response = null, - .a = a, - }; - self.receiver = ReceiverT.init(receive_, self); - const proc = try tp.spawn_link(a, self, start, @typeName(@This())); - defer proc.deinit(); - } - - fn deinit(self: *@This()) void { - self.ctx.deinit(); - std.heap.c_allocator.free(self.request.buf); - self.to.deinit(); - self.a.destroy(self); - } - - fn start(self: *@This()) tp.result { - _ = tp.set_trap(true); - if (@hasDecl(T, "link")) try self.ctx.link(); - errdefer self.deinit(); - try self.to.link(); - try self.to.send_raw(self.request); - tp.receive(&self.receiver); - } - - fn receive_(self: *@This(), _: tp.pid_ref, m: tp.message) tp.result { - defer self.deinit(); - self.ctx.receive(m) catch |e| return tp.exit_error(e, @errorReturnTrace()); - return tp.exit_normal(); - } - }; -} - const Process = struct { allocator: std.mem.Allocator, cmd: tp.message, @@ -123,10 +67,6 @@ const Process = struct { log_file: ?std.fs.File = null, next_id: i32 = 0, requests: std.StringHashMap(tp.pid), - state: enum { init, running } = .init, - init_queue: ?std.ArrayListUnmanaged(struct { tp.pid, []const u8, []const u8, InitQueueType }) = null, - - const InitQueueType = enum { request, notify }; const Receiver = tp.Receiver(*Process); @@ -163,7 +103,6 @@ const Process = struct { } fn deinit(self: *Process) void { - self.free_init_queue(); var i = self.requests.iterator(); while (i.next()) |req| { self.allocator.free(req.key_ptr.*); @@ -227,37 +166,24 @@ const Process = struct { UnsupportedType, ExitNormal, ExitUnexpected, - InvalidMapType, }); fn receive_safe(self: *Process, from: tp.pid_ref, m: tp.message) Error!void { const frame = tracy.initZone(@src(), .{ .name = module_name }); defer frame.deinit(); errdefer self.deinit(); - var method: []const u8 = ""; - var bytes: []const u8 = ""; - var err: []const u8 = ""; + var method: []u8 = ""; + var bytes: []u8 = ""; + var err: []u8 = ""; var code: u32 = 0; var cbor_id: []const u8 = ""; - if (try cbor.match(m.buf, .{ "REQ", "initialize", tp.extract(&bytes) })) { - try self.send_request(from, "initialize", bytes); - } else if (try cbor.match(m.buf, .{ "REQ", tp.extract(&method), tp.extract(&bytes) })) { - switch (self.state) { - .init => try self.append_init_queue(from, method, bytes, .request), //queue requests - .running => try self.send_request(from, method, bytes), - } + if (try cbor.match(m.buf, .{ "REQ", tp.extract(&method), tp.extract(&bytes) })) { + try self.send_request(from, method, bytes); } else if (try cbor.match(m.buf, .{ "RSP", tp.extract_cbor(&cbor_id), tp.extract_cbor(&bytes) })) { try self.send_response(cbor_id, bytes); - } else if (try cbor.match(m.buf, .{ "NTFY", "initialized", tp.extract(&bytes) })) { - self.state = .running; - try self.send_notification("initialized", bytes); - try self.replay_init_queue(); } else if (try cbor.match(m.buf, .{ "NTFY", tp.extract(&method), tp.extract(&bytes) })) { - switch (self.state) { - .init => try self.append_init_queue(from, method, bytes, .notify), //queue requests - .running => try self.send_notification(method, bytes), - } + try self.send_notification(method, bytes); } else if (try cbor.match(m.buf, .{"close"})) { self.write_log("### LSP close ###\n", .{}); try self.close(); @@ -281,43 +207,6 @@ const Process = struct { } } - fn append_init_queue(self: *Process, from: tp.pid_ref, method: []const u8, bytes: []const u8, type_: InitQueueType) !void { - const queue = if (self.init_queue) |*queue| queue else blk: { - self.init_queue = .empty; - break :blk &self.init_queue.?; - }; - const p = try queue.addOne(self.allocator); - p.* = .{ - from.clone(), - try self.allocator.dupe(u8, method), - try self.allocator.dupe(u8, bytes), - type_, - }; - } - - fn replay_init_queue(self: *Process) !void { - defer self.free_init_queue(); - if (self.init_queue) |*queue| { - for (queue.items) |*p| - switch (p[3]) { - .request => try self.send_request(p[0].ref(), p[1], p[2]), - .notify => try self.send_notification(p[1], p[2]), - }; - } - } - - fn free_init_queue(self: *Process) void { - if (self.init_queue) |*queue| { - for (queue.items) |*p| { - p[0].deinit(); - self.allocator.free(p[1]); - self.allocator.free(p[2]); - } - queue.deinit(self.allocator); - } - self.init_queue = null; - } - fn receive_lsp_message(self: *Process, cb: []const u8) Error!void { var iter = cb; @@ -370,16 +259,12 @@ const Process = struct { } } - fn handle_output(self: *Process, bytes: []const u8) Error!void { + fn handle_output(self: *Process, bytes: []u8) Error!void { try self.recv_buf.appendSlice(bytes); self.write_log("### RECV:\n{s}\n###\n", .{bytes}); self.frame_message_recv() catch |e| { self.write_log("### RECV error: {any}\n", .{e}); - switch (e) { - // ignore invalid LSP messages that are at least framed correctly - error.InvalidMessage, error.InvalidMessageField => {}, - else => return e, - } + return e; }; } diff --git a/src/Project.zig b/src/Project.zig index 489c328..83d5517 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -7,44 +7,27 @@ const dizzy = @import("dizzy"); const Buffer = @import("Buffer"); const fuzzig = @import("fuzzig"); const tracy = @import("tracy"); -const git = @import("git"); const builtin = @import("builtin"); const LSP = @import("LSP.zig"); -const walk_tree = @import("walk_tree.zig"); allocator: std.mem.Allocator, name: []const u8, -files: std.ArrayListUnmanaged(File) = .empty, -pending: std.ArrayListUnmanaged(File) = .empty, +files: std.ArrayList(File), +pending: std.ArrayList(File), longest_file_path: usize = 0, open_time: i64, language_servers: std.StringHashMap(LSP), file_language_server: std.StringHashMap(LSP), tasks: std.ArrayList(Task), persistent: bool = false, -logger: log.Logger, -logger_lsp: log.Logger, -logger_git: log.Logger, - -workspace: ?[]const u8 = null, -branch: ?[]const u8 = null, - -walker: ?tp.pid = null, - -// async task states -state: struct { - walk_tree: State = .none, - workspace_path: State = .none, - current_branch: State = .none, - workspace_files: State = .none, -} = .{}, const Self = @This(); const OutOfMemoryError = error{OutOfMemory}; +const CallError = tp.CallError; const SpawnError = (OutOfMemoryError || error{ThespianSpawnFailed}); -pub const InvalidMessageError = error{ InvalidMessage, InvalidMessageField, InvalidTargetURI, InvalidMapType }; +pub const InvalidMessageError = error{ InvalidMessage, InvalidMessageField, InvalidTargetURI }; pub const StartLspError = (error{ ThespianSpawnFailed, Timeout, InvalidLspCommand } || LspError || OutOfMemoryError || cbor.Error); pub const LspError = (error{ NoLsp, LspFailed } || OutOfMemoryError); pub const ClientError = (error{ClientFailed} || OutOfMemoryError); @@ -67,26 +50,20 @@ const Task = struct { mtime: i64, }; -const State = enum { none, running, done, failed }; - pub fn init(allocator: std.mem.Allocator, name: []const u8) OutOfMemoryError!Self { return .{ .allocator = allocator, .name = try allocator.dupe(u8, name), + .files = std.ArrayList(File).init(allocator), + .pending = std.ArrayList(File).init(allocator), .open_time = std.time.milliTimestamp(), .language_servers = std.StringHashMap(LSP).init(allocator), .file_language_server = std.StringHashMap(LSP).init(allocator), .tasks = std.ArrayList(Task).init(allocator), - .logger = log.logger("project"), - .logger_lsp = log.logger("lsp"), - .logger_git = log.logger("git"), }; } pub fn deinit(self: *Self) void { - if (self.walker) |pid| pid.send(.{"stop"}) catch {}; - if (self.workspace) |p| self.allocator.free(p); - if (self.branch) |p| self.allocator.free(p); var i_ = self.file_language_server.iterator(); while (i_.next()) |p| { self.allocator.free(p.key_ptr.*); @@ -97,13 +74,9 @@ pub fn deinit(self: *Self) void { p.value_ptr.*.term(); } for (self.files.items) |file| self.allocator.free(file.path); - self.files.deinit(self.allocator); - self.pending.deinit(self.allocator); + self.files.deinit(); for (self.tasks.items) |task| self.allocator.free(task.command); self.tasks.deinit(); - self.logger_lsp.deinit(); - self.logger_git.deinit(); - self.logger.deinit(); self.allocator.free(self.name); } @@ -159,7 +132,7 @@ pub fn restore_state(self: *Self, data: []const u8) !void { var iter: []const u8 = data; _ = cbor.matchValue(&iter, tp.string) catch {}; _ = cbor.decodeArrayHeader(&iter) catch |e| switch (e) { - error.InvalidArrayType => return self.restore_state_v0(data), + error.InvalidType => return self.restore_state_v0(data), else => return tp.trace(tp.channel.debug, .{ "restore_state", "unknown format", data }), }; self.persistent = true; @@ -219,18 +192,7 @@ pub fn restore_state_v1(self: *Self, data: []const u8) !void { } } -pub fn restore_state_v0(self: *Self, data: []const u8) error{ - OutOfMemory, - IntegerTooLarge, - IntegerTooSmall, - InvalidType, - TooShort, - InvalidFloatType, - InvalidArrayType, - InvalidPIntType, - JsonIncompatibleType, - NotAnObject, -}!void { +pub fn restore_state_v0(self: *Self, data: []const u8) error{ OutOfMemory, IntegerTooLarge, IntegerTooSmall, InvalidType, TooShort }!void { tp.trace(tp.channel.debug, .{"restore_state_v0"}); defer self.sort_files_by_mtime(); var name: []const u8 = undefined; @@ -272,8 +234,11 @@ fn get_language_server_instance(self: *Self, language_server: []const u8) StartL defer self.allocator.free(uri); const basename_begin = std.mem.lastIndexOfScalar(u8, self.name, std.fs.path.sep); const basename = if (basename_begin) |begin| self.name[begin + 1 ..] else self.name; - - try self.send_lsp_init_request(lsp, self.name, basename, uri, language_server); + const response = try self.send_lsp_init_request(lsp, self.name, basename, uri); + defer self.allocator.free(response.buf); + lsp.send_notification("initialized", .{}) catch return error.LspFailed; + if (lsp.pid.expired()) return error.LspFailed; + log.logger("lsp").print("initialized LSP: {s}", .{fmt_lsp_name_func(language_server)}); try self.language_servers.put(try self.allocator.dupe(u8, language_server), lsp); return lsp; } @@ -310,11 +275,11 @@ fn make_URI(self: *Self, file_path: ?[]const u8) LspError![]const u8 { return buf.toOwnedSlice(); } -fn sort_files_by_mtime(self: *Self) void { +pub fn sort_files_by_mtime(self: *Self) void { sort_by_mtime(File, self.files.items); } -fn sort_tasks_by_mtime(self: *Self) void { +pub fn sort_tasks_by_mtime(self: *Self) void { sort_by_mtime(Task, self.tasks.items); } @@ -402,47 +367,21 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co return @min(max, matches.items.len); } -pub fn walk_tree_entry(self: *Self, file_path: []const u8, mtime: i128) OutOfMemoryError!void { +pub fn add_pending_file(self: *Self, file_path: []const u8, mtime: i128) OutOfMemoryError!void { self.longest_file_path = @max(self.longest_file_path, file_path.len); - (try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime }; + (try self.pending.addOne()).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime }; } -pub fn walk_tree_done(self: *Self) OutOfMemoryError!void { - self.state.walk_tree = .done; - if (self.walker) |pid| pid.deinit(); - self.walker = null; - return self.loaded(); -} - -fn merge_pending_files(self: *Self) OutOfMemoryError!void { +pub fn merge_pending_files(self: *Self) OutOfMemoryError!void { defer self.sort_files_by_mtime(); - const existing = try self.files.toOwnedSlice(self.allocator); - defer self.allocator.free(existing); + const existing = try self.files.toOwnedSlice(); self.files = self.pending; - self.pending = .empty; - + self.pending = std.ArrayList(File).init(self.allocator); for (existing) |*file| { self.update_mru_internal(file.path, file.mtime, file.pos.row, file.pos.col) catch {}; self.allocator.free(file.path); } -} - -fn loaded(self: *Self) OutOfMemoryError!void { - inline for (@typeInfo(@TypeOf(self.state)).@"struct".fields) |f| - if (@field(self.state, f.name) == .running) return; - - self.logger.print("project files: {d} restored, {d} {s}", .{ - self.files.items.len, - self.pending.items.len, - if (self.state.workspace_files == .done) "tracked" else "walked", - }); - - try self.merge_pending_files(); - self.logger.print("opened: {s} with {d} files in {d} ms", .{ - self.name, - self.files.items.len, - std.time.milliTimestamp() - self.open_time, - }); + self.allocator.free(existing); } pub fn update_mru(self: *Self, file_path: []const u8, row: usize, col: usize) OutOfMemoryError!void { @@ -462,14 +401,14 @@ fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usi return; } if (row != 0) { - (try self.files.addOne(self.allocator)).* = .{ + (try self.files.addOne()).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime, .pos = .{ .row = row, .col = col }, .visited = true, }; } else { - (try self.files.addOne(self.allocator)).* = .{ + (try self.files.addOne()).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime, }; @@ -520,7 +459,6 @@ pub fn delete_task(self: *Self, command: []const u8) error{}!void { } pub fn did_open(self: *Self, file_path: []const u8, file_type: []const u8, language_server: []const u8, version: usize, text: []const u8) StartLspError!void { - defer std.heap.c_allocator.free(text); self.update_mru(file_path, 0, 0) catch {}; const lsp = try self.get_or_start_language_server(file_path, language_server); const uri = try self.make_URI(file_path); @@ -530,10 +468,7 @@ pub fn did_open(self: *Self, file_path: []const u8, file_type: []const u8, langu }) catch return error.LspFailed; } -pub fn did_change(self: *Self, file_path: []const u8, version: usize, text_dst: []const u8, text_src: []const u8, eol_mode: Buffer.EolMode) LspError!void { - _ = eol_mode; - defer std.heap.c_allocator.free(text_dst); - defer std.heap.c_allocator.free(text_src); +pub fn did_change(self: *Self, file_path: []const u8, version: usize, root_dst_addr: usize, root_src_addr: usize, eol_mode: Buffer.EolMode) LspError!void { const lsp = try self.get_language_server(file_path); const uri = try self.make_URI(file_path); @@ -549,11 +484,22 @@ pub fn did_change(self: *Self, file_path: []const u8, version: usize, text_dst: self.allocator.free(scratch); } + const root_dst: Buffer.Root = if (root_dst_addr == 0) return else @ptrFromInt(root_dst_addr); + const root_src: Buffer.Root = if (root_src_addr == 0) return else @ptrFromInt(root_src_addr); + var dizzy_edits = std.ArrayListUnmanaged(dizzy.Edit){}; + var dst = std.ArrayList(u8).init(arena); + var src = std.ArrayList(u8).init(arena); var edits_cb = std.ArrayList(u8).init(arena); const writer = edits_cb.writer(); - const scratch_len = 4 * (text_dst.len + text_src.len) + 2; + { + const frame = tracy.initZone(@src(), .{ .name = "store" }); + defer frame.deinit(); + try root_dst.store(dst.writer(), eol_mode); + try root_src.store(src.writer(), eol_mode); + } + const scratch_len = 4 * (dst.items.len + src.items.len) + 2; const scratch = blk: { const frame = tracy.initZone(@src(), .{ .name = "scratch" }); defer frame.deinit(); @@ -564,7 +510,7 @@ pub fn did_change(self: *Self, file_path: []const u8, version: usize, text_dst: { const frame = tracy.initZone(@src(), .{ .name = "diff" }); defer frame.deinit(); - try dizzy.PrimitiveSliceDiffer(u8).diff(arena, &dizzy_edits, text_src, text_dst, scratch); + try dizzy.PrimitiveSliceDiffer(u8).diff(arena, &dizzy_edits, src.items, dst.items, scratch); } var lines_dst: usize = 0; var last_offset: usize = 0; @@ -576,7 +522,7 @@ pub fn did_change(self: *Self, file_path: []const u8, version: usize, text_dst: for (dizzy_edits.items) |dizzy_edit| { switch (dizzy_edit.kind) { .equal => { - scan_char(text_src[dizzy_edit.range.start..dizzy_edit.range.end], &lines_dst, '\n', &last_offset); + scan_char(src.items[dizzy_edit.range.start..dizzy_edit.range.end], &lines_dst, '\n', &last_offset); }, .insert => { const line_start_dst: usize = lines_dst; @@ -585,15 +531,15 @@ pub fn did_change(self: *Self, file_path: []const u8, version: usize, text_dst: .start = .{ .line = line_start_dst, .character = last_offset }, .end = .{ .line = line_start_dst, .character = last_offset }, }, - .text = text_dst[dizzy_edit.range.start..dizzy_edit.range.end], + .text = dst.items[dizzy_edit.range.start..dizzy_edit.range.end], }); edits_count += 1; - scan_char(text_dst[dizzy_edit.range.start..dizzy_edit.range.end], &lines_dst, '\n', &last_offset); + scan_char(dst.items[dizzy_edit.range.start..dizzy_edit.range.end], &lines_dst, '\n', &last_offset); }, .delete => { var line_end_dst: usize = lines_dst; var offset_end_dst: usize = last_offset; - scan_char(text_src[dizzy_edit.range.start..dizzy_edit.range.end], &line_end_dst, '\n', &offset_end_dst); + scan_char(src.items[dizzy_edit.range.start..dizzy_edit.range.end], &line_end_dst, '\n', &offset_end_dst); try cbor.writeValue(writer, .{ .range = .{ .start = .{ .line = lines_dst, .character = last_offset }, @@ -674,45 +620,27 @@ fn send_goto_request(self: *Self, from: tp.pid_ref, file_path: []const u8, row: const lsp = try self.get_language_server(file_path); const uri = try self.make_URI(file_path); defer self.allocator.free(uri); - - const handler: struct { - from: tp.pid, - name: []const u8, - project: *Self, - - pub fn deinit(self_: *@This()) void { - std.heap.c_allocator.free(self_.name); - self_.from.deinit(); - } - - pub fn receive(self_: @This(), response: tp.message) !void { - var link: []const u8 = undefined; - var locations: []const u8 = undefined; - if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.array })) { - if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, .{tp.extract_cbor(&link)} })) { - try navigate_to_location_link(self_.from.ref(), link); - } else if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&locations) })) { - try self_.project.send_reference_list(self_.from.ref(), locations, self_.name); - } - } else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) { - return; - } else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.extract_cbor(&link) })) { - try navigate_to_location_link(self_.from.ref(), link); - } - } - } = .{ - .from = from.clone(), - .name = try std.heap.c_allocator.dupe(u8, self.name), - .project = self, - }; - - lsp.send_request(self.allocator, method, .{ + const response = lsp.send_request(self.allocator, method, .{ .textDocument = .{ .uri = uri }, .position = .{ .line = row, .character = col }, - }, handler) catch return error.LspFailed; + }) catch return error.LspFailed; + defer self.allocator.free(response.buf); + var link: []const u8 = undefined; + var locations: []const u8 = undefined; + if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.array })) { + if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, .{tp.extract_cbor(&link)} })) { + try self.navigate_to_location_link(from, link); + } else if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&locations) })) { + try self.send_reference_list(from, locations); + } + } else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) { + return; + } else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.extract_cbor(&link) })) { + try self.navigate_to_location_link(from, link); + } } -fn navigate_to_location_link(from: tp.pid_ref, location_link: []const u8) (ClientError || InvalidMessageError || cbor.Error)!void { +fn navigate_to_location_link(_: *Self, from: tp.pid_ref, location_link: []const u8) (ClientError || InvalidMessageError || cbor.Error)!void { var iter = location_link; var targetUri: ?[]const u8 = null; var targetRange: ?Range = null; @@ -751,8 +679,8 @@ fn navigate_to_location_link(from: tp.pid_ref, location_link: []const u8) (Clien from.send(.{ "cmd", "navigate", .{ .file = file_path, .goto = .{ - targetSelectionRange.?.start.line + 1, - targetSelectionRange.?.start.character + 1, + targetRange.?.start.line + 1, + targetRange.?.start.character + 1, sel.start.line, sel.start.character, sel.end.line, @@ -774,40 +702,23 @@ pub fn references(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usi const lsp = try self.get_language_server(file_path); const uri = try self.make_URI(file_path); defer self.allocator.free(uri); - self.logger_lsp.print("finding references...", .{}); + log.logger("lsp").print("finding references...", .{}); - const handler: struct { - from: tp.pid, - name: []const u8, - project: *Self, - - pub fn deinit(self_: *@This()) void { - std.heap.c_allocator.free(self_.name); - self_.from.deinit(); - } - - pub fn receive(self_: @This(), response: tp.message) !void { - var locations: []const u8 = undefined; - if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) { - return; - } else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.extract_cbor(&locations) })) { - try self_.project.send_reference_list(self_.from.ref(), locations, self_.name); - } - } - } = .{ - .from = from.clone(), - .name = try std.heap.c_allocator.dupe(u8, self.name), - .project = self, - }; - - lsp.send_request(self.allocator, "textDocument/references", .{ + const response = lsp.send_request(self.allocator, "textDocument/references", .{ .textDocument = .{ .uri = uri }, .position = .{ .line = row, .character = col }, .context = .{ .includeDeclaration = true }, - }, handler) catch return error.LspFailed; + }) catch return error.LspFailed; + defer self.allocator.free(response.buf); + var locations: []const u8 = undefined; + if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) { + return; + } else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.extract_cbor(&locations) })) { + try self.send_reference_list(from, locations); + } } -fn send_reference_list(self: *Self, to: tp.pid_ref, locations: []const u8, name: []const u8) (ClientError || InvalidMessageError || GetLineOfFileError || cbor.Error)!void { +fn send_reference_list(self: *Self, to: tp.pid_ref, locations: []const u8) (ClientError || InvalidMessageError || GetLineOfFileError || cbor.Error)!void { defer to.send(.{ "REF", "done" }) catch {}; var iter = locations; var len = try cbor.decodeArrayHeader(&iter); @@ -815,14 +726,13 @@ fn send_reference_list(self: *Self, to: tp.pid_ref, locations: []const u8, name: while (len > 0) : (len -= 1) { var location: []const u8 = undefined; if (try cbor.matchValue(&iter, cbor.extract_cbor(&location))) { - try send_reference(to, location, name); + try self.send_reference(to, location); } else return error.InvalidMessageField; } - self.logger_lsp.print("found {d} references", .{count}); + log.logger("lsp").print("found {d} references", .{count}); } -fn send_reference(to: tp.pid_ref, location: []const u8, name: []const u8) (ClientError || InvalidMessageError || GetLineOfFileError || cbor.Error)!void { - const allocator = std.heap.c_allocator; +fn send_reference(self: *Self, to: tp.pid_ref, location: []const u8) (ClientError || InvalidMessageError || GetLineOfFileError || cbor.Error)!void { var iter = location; var targetUri: ?[]const u8 = null; var targetRange: ?Range = null; @@ -857,10 +767,10 @@ fn send_reference(to: tp.pid_ref, location: []const u8, name: []const u8) (Clien file_path[i] = '\\'; }; } - const line = try get_line_of_file(allocator, file_path, targetRange.?.start.line); - defer allocator.free(line); - const file_path_ = if (file_path.len > name.len and std.mem.eql(u8, name, file_path[0..name.len])) - file_path[name.len + 1 ..] + const line = try self.get_line_of_file(self.allocator, file_path, targetRange.?.start.line); + defer self.allocator.free(line); + const file_path_ = if (file_path.len > self.name.len and std.mem.eql(u8, self.name, file_path[0..self.name.len])) + file_path[self.name.len + 1 ..] else file_path; to.send(.{ @@ -878,44 +788,24 @@ pub fn completion(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usi const lsp = try self.get_language_server(file_path); const uri = try self.make_URI(file_path); defer self.allocator.free(uri); - - const handler: struct { - from: tp.pid, - file_path: []const u8, - row: usize, - col: usize, - - pub fn deinit(self_: *@This()) void { - std.heap.c_allocator.free(self_.file_path); - self_.from.deinit(); - } - - pub fn receive(self_: @This(), response: tp.message) !void { - var result: []const u8 = undefined; - if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) { - try send_content_msg_empty(self_.from.ref(), "hover", self_.file_path, self_.row, self_.col); - } else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.array })) { - if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&result) })) - try send_completion_items(self_.from.ref(), self_.file_path, self_.row, self_.col, result, false); - } else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.map })) { - if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&result) })) - try send_completion_list(self_.from.ref(), self_.file_path, self_.row, self_.col, result); - } - } - } = .{ - .from = from.clone(), - .file_path = try std.heap.c_allocator.dupe(u8, file_path), - .row = row, - .col = col, - }; - - lsp.send_request(self.allocator, "textDocument/completion", .{ + const response = lsp.send_request(self.allocator, "textDocument/completion", .{ .textDocument = .{ .uri = uri }, .position = .{ .line = row, .character = col }, - }, handler) catch return error.LspFailed; + }) catch return error.LspFailed; + defer self.allocator.free(response.buf); + var result: []const u8 = undefined; + if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) { + try send_content_msg_empty(from, "hover", file_path, row, col); + } else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.array })) { + if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&result) })) + try self.send_completion_items(from, file_path, row, col, result, false); + } else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.map })) { + if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&result) })) + try self.send_completion_list(from, file_path, row, col, result); + } } -fn send_completion_list(to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, result: []const u8) (ClientError || InvalidMessageError || cbor.Error)!void { +fn send_completion_list(self: *Self, to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, result: []const u8) (ClientError || InvalidMessageError || cbor.Error)!void { var iter = result; var len = cbor.decodeMapHeader(&iter) catch return; var items: []const u8 = ""; @@ -931,20 +821,20 @@ fn send_completion_list(to: tp.pid_ref, file_path: []const u8, row: usize, col: try cbor.skipValue(&iter); } } - return send_completion_items(to, file_path, row, col, items, is_incomplete) catch error.ClientFailed; + return self.send_completion_items(to, file_path, row, col, items, is_incomplete) catch error.ClientFailed; } -fn send_completion_items(to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, items: []const u8, is_incomplete: bool) (ClientError || InvalidMessageError || cbor.Error)!void { +fn send_completion_items(self: *Self, to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, items: []const u8, is_incomplete: bool) (ClientError || InvalidMessageError || cbor.Error)!void { var iter = items; var len = cbor.decodeArrayHeader(&iter) catch return; var item: []const u8 = ""; while (len > 0) : (len -= 1) { if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&item)))) return error.InvalidMessageField; - send_completion_item(to, file_path, row, col, item, if (len > 1) true else is_incomplete) catch return error.ClientFailed; + self.send_completion_item(to, file_path, row, col, item, if (len > 1) true else is_incomplete) catch return error.ClientFailed; } } -fn send_completion_item(to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, item: []const u8, is_incomplete: bool) (ClientError || InvalidMessageError || cbor.Error)!void { +fn send_completion_item(_: *Self, to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, item: []const u8, is_incomplete: bool) (ClientError || InvalidMessageError || cbor.Error)!void { var label: []const u8 = ""; var label_detail: []const u8 = ""; var label_description: []const u8 = ""; @@ -1023,30 +913,28 @@ fn send_completion_item(to: tp.pid_ref, file_path: []const u8, row: usize, col: const insert = textEdit_insert orelse return error.InvalidMessageField; const replace = textEdit_replace orelse return error.InvalidMessageField; return to.send(.{ - "cmd", "add_completion", .{ - file_path, - row, - col, - is_incomplete, - label, - label_detail, - label_description, - kind, - detail, - documentation, - documentation_kind, - sortText, - insertTextFormat, - textEdit_newText, - insert.start.line, - insert.start.character, - insert.end.line, - insert.end.character, - replace.start.line, - replace.start.character, - replace.end.line, - replace.end.character, - }, + "completion_item", + file_path, + row, + col, + is_incomplete, + label, + label_detail, + label_description, + kind, + detail, + documentation, + sortText, + insertTextFormat, + textEdit_newText, + insert.start.line, + insert.start.character, + insert.end.line, + insert.end.character, + replace.start.line, + replace.start.character, + replace.end.line, + replace.end.character, }) catch error.ClientFailed; } @@ -1060,74 +948,57 @@ pub fn rename_symbol(self: *Self, from: tp.pid_ref, file_path: []const u8, row: const lsp = try self.get_language_server(file_path); const uri = try self.make_URI(file_path); defer self.allocator.free(uri); - - const handler: struct { - from: tp.pid, - file_path: []const u8, - - pub fn deinit(self_: *@This()) void { - std.heap.c_allocator.free(self_.file_path); - self_.from.deinit(); - } - - pub fn receive(self_: @This(), response: tp.message) !void { - const allocator = std.heap.c_allocator; - var result: []const u8 = undefined; - // buffer the renames in order to send as a single, atomic message - var renames = std.ArrayList(Rename).init(allocator); - defer renames.deinit(); - - if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.map })) { - if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&result) })) { - try decode_rename_symbol_map(result, &renames); - // write the renames message manually since there doesn't appear to be an array helper - var msg_buf = std.ArrayList(u8).init(allocator); - defer msg_buf.deinit(); - const w = msg_buf.writer(); - try cbor.writeArrayHeader(w, 3); - try cbor.writeValue(w, "cmd"); - try cbor.writeValue(w, "rename_symbol_item"); - try cbor.writeArrayHeader(w, renames.items.len); - for (renames.items) |rename| { - if (!std.mem.eql(u8, rename.uri[0..7], "file://")) return error.InvalidTargetURI; - var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; - var file_path_ = std.Uri.percentDecodeBackwards(&file_path_buf, rename.uri[7..]); - if (builtin.os.tag == .windows) { - if (file_path_[0] == '/') file_path_ = file_path_[1..]; - for (file_path_, 0..) |c, i| if (c == '/') { - file_path_[i] = '\\'; - }; - } - const line = try get_line_of_file(allocator, self_.file_path, rename.range.start.line); - try cbor.writeValue(w, .{ - file_path_, - rename.range.start.line, - rename.range.start.character, - rename.range.end.line, - rename.range.end.character, - rename.new_text, - line, - }); - } - self_.from.send_raw(.{ .buf = msg_buf.items }) catch return error.ClientFailed; - } - } - } - } = .{ - .from = from.clone(), - .file_path = try std.heap.c_allocator.dupe(u8, file_path), - }; - - lsp.send_request(self.allocator, "textDocument/rename", .{ + const response = lsp.send_request(self.allocator, "textDocument/rename", .{ .textDocument = .{ .uri = uri }, .position = .{ .line = row, .character = col }, .newName = "PLACEHOLDER", - }, handler) catch return error.LspFailed; + }) catch return error.LspFailed; + defer self.allocator.free(response.buf); + var result: []const u8 = undefined; + // buffer the renames in order to send as a single, atomic message + var renames = std.ArrayList(Rename).init(self.allocator); + defer renames.deinit(); + + if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.map })) { + if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&result) })) { + try self.decode_rename_symbol_map(result, &renames); + // write the renames message manually since there doesn't appear to be an array helper + var msg_buf = std.ArrayList(u8).init(self.allocator); + defer msg_buf.deinit(); + const w = msg_buf.writer(); + try cbor.writeArrayHeader(w, 3); + try cbor.writeValue(w, "cmd"); + try cbor.writeValue(w, "rename_symbol_item"); + try cbor.writeArrayHeader(w, renames.items.len); + for (renames.items) |rename| { + if (!std.mem.eql(u8, rename.uri[0..7], "file://")) return error.InvalidTargetURI; + var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; + var file_path_ = std.Uri.percentDecodeBackwards(&file_path_buf, rename.uri[7..]); + if (builtin.os.tag == .windows) { + if (file_path_[0] == '/') file_path_ = file_path_[1..]; + for (file_path_, 0..) |c, i| if (c == '/') { + file_path_[i] = '\\'; + }; + } + const line = try self.get_line_of_file(self.allocator, file_path, rename.range.start.line); + try cbor.writeValue(w, .{ + file_path_, + rename.range.start.line, + rename.range.start.character, + rename.range.end.line, + rename.range.end.character, + rename.new_text, + line, + }); + } + from.send_raw(.{ .buf = msg_buf.items }) catch return error.ClientFailed; + } + } } // decode a WorkspaceEdit record which may have shape {"changes": {}} or {"documentChanges": []} // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspaceEdit -fn decode_rename_symbol_map(result: []const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void { +fn decode_rename_symbol_map(self: *Self, result: []const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void { var iter = result; var len = cbor.decodeMapHeader(&iter) catch return error.InvalidMessage; var changes: []const u8 = ""; @@ -1136,11 +1007,11 @@ fn decode_rename_symbol_map(result: []const u8, renames: *std.ArrayList(Rename)) if (!(try cbor.matchString(&iter, &field_name))) return error.InvalidMessage; if (std.mem.eql(u8, field_name, "changes")) { if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&changes)))) return error.InvalidMessageField; - try decode_rename_symbol_changes(changes, renames); + try self.decode_rename_symbol_changes(changes, renames); return; } else if (std.mem.eql(u8, field_name, "documentChanges")) { if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&changes)))) return error.InvalidMessageField; - try decode_rename_symbol_doc_changes(changes, renames); + try self.decode_rename_symbol_doc_changes(changes, renames); return; } else { try cbor.skipValue(&iter); @@ -1149,17 +1020,17 @@ fn decode_rename_symbol_map(result: []const u8, renames: *std.ArrayList(Rename)) return error.ClientFailed; } -fn decode_rename_symbol_changes(changes: []const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void { +fn decode_rename_symbol_changes(self: *Self, changes: []const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void { var iter = changes; var files_len = cbor.decodeMapHeader(&iter) catch return error.InvalidMessage; while (files_len > 0) : (files_len -= 1) { var file_uri: []const u8 = undefined; if (!(try cbor.matchString(&iter, &file_uri))) return error.InvalidMessage; - try decode_rename_symbol_item(file_uri, &iter, renames); + try decode_rename_symbol_item(self, file_uri, &iter, renames); } } -fn decode_rename_symbol_doc_changes(changes: []const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void { +fn decode_rename_symbol_doc_changes(self: *Self, changes: []const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void { var iter = changes; var changes_len = cbor.decodeArrayHeader(&iter) catch return error.InvalidMessage; while (changes_len > 0) : (changes_len -= 1) { @@ -1179,14 +1050,14 @@ fn decode_rename_symbol_doc_changes(changes: []const u8, renames: *std.ArrayList } } else if (std.mem.eql(u8, field_name, "edits")) { if (file_uri.len == 0) return error.InvalidMessage; - try decode_rename_symbol_item(file_uri, &iter, renames); + try decode_rename_symbol_item(self, file_uri, &iter, renames); } } } } // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textEdit -fn decode_rename_symbol_item(file_uri: []const u8, iter: *[]const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void { +fn decode_rename_symbol_item(_: *Self, file_uri: []const u8, iter: *[]const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void { var text_edits_len = cbor.decodeArrayHeader(iter) catch return error.InvalidMessage; while (text_edits_len > 0) : (text_edits_len -= 1) { var m_range: ?Range = null; @@ -1215,41 +1086,22 @@ pub fn hover(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, c const lsp = try self.get_language_server(file_path); const uri = try self.make_URI(file_path); defer self.allocator.free(uri); - // self.logger_lsp.print("fetching hover information...", .{}); + // log.logger("lsp").print("fetching hover information...", .{}); - const handler: struct { - from: tp.pid, - file_path: []const u8, - row: usize, - col: usize, - - pub fn deinit(self_: *@This()) void { - self_.from.deinit(); - std.heap.c_allocator.free(self_.file_path); - } - - pub fn receive(self_: @This(), response: tp.message) !void { - var result: []const u8 = undefined; - if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) { - try send_content_msg_empty(self_.from.ref(), "hover", self_.file_path, self_.row, self_.col); - } else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.extract_cbor(&result) })) { - try send_hover(self_.from.ref(), self_.file_path, self_.row, self_.col, result); - } - } - } = .{ - .from = from.clone(), - .file_path = try std.heap.c_allocator.dupe(u8, file_path), - .row = row, - .col = col, - }; - - lsp.send_request(self.allocator, "textDocument/hover", .{ + const response = lsp.send_request(self.allocator, "textDocument/hover", .{ .textDocument = .{ .uri = uri }, .position = .{ .line = row, .character = col }, - }, handler) catch return error.LspFailed; + }) catch return error.LspFailed; + defer self.allocator.free(response.buf); + var result: []const u8 = undefined; + if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) { + try send_content_msg_empty(from, "hover", file_path, row, col); + } else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.extract_cbor(&result) })) { + try self.send_hover(from, file_path, row, col, result); + } } -fn send_hover(to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, result: []const u8) (ClientError || InvalidMessageError || cbor.Error)!void { +fn send_hover(self: *Self, to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, result: []const u8) (ClientError || InvalidMessageError || cbor.Error)!void { var iter = result; var len = cbor.decodeMapHeader(&iter) catch return; var contents: []const u8 = ""; @@ -1268,10 +1120,11 @@ fn send_hover(to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, res } } if (contents.len > 0) - return send_contents(to, "hover", file_path, row, col, contents, range); + return self.send_contents(to, "hover", file_path, row, col, contents, range); } fn send_contents( + self: *Self, to: tp.pid_ref, tag: []const u8, file_path: []const u8, @@ -1294,7 +1147,7 @@ fn send_contents( }; if (is_list) { - var content = std.ArrayList(u8).init(std.heap.c_allocator); + var content = std.ArrayList(u8).init(self.allocator); defer content.deinit(); while (len > 0) : (len -= 1) { if (try cbor.matchValue(&iter, cbor.extract(&value))) { @@ -1465,7 +1318,7 @@ fn read_position(position: []const u8) !Position { return .{ .line = line.?, .character = character.? }; } -pub fn show_message(self: *Self, _: tp.pid_ref, params_cb: []const u8) !void { +pub fn show_message(_: *Self, _: tp.pid_ref, params_cb: []const u8) !void { var type_: i32 = 0; var message: ?[]const u8 = null; var iter = params_cb; @@ -1482,10 +1335,12 @@ pub fn show_message(self: *Self, _: tp.pid_ref, params_cb: []const u8) !void { } } const msg = message orelse return; + const logger = log.logger("lsp"); + defer logger.deinit(); if (type_ <= 2) - self.logger_lsp.err_msg("lsp", msg) + logger.err_msg("lsp", msg) else - self.logger_lsp.print("{s}", .{msg}); + logger.print("{s}", .{msg}); } pub fn register_capability(self: *Self, from: tp.pid_ref, cbor_id: []const u8, params_cb: []const u8) ClientError!void { @@ -1509,32 +1364,8 @@ pub fn send_lsp_response(self: *Self, from: tp.pid_ref, cbor_id: []const u8, res from.send_raw(.{ .buf = cb.items }) catch return error.ClientFailed; } -fn send_lsp_init_request(self: *Self, lsp: LSP, project_path: []const u8, project_basename: []const u8, project_uri: []const u8, language_server: []const u8) !void { - const handler: struct { - language_server: []const u8, - lsp: LSP, - project: *Self, - - pub fn deinit(self_: *@This()) void { - self_.lsp.pid.deinit(); - std.heap.c_allocator.free(self_.language_server); - } - - pub fn receive(self_: @This(), _: tp.message) !void { - self_.lsp.send_notification("initialized", .{}) catch return error.LspFailed; - if (self_.lsp.pid.expired()) return error.LspFailed; - self_.project.logger_lsp.print("initialized LSP: {s}", .{fmt_lsp_name_func(self_.language_server)}); - } - } = .{ - .language_server = try std.heap.c_allocator.dupe(u8, language_server), - .lsp = .{ - .allocator = lsp.allocator, - .pid = lsp.pid.clone(), - }, - .project = self, - }; - - try lsp.send_request(self.allocator, "initialize", .{ +fn send_lsp_init_request(self: *Self, lsp: LSP, project_path: []const u8, project_basename: []const u8, project_uri: []const u8) CallError!tp.message { + return lsp.send_request(self.allocator, "initialize", .{ .processId = if (builtin.os.tag == .linux) std.os.linux.getpid() else null, .rootPath = project_path, .rootUri = project_uri, @@ -1836,7 +1667,7 @@ fn send_lsp_init_request(self: *Self, lsp: LSP, project_path: []const u8, projec }, }, }, - }, handler); + }); } fn fmt_lsp_name_func(bytes: []const u8) std.fmt.Formatter(format_lsp_name_func) { @@ -1867,7 +1698,7 @@ const eol = '\n'; pub const GetLineOfFileError = (OutOfMemoryError || std.fs.File.OpenError || std.fs.File.Reader.Error); -fn get_line_of_file(allocator: std.mem.Allocator, file_path: []const u8, line_: usize) GetLineOfFileError![]const u8 { +fn get_line_of_file(self: *Self, allocator: std.mem.Allocator, file_path: []const u8, line_: usize) GetLineOfFileError![]const u8 { const line = line_ + 1; const file = try std.fs.cwd().openFile(file_path, .{ .mode = .read_only }); defer file.close(); @@ -1881,70 +1712,15 @@ fn get_line_of_file(allocator: std.mem.Allocator, file_path: []const u8, line_: var line_count: usize = 1; for (0..buf.len) |i| { if (line_count == line) - return get_line(allocator, buf[i..]); + return self.get_line(allocator, buf[i..]); if (buf[i] == eol) line_count += 1; } return allocator.dupe(u8, ""); } -pub fn get_line(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 { +pub fn get_line(_: *Self, allocator: std.mem.Allocator, buf: []const u8) ![]const u8 { for (0..buf.len) |i| { if (buf[i] == eol) return allocator.dupe(u8, buf[0..i]); } return allocator.dupe(u8, buf); } - -pub fn query_git(self: *Self) void { - self.state.workspace_path = .running; - git.workspace_path(@intFromPtr(self)) catch { - self.state.workspace_path = .failed; - self.start_walker(); - }; - self.state.current_branch = .running; - git.current_branch(@intFromPtr(self)) catch { - self.state.current_branch = .failed; - }; -} - -fn start_walker(self: *Self) void { - self.state.walk_tree = .running; - self.walker = walk_tree.start(self.allocator, self.name) catch blk: { - self.state.walk_tree = .failed; - break :blk null; - }; -} - -pub fn process_git(self: *Self, m: tp.message) (OutOfMemoryError || error{Exit})!void { - var value: []const u8 = undefined; - var path: []const u8 = undefined; - if (try m.match(.{ tp.any, tp.any, "workspace_path", tp.null_ })) { - self.state.workspace_path = .done; - self.start_walker(); - try self.loaded(); - } else if (try m.match(.{ tp.any, tp.any, "workspace_path", tp.extract(&value) })) { - if (self.workspace) |p| self.allocator.free(p); - self.workspace = try self.allocator.dupe(u8, value); - self.state.workspace_path = .done; - self.state.workspace_files = .running; - git.workspace_files(@intFromPtr(self)) catch { - self.state.workspace_files = .failed; - }; - } else if (try m.match(.{ tp.any, tp.any, "current_branch", tp.null_ })) { - self.state.current_branch = .done; - try self.loaded(); - } else if (try m.match(.{ tp.any, tp.any, "current_branch", tp.extract(&value) })) { - if (self.branch) |p| self.allocator.free(p); - self.branch = try self.allocator.dupe(u8, value); - self.state.current_branch = .done; - try self.loaded(); - } else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.extract(&path) })) { - self.longest_file_path = @max(self.longest_file_path, path.len); - const stat = std.fs.cwd().statFile(path) catch return; - (try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, path), .mtime = stat.mtime }; - } else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.null_ })) { - self.state.workspace_files = .done; - try self.loaded(); - } else { - self.logger_git.err("git", tp.unexpected(m)); - } -} diff --git a/src/buffer/Buffer.zig b/src/buffer/Buffer.zig index c90bf76..e2ed666 100644 --- a/src/buffer/Buffer.zig +++ b/src/buffer/Buffer.zig @@ -14,7 +14,7 @@ pub const Manager = @import("Manager.zig"); pub const Cursor = @import("Cursor.zig"); pub const View = @import("View.zig"); pub const Selection = @import("Selection.zig"); -pub const MetaWriter = std.ArrayListUnmanaged(u8).Writer; +pub const MetaWriter = std.ArrayList(u8).Writer; pub const Metrics = struct { ctx: *const anyopaque, @@ -55,7 +55,7 @@ file_type_icon: ?[]const u8 = null, file_type_color: ?u24 = null, pub const EolMode = enum { lf, crlf }; -pub const EolModeTag = @typeInfo(EolMode).@"enum".tag_type; +pub const EolModeTag = @typeInfo(EolMode).Enum.tag_type; const UndoNode = struct { root: Root, @@ -71,26 +71,26 @@ const UndoBranch = struct { }; pub const WalkerMut = struct { - keep_walking_: bool = false, - found_: bool = false, + keep_walking: bool = false, + found: bool = false, replace: ?Root = null, err: ?anyerror = null, - pub const keep_walking = WalkerMut{ .keep_walking_ = true }; - pub const stop = WalkerMut{ .keep_walking_ = false }; - pub const found = WalkerMut{ .found_ = true }; + pub const keep_walking = WalkerMut{ .keep_walking = true }; + pub const stop = WalkerMut{ .keep_walking = false }; + pub const found = WalkerMut{ .found = true }; const F = *const fn (ctx: *anyopaque, leaf: *const Leaf, metrics: Metrics) WalkerMut; }; pub const Walker = struct { - keep_walking_: bool = false, - found_: bool = false, + keep_walking: bool = false, + found: bool = false, err: ?anyerror = null, - pub const keep_walking = Walker{ .keep_walking_ = true }; - pub const stop = Walker{ .keep_walking_ = false }; - pub const found = Walker{ .found_ = true }; + pub const keep_walking = Walker{ .keep_walking = true }; + pub const stop = Walker{ .keep_walking = false }; + pub const found = Walker{ .found = true }; const F = *const fn (ctx: *anyopaque, leaf: *const Leaf, metrics: Metrics) Walker; }; @@ -126,8 +126,8 @@ pub const Branch = struct { fn merge_results_const(_: *const Branch, left: Walker, right: Walker) Walker { var result = Walker{}; result.err = if (left.err) |_| left.err else right.err; - result.keep_walking_ = left.keep_walking_ and right.keep_walking_; - result.found_ = left.found_ or right.found_; + result.keep_walking = left.keep_walking and right.keep_walking; + result.found = left.found or right.found; return result; } @@ -144,8 +144,8 @@ pub const Branch = struct { else Node.new(allocator, new_left, new_right) catch |e| return .{ .err = e }; } - result.keep_walking_ = left.keep_walking_ and right.keep_walking_; - result.found_ = left.found_ or right.found_; + result.keep_walking = left.keep_walking and right.keep_walking; + result.found = left.found or right.found; return result; } }; @@ -350,10 +350,10 @@ const Node = union(enum) { switch (self.*) { .node => |*node| { const left = node.left.walk_const(f, ctx, metrics); - if (!left.keep_walking_) { + if (!left.keep_walking) { var result = Walker{}; result.err = left.err; - result.found_ = left.found_; + result.found = left.found; return result; } const right = node.right.walk_const(f, ctx, metrics); @@ -367,10 +367,10 @@ const Node = union(enum) { switch (self.*) { .node => |*node| { const left = node.left.walk(allocator, f, ctx, metrics); - if (!left.keep_walking_) { + if (!left.keep_walking) { var result = WalkerMut{}; result.err = left.err; - result.found_ = left.found_; + result.found = left.found; if (left.replace) |p| { result.replace = Node.new(allocator, p, node.right) catch |e| return .{ .err = e }; } @@ -390,14 +390,14 @@ const Node = union(enum) { if (line >= left_bols) return node.right.walk_from_line_begin_const_internal(line - left_bols, f, ctx, metrics); const left_result = node.left.walk_from_line_begin_const_internal(line, f, ctx, metrics); - const right_result = if (left_result.found_ and left_result.keep_walking_) node.right.walk_const(f, ctx, metrics) else Walker{}; + const right_result = if (left_result.found and left_result.keep_walking) node.right.walk_const(f, ctx, metrics) else Walker{}; return node.merge_results_const(left_result, right_result); }, .leaf => |*l| { if (line == 0) { var result = f(ctx, l, metrics); if (result.err) |_| return result; - result.found_ = true; + result.found = true; return result; } return Walker.keep_walking; @@ -408,7 +408,7 @@ const Node = union(enum) { pub fn walk_from_line_begin_const(self: *const Node, line: usize, f: Walker.F, ctx: *anyopaque, metrics: Metrics) !bool { const result = self.walk_from_line_begin_const_internal(line, f, ctx, metrics); if (result.err) |e| return e; - return result.found_; + return result.found; } fn walk_from_line_begin_internal(self: *const Node, allocator: Allocator, line: usize, f: WalkerMut.F, ctx: *anyopaque, metrics: Metrics) WalkerMut { @@ -420,8 +420,8 @@ const Node = union(enum) { if (right_result.replace) |p| { var result = WalkerMut{}; result.err = right_result.err; - result.found_ = right_result.found_; - result.keep_walking_ = right_result.keep_walking_; + result.found = right_result.found; + result.keep_walking = right_result.keep_walking; result.replace = if (p.is_empty()) node.left else @@ -432,7 +432,7 @@ const Node = union(enum) { } } const left_result = node.left.walk_from_line_begin_internal(allocator, line, f, ctx, metrics); - const right_result = if (left_result.found_ and left_result.keep_walking_) node.right.walk(allocator, f, ctx, metrics) else WalkerMut{}; + const right_result = if (left_result.found and left_result.keep_walking) node.right.walk(allocator, f, ctx, metrics) else WalkerMut{}; return node.merge_results(allocator, left_result, right_result); }, .leaf => |*l| { @@ -442,7 +442,7 @@ const Node = union(enum) { result.replace = null; return result; } - result.found_ = true; + result.found = true; return result; } return WalkerMut.keep_walking; @@ -453,7 +453,7 @@ const Node = union(enum) { pub fn walk_from_line_begin(self: *const Node, allocator: Allocator, line: usize, f: WalkerMut.F, ctx: *anyopaque, metrics: Metrics) !struct { bool, ?Root } { const result = self.walk_from_line_begin_internal(allocator, line, f, ctx, metrics); if (result.err) |e| return e; - return .{ result.found_, result.replace }; + return .{ result.found, result.replace }; } fn find_line_node(self: *const Node, line: usize) ?*const Node { @@ -508,12 +508,12 @@ const Node = union(enum) { if (ret.err) |e| return .{ .err = e }; buf = buf[bytes..]; ctx.abs_col += @intCast(cols); - if (!ret.keep_walking_) return Walker.stop; + if (!ret.keep_walking) return Walker.stop; } if (leaf.eol) { const ret = ctx.walker_f(ctx.walker_ctx, "\n", 1, metrics); if (ret.err) |e| return .{ .err = e }; - if (!ret.keep_walking_) return Walker.stop; + if (!ret.keep_walking) return Walker.stop; ctx.abs_col = 0; } return Walker.keep_walking; @@ -670,7 +670,7 @@ const Node = union(enum) { var result = WalkerMut.keep_walking; if (ctx.delete_next_bol and ctx.bytes == 0) { result.replace = Leaf.new(ctx.allocator, leaf.buf, false, leaf.eol) catch |e| return .{ .err = e }; - result.keep_walking_ = false; + result.keep_walking = false; ctx.delete_next_bol = false; return result; } @@ -724,7 +724,7 @@ const Node = union(enum) { } } if (ctx.bytes == 0 and !ctx.delete_next_bol) - result.keep_walking_ = false; + result.keep_walking = false; } return result; } @@ -1030,9 +1030,7 @@ const Node = union(enum) { return error.NotFound; } - pub fn debug_render_chunks(self: *const Node, allocator: std.mem.Allocator, line: usize, metrics_: Metrics) ![]const u8 { - var output = std.ArrayList(u8).init(allocator); - defer output.deinit(); + pub fn debug_render_chunks(self: *const Node, line: usize, output: *ArrayList(u8), metrics_: Metrics) !void { const ctx_ = struct { l: *ArrayList(u8), wcwidth: usize = 0, @@ -1043,23 +1041,17 @@ const Node = union(enum) { return if (!leaf.eol) Walker.keep_walking else Walker.stop; } }; - var ctx: ctx_ = .{ .l = &output }; + var ctx: ctx_ = .{ .l = output }; const found = self.walk_from_line_begin_const(line, ctx_.walker, &ctx, metrics_) catch true; if (!found) return error.NotFound; var buf: [16]u8 = undefined; const wcwidth = try std.fmt.bufPrint(&buf, "{d}", .{ctx.wcwidth}); try output.appendSlice(wcwidth); - return output.toOwnedSlice(); } - pub fn debug_line_render_tree(self: *const Node, allocator: std.mem.Allocator, line: usize) ![]const u8 { - return if (self.find_line_node(line)) |n| blk: { - var l = std.ArrayList(u8).init(allocator); - defer l.deinit(); - n.debug_render_tree(&l, 0); - break :blk l.toOwnedSlice(); - } else error.NotFound; + pub fn debug_line_render_tree(self: *const Node, line: usize, l: *ArrayList(u8)) !void { + return if (self.find_line_node(line)) |n| n.debug_render_tree(l, 0) else error.NotFound; } }; @@ -1219,9 +1211,6 @@ pub const LoadFromFileError = error{ DanglingSurrogateHalf, ExpectedSecondSurrogateHalf, UnexpectedSecondSurrogateHalf, - LockViolation, - ProcessNotFound, - Canceled, }; pub fn load_from_file( @@ -1265,11 +1254,6 @@ pub fn reset_to_last_saved(self: *Self) void { } } -pub fn refresh_from_file(self: *Self) LoadFromFileError!void { - try self.load_from_file_and_update(self.file_path); - self.update_last_used_time(); -} - pub fn store_to_string(self: *const Self, allocator: Allocator, eol_mode: EolMode) ![]u8 { var s = try ArrayList(u8).initCapacity(allocator, self.root.weights_sum().len); try self.root.store(s.writer(), eol_mode); @@ -1318,7 +1302,6 @@ pub const StoreToFileError = error{ PathAlreadyExists, PipeBusy, ProcessFdQuotaExceeded, - ProcessNotFound, ReadOnlyFileSystem, RenameAcrossMountPoints, SharingViolation, diff --git a/src/buffer/Cursor.zig b/src/buffer/Cursor.zig index aca5339..23e562b 100644 --- a/src/buffer/Cursor.zig +++ b/src/buffer/Cursor.zig @@ -218,21 +218,24 @@ pub fn nudge_insert(self: *Self, nudge: Selection) void { } pub fn nudge_delete(self: *Self, nudge: Selection) bool { - if (nudge.begin.right_of(self.*)) return true; - if (nudge.end.right_of(self.*)) return false; - + if (self.row < nudge.begin.row or (self.row == nudge.begin.row and self.col < nudge.begin.col)) return true; if (self.row == nudge.begin.row) { - self.col -= nudge.end.col - nudge.begin.col; - self.target = self.col; - return true; - } - if (self.row == nudge.end.row) { - self.row -= nudge.end.row - nudge.begin.row; - if (self.row == nudge.begin.row) { - self.col += nudge.begin.col + nudge.end.col; + if (nudge.begin.row < nudge.end.row) { + return false; } else { - self.col -= nudge.end.col; + if (self.col < nudge.end.col) { + return false; + } + self.col -= nudge.end.col - nudge.begin.col; + self.target = self.col; + return true; } + } + if (self.row < nudge.end.row) return false; + if (self.row == nudge.end.row) { + if (self.col < nudge.end.col) return false; + self.row -= nudge.end.row - nudge.begin.row; + self.col -= nudge.end.col; self.target = self.col; return true; } diff --git a/src/buffer/unicode.zig b/src/buffer/unicode.zig index a2747a8..5cd4986 100644 --- a/src/buffer/unicode.zig +++ b/src/buffer/unicode.zig @@ -72,12 +72,12 @@ pub fn utf8_sanitize(allocator: std.mem.Allocator, input: []const u8) error{ return output.toOwnedSlice(allocator); } -pub const LetterCasing = @import("LetterCasing"); -var letter_casing: ?LetterCasing = null; -var letter_casing_arena = std.heap.ArenaAllocator.init(std.heap.c_allocator); +pub const CaseData = @import("CaseData"); +var case_data: ?CaseData = null; +var case_data_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); -pub fn get_letter_casing() *LetterCasing { - if (letter_casing) |*cd| return cd; - letter_casing = LetterCasing.init(letter_casing_arena.allocator()) catch @panic("LetterCasing.init"); - return &letter_casing.?; +pub fn get_case_data() *CaseData { + if (case_data) |*cd| return cd; + case_data = CaseData.init(case_data_arena.allocator()) catch @panic("CaseData.init"); + return &case_data.?; } diff --git a/src/command.zig b/src/command.zig index e7e0829..44de9e6 100644 --- a/src/command.zig +++ b/src/command.zig @@ -13,14 +13,10 @@ pub const Context = struct { args: tp.message = .{}, pub fn fmt(value: anytype) Context { - context_buffer.clearRetainingCapacity(); - cbor.writeValue(context_buffer.writer(), value) catch @panic("command.Context.fmt failed"); - return .{ .args = .{ .buf = context_buffer.items } }; + return .{ .args = tp.message.fmtbuf(&context_buffer, value) catch @panic("command.Context.fmt failed") }; } }; - -const context_buffer_allocator = std.heap.c_allocator; -threadlocal var context_buffer: std.ArrayList(u8) = std.ArrayList(u8).init(context_buffer_allocator); +threadlocal var context_buffer: [tp.max_message_size]u8 = undefined; pub const fmt = Context.fmt; const Vtable = struct { @@ -39,7 +35,6 @@ pub const ArgumentType = enum { string, integer, float, - boolean, object, array, }; @@ -151,8 +146,13 @@ pub fn execute(id: ID, ctx: Context) tp.result { } pub fn get_id(name: []const u8) ?ID { - var id: ?ID = null; - return get_id_cache(name, &id); + for (commands.items) |cmd| { + if (cmd) |p| + if (std.mem.eql(u8, p.name, name)) + return p.id; + } + tp.trace(tp.channel.debug, .{ "command", "get_id", "failed", name }); + return null; } pub fn get_name(id: ID) ?[]const u8 { @@ -174,7 +174,6 @@ pub fn get_id_cache(name: []const u8, id: *?ID) ?ID { return p.id; }; } - tp.trace(tp.channel.debug, .{ "command", "get_id_cache", "failed", name }); return null; } @@ -217,7 +216,7 @@ fn getTargetType(comptime Namespace: type) type { fn getCommands(comptime Namespace: type) []const CmdDef(*getTargetType(Namespace)) { @setEvalBranchQuota(10_000); comptime switch (@typeInfo(Namespace)) { - .@"struct" => |info| { + .Struct => |info| { var count = 0; const Target = getTargetType(Namespace); // @compileLog(Namespace, Target); @@ -258,14 +257,14 @@ pub fn Collection(comptime Namespace: type) type { fields_var[i] = .{ .name = cmd.name, .type = Clsr, - .default_value_ptr = null, + .default_value = null, .is_comptime = false, .alignment = if (@sizeOf(Clsr) > 0) @alignOf(Clsr) else 0, }; } const fields: [cmds.len]std.builtin.Type.StructField = fields_var; const Fields = @Type(.{ - .@"struct" = .{ + .Struct = .{ .is_tuple = false, .layout = .auto, .decls = &.{}, diff --git a/src/completion.zig b/src/completion.zig deleted file mode 100644 index ac8a6ee..0000000 --- a/src/completion.zig +++ /dev/null @@ -1,65 +0,0 @@ -const std = @import("std"); -const tp = @import("thespian"); - -const OutOfMemoryError = error{OutOfMemory}; -const SpawnError = error{ThespianSpawnFailed}; - -pub fn send( - allocator: std.mem.Allocator, - to: tp.pid_ref, - m: anytype, - ctx: anytype, -) (OutOfMemoryError || SpawnError)!void { - return RequestContext(@TypeOf(ctx)).send(allocator, to, ctx, tp.message.fmt(m)); -} - -fn RequestContext(T: type) type { - return struct { - receiver: ReceiverT, - ctx: T, - to: tp.pid, - request: tp.message, - response: ?tp.message, - a: std.mem.Allocator, - - const Self = @This(); - const ReceiverT = tp.Receiver(*@This()); - - fn send(a: std.mem.Allocator, to: tp.pid_ref, ctx: T, request: tp.message) (OutOfMemoryError || SpawnError)!void { - const self = try a.create(@This()); - self.* = .{ - .receiver = undefined, - .ctx = if (@hasDecl(T, "clone")) ctx.clone() else ctx, - .to = to.clone(), - .request = try request.clone(std.heap.c_allocator), - .response = null, - .a = a, - }; - self.receiver = ReceiverT.init(receive_, self); - const proc = try tp.spawn_link(a, self, start, @typeName(@This())); - defer proc.deinit(); - } - - fn deinit(self: *@This()) void { - if (@hasDecl(T, "deinit")) self.ctx.deinit(); - std.heap.c_allocator.free(self.request.buf); - self.to.deinit(); - self.a.destroy(self); - } - - fn start(self: *@This()) tp.result { - _ = tp.set_trap(true); - if (@hasDecl(T, "link")) try self.ctx.link(); - errdefer self.deinit(); - try self.to.link(); - try self.to.send_raw(self.request); - tp.receive(&self.receiver); - } - - fn receive_(self: *@This(), _: tp.pid_ref, m: tp.message) tp.result { - defer self.deinit(); - self.ctx.receive(m) catch |e| return tp.exit_error(e, @errorReturnTrace()); - return tp.exit_normal(); - } - }; -} diff --git a/src/config.zig b/src/config.zig index 22c233c..eb37c6d 100644 --- a/src/config.zig +++ b/src/config.zig @@ -10,27 +10,22 @@ enable_terminal_cursor: bool = true, enable_terminal_color_scheme: bool = builtin.os.tag != .windows, highlight_current_line: bool = true, highlight_current_line_gutter: bool = true, -highlight_columns: []const u8 = "80 100 120", -highlight_columns_alpha: u8 = 240, -highlight_columns_enabled: bool = false, whitespace_mode: []const u8 = "none", inline_diagnostics: bool = true, animation_min_lag: usize = 0, //milliseconds animation_max_lag: usize = 150, //milliseconds enable_format_on_save: bool = false, -restore_last_cursor_position: bool = true, -follow_cursor_on_buffer_switch: bool = false, //scroll cursor into view on buffer switch default_cursor: []const u8 = "default", indent_size: usize = 4, tab_width: usize = 8, top_bar: []const u8 = "tabs", -bottom_bar: []const u8 = "mode file log selection diagnostics keybind branch linenumber clock spacer", +bottom_bar: []const u8 = "mode file log selection diagnostics keybind linenumber clock spacer", show_scrollbars: bool = true, show_fileicons: bool = true, -start_debugger_on_crash: bool = false, +lsp_request_timeout: usize = 10, include_files: []const u8 = "", diff --git a/src/diff.zig b/src/diff.zig index abb2bd6..c73e4b9 100644 --- a/src/diff.zig +++ b/src/diff.zig @@ -38,23 +38,10 @@ pub const AsyncDiffer = struct { } } - fn text_from_root(root: Buffer.Root, eol_mode: Buffer.EolMode) ![]const u8 { - var text = std.ArrayList(u8).init(std.heap.c_allocator); - defer text.deinit(); - try root.store(text.writer(), eol_mode); - return text.toOwnedSlice(); - } - pub const CallBack = fn (from: tp.pid_ref, edits: []Diff) void; pub fn diff(self: @This(), cb: *const CallBack, root_dst: Buffer.Root, root_src: Buffer.Root, eol_mode: Buffer.EolMode) tp.result { - const text_dst = text_from_root(root_dst, eol_mode) catch |e| return tp.exit_error(e, @errorReturnTrace()); - errdefer std.heap.c_allocator.free(text_dst); - const text_src = text_from_root(root_src, eol_mode) catch |e| return tp.exit_error(e, @errorReturnTrace()); - errdefer std.heap.c_allocator.free(text_src); - const text_dst_ptr: usize = if (text_dst.len > 0) @intFromPtr(text_dst.ptr) else 0; - const text_src_ptr: usize = if (text_src.len > 0) @intFromPtr(text_src.ptr) else 0; - if (self.pid) |pid| try pid.send(.{ "D", @intFromPtr(cb), text_dst_ptr, text_dst.len, text_src_ptr, text_src.len }); + if (self.pid) |pid| try pid.send(.{ "D", @intFromPtr(cb), @intFromPtr(root_dst), @intFromPtr(root_src), @intFromEnum(eol_mode) }); } }; @@ -85,29 +72,31 @@ const Process = struct { errdefer self.deinit(); var cb: usize = 0; - var text_dst_ptr: usize = 0; - var text_dst_len: usize = 0; - var text_src_ptr: usize = 0; - var text_src_len: usize = 0; + var root_dst: usize = 0; + var root_src: usize = 0; + var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf); - return if (try m.match(.{ "D", tp.extract(&cb), tp.extract(&text_dst_ptr), tp.extract(&text_dst_len), tp.extract(&text_src_ptr), tp.extract(&text_src_len) })) blk: { - const text_dst = if (text_dst_len > 0) @as([*]const u8, @ptrFromInt(text_dst_ptr))[0..text_dst_len] else ""; - const text_src = if (text_src_len > 0) @as([*]const u8, @ptrFromInt(text_src_ptr))[0..text_src_len] else ""; - break :blk do_diff_async(from, cb, text_dst, text_src) catch |e| tp.exit_error(e, @errorReturnTrace()); - } else if (try m.match(.{"shutdown"})) + return if (try m.match(.{ "D", tp.extract(&cb), tp.extract(&root_dst), tp.extract(&root_src), tp.extract(&eol_mode) })) + do_diff_async(from, cb, root_dst, root_src, @enumFromInt(eol_mode)) catch |e| tp.exit_error(e, @errorReturnTrace()) + else if (try m.match(.{"shutdown"})) tp.exit_normal(); } - fn do_diff_async(from_: tp.pid_ref, cb_addr: usize, text_dst: []const u8, text_src: []const u8) !void { - defer std.heap.c_allocator.free(text_dst); - defer std.heap.c_allocator.free(text_src); + fn do_diff_async(from_: tp.pid_ref, cb_addr: usize, root_dst_addr: usize, root_src_addr: usize, eol_mode: Buffer.EolMode) !void { const cb_: *AsyncDiffer.CallBack = if (cb_addr == 0) return else @ptrFromInt(cb_addr); + const root_dst: Buffer.Root = if (root_dst_addr == 0) return else @ptrFromInt(root_dst_addr); + const root_src: Buffer.Root = if (root_src_addr == 0) return else @ptrFromInt(root_src_addr); var arena_ = std.heap.ArenaAllocator.init(allocator); defer arena_.deinit(); const arena = arena_.allocator(); - const edits = try diff(arena, text_dst, text_src); + var dst = std.ArrayList(u8).init(arena); + var src = std.ArrayList(u8).init(arena); + try root_dst.store(dst.writer(), eol_mode); + try root_src.store(src.writer(), eol_mode); + + const edits = try diff(arena, dst.items, src.items); cb_(from_, edits); } }; diff --git a/src/git.zig b/src/git.zig deleted file mode 100644 index 714ba86..0000000 --- a/src/git.zig +++ /dev/null @@ -1,273 +0,0 @@ -const std = @import("std"); -const tp = @import("thespian"); -const shell = @import("shell"); -const bin_path = @import("bin_path"); - -pub const Error = error{ OutOfMemory, GitNotFound, GitCallFailed }; - -const log_execute = false; - -pub fn workspace_path(context_: usize) Error!void { - const fn_name = @src().fn_name; - try git(context_, .{ "rev-parse", "--show-toplevel" }, struct { - fn result(context: usize, parent: tp.pid_ref, output: []const u8) void { - var it = std.mem.splitScalar(u8, output, '\n'); - while (it.next()) |value| if (value.len > 0) - parent.send(.{ module_name, context, fn_name, value }) catch {}; - } - }.result, exit_null_on_error(fn_name)); -} - -pub fn current_branch(context_: usize) Error!void { - const fn_name = @src().fn_name; - try git(context_, .{ "rev-parse", "--abbrev-ref", "HEAD" }, struct { - fn result(context: usize, parent: tp.pid_ref, output: []const u8) void { - var it = std.mem.splitScalar(u8, output, '\n'); - while (it.next()) |value| if (value.len > 0) { - parent.send(.{ module_name, context, fn_name, value }) catch {}; - return; - }; - } - }.result, exit_null_on_error(fn_name)); -} - -pub fn workspace_files(context: usize) Error!void { - return git_line_output( - context, - @src().fn_name, - .{ "ls-files", "--cached", "--others", "--exclude-standard" }, - ); -} - -pub fn workspace_ignored_files(context: usize) Error!void { - return git_line_output( - context, - @src().fn_name, - .{ "ls-files", "--cached", "--others", "--exclude-standard", "--ignored" }, - ); -} - -const StatusRecordType = enum { - @"#", // header - @"1", // ordinary file - @"2", // rename or copy - u, // unmerged file - @"?", // untracked file - @"!", // ignored file -}; - -pub fn status(context_: usize) Error!void { - const tag = @src().fn_name; - try git_err(context_, .{ - "--no-optional-locks", - "status", - "--porcelain=v2", - "--branch", - "--show-stash", - // "--untracked-files=no", - "--null", - }, struct { - fn result(context: usize, parent: tp.pid_ref, output: []const u8) void { - var it_ = std.mem.splitScalar(u8, output, 0); - while (it_.next()) |line| { - var it = std.mem.splitScalar(u8, line, ' '); - const rec_type = if (it.next()) |type_tag| - std.meta.stringToEnum(StatusRecordType, type_tag) orelse return - else - return; - switch (rec_type) { - .@"#" => { // header - const name = it.next() orelse return; - const value1 = it.next() orelse return; - if (it.next()) |value2| - parent.send(.{ module_name, context, tag, "#", name, value1, value2 }) catch {} - else - parent.send(.{ module_name, context, tag, "#", name, value1 }) catch {}; - }, - .@"1" => { // ordinary file: - const XY = it.next() orelse return; - const sub = it.next() orelse return; - const mH = it.next() orelse return; - const mI = it.next() orelse return; - const mW = it.next() orelse return; - const hH = it.next() orelse return; - const hI = it.next() orelse return; - - var path: std.ArrayListUnmanaged(u8) = .empty; - defer path.deinit(allocator); - while (it.next()) |path_part| { - if (path.items.len > 0) path.append(allocator, ' ') catch return; - path.appendSlice(allocator, path_part) catch return; - } - - parent.send(.{ module_name, context, tag, "1", XY, sub, mH, mI, mW, hH, hI, path.items }) catch {}; - }, - .@"2" => { // rename or copy: - const XY = it.next() orelse return; - const sub = it.next() orelse return; - const mH = it.next() orelse return; - const mI = it.next() orelse return; - const mW = it.next() orelse return; - const hH = it.next() orelse return; - const hI = it.next() orelse return; - const Xscore = it.next() orelse return; - - var path: std.ArrayListUnmanaged(u8) = .empty; - defer path.deinit(allocator); - while (it.next()) |path_part| { - if (path.items.len > 0) path.append(allocator, ' ') catch return; - path.appendSlice(allocator, path_part) catch return; - } - - const origPath = it_.next() orelse return; // NOTE: this is the next zero terminated part - - parent.send(.{ module_name, context, tag, "2", XY, sub, mH, mI, mW, hH, hI, Xscore, path.items, origPath }) catch {}; - }, - .u => { // unmerged file:

- const XY = it.next() orelse return; - const sub = it.next() orelse return; - const m1 = it.next() orelse return; - const m2 = it.next() orelse return; - const m3 = it.next() orelse return; - const mW = it.next() orelse return; - const h1 = it.next() orelse return; - const h2 = it.next() orelse return; - const h3 = it.next() orelse return; - - var path: std.ArrayListUnmanaged(u8) = .empty; - defer path.deinit(allocator); - while (it.next()) |path_part| { - if (path.items.len > 0) path.append(allocator, ' ') catch return; - path.appendSlice(allocator, path_part) catch return; - } - - parent.send(.{ module_name, context, tag, "u", XY, sub, m1, m2, m3, mW, h1, h2, h3, path.items }) catch {}; - }, - .@"?" => { // untracked file: - var path: std.ArrayListUnmanaged(u8) = .empty; - defer path.deinit(allocator); - while (it.next()) |path_part| { - if (path.items.len > 0) path.append(allocator, ' ') catch return; - path.appendSlice(allocator, path_part) catch return; - } - parent.send(.{ module_name, context, tag, "?", path.items }) catch {}; - }, - .@"!" => { // ignored file: - var path: std.ArrayListUnmanaged(u8) = .empty; - defer path.deinit(allocator); - while (it.next()) |path_part| { - if (path.items.len > 0) path.append(allocator, ' ') catch return; - path.appendSlice(allocator, path_part) catch return; - } - parent.send(.{ module_name, context, tag, "!", path.items }) catch {}; - }, - } - // parent.send(.{ module_name, context, tag, value }) catch {}; - } - } - }.result, struct { - fn result(_: usize, _: tp.pid_ref, output: []const u8) void { - var it = std.mem.splitScalar(u8, output, '\n'); - while (it.next()) |line| std.log.err("{s}: {s}", .{ module_name, line }); - } - }.result, exit_null(tag)); -} - -fn git_line_output(context_: usize, comptime tag: []const u8, cmd: anytype) Error!void { - try git_err(context_, cmd, struct { - fn result(context: usize, parent: tp.pid_ref, output: []const u8) void { - var it = std.mem.splitScalar(u8, output, '\n'); - while (it.next()) |value| if (value.len > 0) - parent.send(.{ module_name, context, tag, value }) catch {}; - } - }.result, struct { - fn result(_: usize, _: tp.pid_ref, output: []const u8) void { - var it = std.mem.splitScalar(u8, output, '\n'); - while (it.next()) |line| std.log.err("{s}: {s}", .{ module_name, line }); - } - }.result, exit_null(tag)); -} - -fn git( - context: usize, - cmd: anytype, - out: OutputHandler, - exit: ExitHandler, -) Error!void { - return git_err(context, cmd, out, noop, exit); -} - -fn git_err( - context: usize, - cmd: anytype, - out: OutputHandler, - err: OutputHandler, - exit: ExitHandler, -) Error!void { - const cbor = @import("cbor"); - const git_binary = get_git() orelse return error.GitNotFound; - var buf: std.ArrayListUnmanaged(u8) = .empty; - const writer = buf.writer(allocator); - switch (@typeInfo(@TypeOf(cmd))) { - .@"struct" => |info| if (info.is_tuple) { - try cbor.writeArrayHeader(writer, info.fields.len + 1); - try cbor.writeValue(writer, git_binary); - inline for (info.fields) |f| - try cbor.writeValue(writer, @field(cmd, f.name)); - return shell.execute(allocator, .{ .buf = buf.items }, .{ - .context = context, - .out = to_shell_output_handler(out), - .err = to_shell_output_handler(err), - .exit = exit, - .log_execute = log_execute, - }) catch error.GitCallFailed; - }, - else => {}, - } - @compileError("git command should be a tuple: " ++ @typeName(@TypeOf(cmd))); -} - -fn exit_null(comptime tag: []const u8) shell.ExitHandler { - return struct { - fn exit(context: usize, parent: tp.pid_ref, _: []const u8, _: []const u8, _: i64) void { - parent.send(.{ module_name, context, tag, null }) catch {}; - } - }.exit; -} - -fn exit_null_on_error(comptime tag: []const u8) shell.ExitHandler { - return struct { - fn exit(context: usize, parent: tp.pid_ref, _: []const u8, _: []const u8, exit_code: i64) void { - if (exit_code > 0) - parent.send(.{ module_name, context, tag, null }) catch {}; - } - }.exit; -} - -const OutputHandler = fn (context: usize, parent: tp.pid_ref, output: []const u8) void; -const ExitHandler = shell.ExitHandler; - -fn to_shell_output_handler(handler: anytype) shell.OutputHandler { - return struct { - fn out(context: usize, parent: tp.pid_ref, _: []const u8, output: []const u8) void { - handler(context, parent, output); - } - }.out; -} - -fn noop(_: usize, _: tp.pid_ref, _: []const u8) void {} - -var git_path: ?struct { - path: ?[:0]const u8 = null, -} = null; - -const allocator = std.heap.c_allocator; - -fn get_git() ?[]const u8 { - if (git_path) |p| return p.path; - const path = bin_path.find_binary_in_path(allocator, module_name) catch null; - git_path = .{ .path = path }; - return path; -} - -const module_name = @typeName(@This()); diff --git a/src/keybind/builtin/emacs.json b/src/keybind/builtin/emacs.json index b2a9c71..b7349ec 100644 --- a/src/keybind/builtin/emacs.json +++ b/src/keybind/builtin/emacs.json @@ -20,7 +20,6 @@ "press": [ ["ctrl+g", "cancel"], ["ctrl+_", "undo"], - ["\u001f", "undo"], ["ctrl+k", ["select_end"], ["cut"]], ["ctrl+w", "cut"], @@ -48,17 +47,6 @@ ["ctrl+x ctrl+r", "open_recent"], ["ctrl+space", "enter_mode", "select"], - ["alt+0", "add_integer_argument_digit", 0], - ["alt+1", "add_integer_argument_digit", 1], - ["alt+2", "add_integer_argument_digit", 2], - ["alt+3", "add_integer_argument_digit", 3], - ["alt+4", "add_integer_argument_digit", 4], - ["alt+5", "add_integer_argument_digit", 5], - ["alt+6", "add_integer_argument_digit", 6], - ["alt+7", "add_integer_argument_digit", 7], - ["alt+8", "add_integer_argument_digit", 8], - ["alt+9", "add_integer_argument_digit", 9], - ["ctrl+c l = =", "format"], ["ctrl+c l = r", "format"], ["ctrl+c l g g", "goto_definition"], @@ -67,7 +55,6 @@ ["ctrl+c l g r", "references"], ["ctrl+c l h h", "hover"], ["ctrl+c l r r", "rename_symbol"], - ["ctrl+c f", "copy_file_name"], ["super+l = =", "format"], ["super+l = r", "format"], diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 2a6641b..38befc3 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -10,7 +10,6 @@ ["ctrl+w", "quit"], ["ctrl+o", "open_file"], ["ctrl+e", "open_recent"], - ["ctrl+shift+f5", "reload_file"], ["ctrl+k ctrl+t", "change_theme"], ["ctrl+shift+p", "open_command_palette"], ["ctrl+shift+q", "quit_without_saving"], @@ -38,8 +37,7 @@ ["f5", ["create_scratch_buffer", "*test*"], ["shell_execute_insert", "zig", "build", "test"]], ["f7", ["create_scratch_buffer", "*build*"], ["shell_execute_insert", "zig", "build"]], ["ctrl+f6", "open_version_info"], - ["alt+d", ["shell_execute_insert", "date", "--iso-8601"]], - ["ctrl+alt+shift+d", ["shell_execute_insert", "date", "--iso-8601=seconds"]] + ["alt+d", ["shell_execute_log", "date"]] ] }, "normal": { @@ -52,12 +50,10 @@ ["ctrl+l", "scroll_view_center_cycle"], ["ctrl+n", "goto_next_match"], ["ctrl+p", "goto_prev_match"], - ["ctrl+b", "move_to_char", "move_or_select_to_char_left"], - ["ctrl+t", "move_to_char", "move_or_select_to_char_right"], + ["ctrl+b", "move_to_char", "left"], + ["ctrl+t", "move_to_char", "right"], ["ctrl+x", "cut"], ["ctrl+c", "copy"], - ["alt+C", "copy_file_name"], - ["ctrl+k alt+c", "copy_file_name", "file_name_only"], ["ctrl+v", "system_paste"], ["ctrl+u", "pop_cursor"], ["ctrl+k m", "change_file_type"], @@ -159,7 +155,6 @@ ["f9", "theme_prev"], ["f10", "theme_next"], ["f11", "toggle_panel"], - ["shift+f11", "toggle_highlight_columns"], ["ctrl+f11", "toggle_inspector_view"], ["f12", "goto_definition"], ["f34", "toggle_whitespace_mode"], @@ -195,17 +190,6 @@ ["«", "smart_insert_pair", "«", "»"], ["»", "smart_insert_pair_close", "«", "»"], - ["alt+0", "add_integer_argument_digit", 0], - ["alt+1", "add_integer_argument_digit", 1], - ["alt+2", "add_integer_argument_digit", 2], - ["alt+3", "add_integer_argument_digit", 3], - ["alt+4", "add_integer_argument_digit", 4], - ["alt+5", "add_integer_argument_digit", 5], - ["alt+6", "add_integer_argument_digit", 6], - ["alt+7", "add_integer_argument_digit", 7], - ["alt+8", "add_integer_argument_digit", 8], - ["alt+9", "add_integer_argument_digit", 9], - ["left_control", "enable_jump_mode"], ["right_control", "enable_jump_mode"], ["left_alt", "enable_fast_scroll"], @@ -301,8 +285,6 @@ ["down", "palette_menu_down"], ["page_up", "palette_menu_pageup"], ["page_down", "palette_menu_pagedown"], - ["home", "palette_menu_top"], - ["end", "palette_menu_bottom"], ["enter", "palette_menu_activate"], ["delete", "palette_menu_delete_item"], ["backspace", "overlay_delete_backwards"] @@ -405,7 +387,7 @@ ["f15", "goto_prev_match"], ["f9", "theme_prev"], ["f10", "theme_next"], - ["escape", "mini_mode_cancel"], + ["escape", "exit_mini_mode"], ["enter", "mini_mode_select"], ["backspace", "mini_mode_delete_backwards"] ] diff --git a/src/keybind/builtin/helix.json b/src/keybind/builtin/helix.json index 2129459..2035724 100644 --- a/src/keybind/builtin/helix.json +++ b/src/keybind/builtin/helix.json @@ -21,7 +21,7 @@ ["ctrl+a", "increment"], ["ctrl+x", "decrement"], - ["ctrl+^", "open_previous_file"], + ["ctrl+shift+6", "open_previous_file"], ["alt+.", "repeat_last_motion"], ["alt+`", "to_upper"], @@ -50,54 +50,56 @@ ["alt+u", "undo"], ["alt+,", "remove_primary_selection"], - ["alt+C", "copy_selection_on_next_line"], - ["alt+I", "select_all_children"], + ["alt+shift+c", "copy_selection_on_next_line"], + ["alt+shift+i", "select_all_children"], ["alt+shift+down", "select_all_children"], - ["alt+U", "redo"], - ["alt+J", "join_selections_space"], - ["alt+(", "rotate_selection_contents_backward"], ["alt+)", "rotate_selection_contents_forward"], - ["alt+|", "shell_pipe_to"], - ["alt+!", "shell_append_output"], + ["alt+shift+u", "redo"], + ["alt+shift+j", "join_selections_space"], + ["alt+shift+(", "rotate_selection_contents_backward"], + ["alt+shift+)", "rotate_selection_contents_forward"], + ["alt+shift+\\", "shell_pipe_to"], + ["alt+shift+1", "shell_append_output"], - ["~", "switch_case"], - ["T", "till_prev_char"], - ["F", "move_to_char", "move_to_char_left"], - ["W", "move_next_long_word_start"], - ["B", "move_prev_long_word_start"], - ["E", "move_next_long_word_end"], + ["shift+`", "switch_case"], + ["shift+t", "till_prev_char"], + ["shift+f", "move_to_char", false], + ["shift+w", "move_next_long_word_start"], + ["shift+b", "move_prev_long_word_start"], + ["shift+e", "move_next_long_word_end"], - ["I", ["enter_mode", "insert"], ["smart_move_begin"]], - ["A", ["enter_mode", "insert"], ["move_end"]], - ["O", ["enter_mode", "insert"], ["smart_insert_line_before"]], + ["shift+i", ["smart_move_begin"], ["enter_mode", "insert"]], + ["shift+a", ["move_end"], ["enter_mode", "insert"]], + ["shift+o", ["smart_insert_line_before"], ["enter_mode", "insert"]], - ["C", "copy_selection_on_next_line"], - ["S", "split_selection"], - ["X", "extend_to_line_bounds"], - ["?", "rfind"], - ["N", "goto_prev_match"], - ["*", "search_selection"], + ["shift+c", "copy_selection_on_next_line"], + ["shift+s", "split_selection"], + ["shift+x", "extend_to_line_bounds"], + ["shift+/", "rfind"], + ["shift+n", "goto_prev_match"], + ["shift+8", "search_selection"], - ["U", "redo"], - ["P", "paste"], - ["Q", "replay_macro"], + ["shift+u", "redo"], + ["shift+p", "paste"], + ["shift+q", "replay_macro"], - [">", "indent"], - ["<", "unindent"], + ["shift+.", "indent"], + ["shift+,", "unindent"], - ["J", "join_selections"], + ["shift+j", "join_selections"], [":", "open_command_palette"], + ["shift+;", "open_command_palette"], - ["&", "align_selections"], - ["_", "trim_selections"], + ["shift+7", "align_selections"], + ["shift+-", "trim_selections"], - ["(", "rotate_selections_backward"], - [")", "rotate_selections_forward"], + ["shift+9", "rotate_selections_backward"], + ["shift+0", "rotate_selections_forward"], - ["\"", "select_register"], - ["|", "shell_pipe"], - ["!", "shell_insert_output"], - ["$", "shell_keep_pipe"], + ["shift+\"", "select_register"], + ["shift+\\", "shell_pipe"], + ["shift+1", "shell_insert_output"], + ["shift+4", "shell_keep_pipe"], ["h", "move_left"], ["j", "move_down"], @@ -105,7 +107,7 @@ ["l", "move_right"], ["t", "find_till_char"], - ["f", "move_to_char", "move_to_char_right"], + ["f", "move_to_char", true], ["`", "to_lower"], @@ -118,7 +120,7 @@ ["v", "enter_mode", "select"], - ["g g", "goto_line_vim"], + ["g g", "move_buffer_begin"], ["g e", "move_buffer_end"], ["g f", "goto_file"], ["g h", "move_begin"], @@ -138,14 +140,14 @@ ["g k", "goto_previous_buffer"], ["g .", "goto_last_modification"], ["g w", "goto_word"], - ["g D", "goto_declaration"], + ["g shift+d", "goto_declaration"], ["i", "enter_mode", "insert"], - ["a", ["enter_mode", "insert"], ["move_right"]], - ["o", ["enter_mode", "insert"], ["smart_insert_line_after"]], + ["a", ["move_right"], ["enter_mode", "insert"]], + ["o", ["smart_insert_line_after"], ["enter_mode", "insert"]], - ["d", "cut_forward_internal_inclusive"], - ["c", ["enable_selection"], ["enter_mode", "insert"], ["cut_forward_internal_inclusive"]], + ["d", "cut"], + ["c", ["cut"], ["enter_mode", "insert"]], ["s", "select_regex"], [";", "collapse_selections"], @@ -158,9 +160,9 @@ ["m a", "select_textobject_around"], ["m i", "select_textobject_inner"], - ["[ D", "goto_first_diag"], - ["[ G", "goto_first_change"], - ["[ T", "goto_prev_test"], + ["[ shift+d", "goto_first_diag"], + ["[ shift+g", "goto_first_change"], + ["[ shift+t", "goto_prev_test"], ["[ d", "goto_prev_diagnostic"], ["[ g", "goto_prev_change"], ["[ f", "goto_prev_function"], @@ -171,9 +173,9 @@ ["[ p", "goto_prev_paragraph"], ["[ space", "add_newline_above"], - ["] D", "goto_last_diag"], - ["] G", "goto_last_change"], - ["] T", "goto_next_test"], + ["] shift+d", "goto_last_diag"], + ["] shift+g", "goto_last_change"], + ["] shift+t", "goto_next_test"], ["] d", "goto_next_diagnostic"], ["] g", "goto_next_change"], ["] f", "goto_next_function"], @@ -188,7 +190,7 @@ ["n", "goto_next_match"], ["u", "undo"], - ["y", ["enable_selection"], ["copy_helix"], ["enter_mode", "normal"]], + ["y", "copy"], ["p", "paste_after"], ["q", "record_macro"], @@ -202,13 +204,13 @@ ["page_up", "move_scroll_page_up"], ["page_down", "move_scroll_page_down"], - ["space F", "find_file"], - ["space S", "workspace_symbol_picker"], - ["space D", "workspace_diagnostics_picker"], - ["space P", "system_paste"], - ["space R", "replace_selections_with_clipboard"], - ["space ?", "open_command_palette"], - ["space f", "find_file"], + ["space shift+f", "file_picker_in_current_directory"], + ["space shift+s", "workspace_symbol_picker"], + ["space shift+d", "workspace_diagnostics_picker"], + ["space shift+p", "system_paste"], + ["space shift+r", "replace_selections_with_clipboard"], + ["space shift+/", "open_command_palette"], + ["space f", "file_picker"], ["space b", "buffer_picker"], ["space j", "jumplist_picker"], ["space s", "symbol_picker"], @@ -223,24 +225,21 @@ ["space h", "select_references_to_symbol_under_cursor"], ["space c", "toggle_comment"], - ["0", "add_integer_argument_digit", 0], - ["1", "add_integer_argument_digit", 1], - ["2", "add_integer_argument_digit", 2], - ["3", "add_integer_argument_digit", 3], - ["4", "add_integer_argument_digit", 4], - ["5", "add_integer_argument_digit", 5], - ["6", "add_integer_argument_digit", 6], - ["7", "add_integer_argument_digit", 7], - ["8", "add_integer_argument_digit", 8], - ["9", "add_integer_argument_digit", 9] + ["1", "add_count", 1], + ["2", "add_count", 2], + ["3", "add_count", 3], + ["4", "add_count", 4], + ["5", "add_count", 5], + ["6", "add_count", 6], + ["7", "add_count", 7], + ["8", "add_count", 8], + ["9", "add_count", 9] ] }, "insert": { "name": "INS", "line_numbers": "absolute", "cursor": "beam", - "init_command": ["pause_undo_history"], - "deinit_command": ["resume_undo_history"], "press": [ ["ctrl+u", "move_scroll_page_up"], ["ctrl+d", "move_scroll_page_down"], @@ -255,7 +254,6 @@ "line_numbers": "relative", "cursor": "block", "selection": "inclusive", - "init_command": ["enable_selection"], "press": [ ["ctrl+b", "select_page_up"], ["ctrl+f", "select_page_down"], @@ -271,14 +269,14 @@ ["ctrl+a", "increment"], ["ctrl+x", "decrement"], - ["ctrl+^", "open_previous_file"], + ["ctrl+shift+6", "open_previous_file"], ["alt+.", "repeat_last_motion"], ["alt+`", "switch_to_uppercase"], ["alt+d", "delete_backward"], - ["alt+c", ["enter_mode", "insert"], ["delete_backward"]], + ["alt+c", ["delete_backward"], ["enter_mode", "insert"]], ["alt+s", "split_selection_on_newline"], ["alt+-", "merge_selections"], @@ -304,83 +302,84 @@ ["alt+,", "remove_primary_selection"], - ["alt+C", "copy_selection_on_next_line"], + ["alt+shift+c", "copy_selection_on_next_line"], - ["alt+I", "select_all_children"], + ["alt+shift+i", "select_all_children"], ["alt+shift+down", "select_all_children"], - ["alt+U", "redo"], + ["alt+shift+u", "redo"], - ["alt+J", "join_selections_space"], + ["alt+shift+j", "join_selections_space"], - ["alt+(", "rotate_selection_contents_backward"], - ["alt+)", "rotate_selection_contents_forward"], + ["alt+shift+(", "rotate_selection_contents_backward"], + ["alt+shift+)", "rotate_selection_contents_forward"], - ["alt+|", "shell_pipe_to"], - ["alt+!", "shell_append_output"], + ["alt+shift+\\", "shell_pipe_to"], + ["alt+shift+1", "shell_append_output"], - ["~", "switch_case"], + ["shift+`", "switch_case"], - ["T", "extend_till_prev_char"], - ["F", "move_to_char", "select_to_char_left_vim"], + ["shift+t", "extend_till_prev_char"], + ["shift+f", "extend_prev_char"], - ["W", "extend_next_long_word_start"], - ["B", "extend_prev_long_word_start"], - ["E", "extend_next_long_word_end"], + ["shift+w", "extend_next_long_word_start"], + ["shift+b", "extend_prev_long_word_start"], + ["shift+e", "extend_next_long_word_end"], - ["G", "move_buffer_end_or_count_line"], + ["shift+g", "move_buffer_end_or_count_line"], - ["I", ["enter_mode", "insert"], ["smart_move_begin"]], - ["A", ["enter_mode", "insert"], ["move_end"]], + ["shift+i", ["smart_move_begin"], ["enter_mode", "insert"]], + ["shift+a", ["move_end"], ["enter_mode", "insert"]], - ["O", ["enter_mode", "insert"], ["smart_insert_line_before"]], + ["shift+o", ["smart_insert_line_before"], ["enter_mode", "insert"]], - ["C", "copy_selection_on_next_line"], + ["shift+c", "copy_selection_on_next_line"], - ["S", "split_selection"], + ["shift+s", "split_selection"], - ["X", "extend_to_line_bounds"], + ["shift+x", "extend_to_line_bounds"], - ["?", "rfind"], + ["shift+/", "rfind"], - ["N", "extend_search_next"], - ["*", "extend_search_prev"], + ["shift+n", "extend_search_next"], + ["shift+8", "extend_search_prev"], - ["U", "redo"], + ["shift+u", "redo"], - ["P", "paste"], + ["shift+p", "paste"], - ["Q", "replay_macro"], + ["shift+q", "replay_macro"], - [">", "indent"], - ["<", "unindent"], + ["shift+.", "indent"], + ["shift+,", "unindent"], - ["J", "join_selections"], + ["shift+j", "join_selections"], [":", "open_command_palette"], + ["shift+;", "open_command_palette"], - ["&", "align_selections"], - ["_", "trim_selections"], + ["shift+7", "align_selections"], + ["shift+-", "trim_selections"], - ["(", "rotate_selections_backward"], - [")", "rotate_selections_forward"], + ["shift+9", "rotate_selections_backward"], + ["shift+0", "rotate_selections_forward"], - ["\"", "select_register"], - ["|", "shell_pipe"], - ["!", "shell_insert_output"], - ["$", "shell_keep_pipe"], + ["shift+'", "select_register"], + ["shift+\\", "shell_pipe"], + ["shift+1", "shell_insert_output"], + ["shift+4", "shell_keep_pipe"], - ["h", "select_left_helix"], + ["h", "select_left"], ["j", "select_down"], ["k", "select_up"], - ["l", "select_right_helix"], + ["l", "select_right"], ["left", "select_left"], ["down", "select_down"], ["up", "select_up"], ["right", "select_right"], ["t", "extend_till_char"], - ["f", "move_to_char", "select_to_char_right_helix"], + ["f", "extend_next_char"], ["`", "switch_to_lowercase"], @@ -391,13 +390,13 @@ ["b", "extend_pre_word_start"], ["e", "extend_next_word_end"], - ["v", "enter_mode", "normal"], + ["shift+v", "enter_mode", "normal"], ["g g", "move_buffer_begin"], ["g e", "move_buffer_end"], ["g f", "goto_file"], ["g h", "move_begin"], - ["g l", "select_end"], + ["g l", "move_end"], ["g s", "smart_move_begin"], ["g d", "goto_definition"], ["g y", "goto_type_definition"], @@ -413,14 +412,14 @@ ["g k", "goto_previous_buffer"], ["g .", "goto_last_modification"], ["g w", "goto_word"], - ["g D", "goto_declaration"], + ["g shift+d", "goto_declaration"], ["i", "enter_mode", "insert"], - ["a", ["enter_mode", "insert"], ["move_right"]], - ["o", ["enter_mode", "insert"], ["smart_insert_line_after"]], + ["a", ["move_right"], ["enter_mode", "insert"]], + ["o", ["smart_insert_line_after"], ["enter_mode", "insert"]], - ["d", ["cut"], ["enter_mode", "normal"]], - ["c", ["enter_mode", "insert"], ["cut"]], + ["d", "cut"], + ["c", ["cut"], ["enter_mode", "insert"]], ["s", "select_regex"], [";", "collapse_selections"], @@ -434,9 +433,9 @@ ["m a", "select_textobject_around"], ["m i", "select_textobject_inner"], - ["[ D", "goto_first_diag"], - ["[ G", "goto_first_change"], - ["[ T", "goto_prev_test"], + ["[ shift+d", "goto_first_diag"], + ["[ shift+g", "goto_first_change"], + ["[ shift+t", "goto_prev_test"], ["[ d", "goto_prev_diagnostic"], ["[ g", "goto_prev_change"], @@ -465,7 +464,7 @@ ["n", "goto_next_match"], ["u", "undo"], - ["y", ["copy_helix"], ["enter_mode", "normal"]], + ["y", "copy"], ["p", "paste_after"], ["q", "record_macro"], @@ -476,12 +475,12 @@ ["escape", "enter_mode", "normal"], - ["space F", "file_picker_in_current_directory"], - ["space S", "workspace_symbol_picker"], - ["space D", "workspace_diagnostics_picker"], - ["space P", "system_paste"], - ["space R", "replace_selections_with_clipboard"], - ["space ?", "open_command_palette"], + ["space shift+f", "file_picker_in_current_directory"], + ["space shift+s", "workspace_symbol_picker"], + ["space shift+d", "workspace_diagnostics_picker"], + ["space shift+p", "system_paste"], + ["space shift+r", "replace_selections_with_clipboard"], + ["space shift+/", "open_command_palette"], ["space f", "file_picker"], ["space b", "buffer_picker"], @@ -498,16 +497,15 @@ ["space h", "select_references_to_symbol_under_cursor"], ["space c", "toggle_comment"], - ["0", "add_integer_argument_digit", 0], - ["1", "add_integer_argument_digit", 1], - ["2", "add_integer_argument_digit", 2], - ["3", "add_integer_argument_digit", 3], - ["4", "add_integer_argument_digit", 4], - ["5", "add_integer_argument_digit", 5], - ["6", "add_integer_argument_digit", 6], - ["7", "add_integer_argument_digit", 7], - ["8", "add_integer_argument_digit", 8], - ["9", "add_integer_argument_digit", 9] + ["1", "add_count", 1], + ["2", "add_count", 2], + ["3", "add_count", 3], + ["4", "add_count", 4], + ["5", "add_count", 5], + ["6", "add_count", 6], + ["7", "add_count", 7], + ["8", "add_count", 8], + ["9", "add_count", 9] ] }, "home": { diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index 19f1685..87438f8 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -17,7 +17,7 @@ ["B", "move_word_left"], ["e", "move_word_right_end_vim"], ["x", "cut_forward_internal"], - ["s", ["enter_mode", "insert"], ["cut_forward_internal"]], + ["s", ["cut_forward_internal"], ["enter_mode", "insert"]], ["u", "undo"], ["j", "move_down_vim"], @@ -26,24 +26,22 @@ ["h", "move_left_vim"], ["", "move_right_vim"], - ["J", "join_next_line"], - ["i", "enter_mode", "insert"], - ["a", ["enter_mode", "insert"], ["move_right"]], - ["I", ["enter_mode", "insert"], ["smart_move_begin"]], - ["A", ["enter_mode", "insert"], ["move_end"]], - ["o", ["enter_mode", "insert"], ["smart_insert_line_after"]], - ["O", ["enter_mode", "insert"], ["smart_insert_line_before"]], + ["a", ["move_right"], ["enter_mode", "insert"]], + ["I", ["smart_move_begin"], ["enter_mode", "insert"]], + ["A", ["move_end"], ["enter_mode", "insert"]], + ["o", ["smart_insert_line_after"], ["enter_mode", "insert"]], + ["O", ["smart_insert_line_before"], ["enter_mode", "insert"]], - ["", "indent"], - ["", "unindent"], + ["", "indent"], + ["", "unindent"], ["v", "enter_mode", "visual"], ["V", ["enter_mode", "visual line"], ["select_line_vim"]], - ["", "enter_mode", "visual block"], ["n", "goto_next_match"], ["N", "goto_prev_match"], + ["0", "move_begin"], ["^", "smart_move_begin"], ["$", "move_end"], [":", "open_command_palette"], @@ -54,7 +52,7 @@ ["gd", "goto_definition"], ["gi", "goto_implementation"], ["gy", "goto_type_definition"], - ["gg", "goto_line_vim"], + ["gg", "move_buffer_begin"], ["grn", "rename_symbol"], ["gD", "goto_declaration"], ["G", "move_buffer_end"], @@ -63,15 +61,13 @@ ["dw", "cut_word_right_vim"], ["db", "cut_word_left_vim"], ["dd", "cut_internal_vim"], - ["dG", "cut_buffer_end"], - ["dgg", "cut_buffer_begin"], ["\"_dd", "delete_line"], - ["cc", ["enter_mode", "insert"], ["cut_internal_vim"]], - ["C", ["enter_mode", "insert"], ["cut_to_end_vim"]], + ["cc", ["cut_internal_vim"], ["enter_mode", "insert"]], + ["C", ["cut_to_end_vim"], ["enter_mode", "insert"]], ["D", "cut_to_end_vim"], - ["cw", ["enter_mode", "insert"], ["cut_word_right_vim"]], - ["cb", ["enter_mode", "insert"], ["cut_word_left_vim"]], + ["cw", ["cut_word_right_vim"], ["enter_mode", "insert"]], + ["cb", ["cut_word_left_vim"], ["enter_mode", "insert"]], ["yy", ["copy_line_internal_vim"], ["cancel"]], @@ -87,31 +83,14 @@ ["", "redo"], ["/", "find"], - ["*", "find_word_at_cursor"], ["", "TODO"], - ["F", "move_to_char", "move_to_char_left"], - ["f", "move_to_char", "move_to_char_right"], - ["T", "move_to_char", "move_till_char_left"], - ["t", "move_to_char", "move_till_char_right"], + ["F", "move_to_char", "left"], + ["f", "move_to_char", "right"], ["", ["move_down"], ["move_begin"]], - ["", ["move_down"], ["move_begin"]], - - ["gt", "next_tab"], - ["gT", "previous_tab"], - - ["0", "move_begin_or_add_integer_argument_zero"], - ["1", "add_integer_argument_digit", 1], - ["2", "add_integer_argument_digit", 2], - ["3", "add_integer_argument_digit", 3], - ["4", "add_integer_argument_digit", 4], - ["5", "add_integer_argument_digit", 5], - ["6", "add_integer_argument_digit", 6], - ["7", "add_integer_argument_digit", 7], - ["8", "add_integer_argument_digit", 8], - ["9", "add_integer_argument_digit", 9] + ["", ["move_down"], ["move_begin"]] ] }, "visual": { @@ -121,7 +100,6 @@ "line_numbers": "relative", "cursor": "block", "selection": "normal", - "init_command": ["enable_selection"], "press": [ ["", ["cancel"], ["enter_mode", "normal"]], ["k", "select_up"], @@ -129,23 +107,15 @@ ["h", "select_left"], ["l", "select_right"], - ["gg", "select_buffer_begin"], - ["G", "select_buffer_end"], - ["b", "select_word_left_vim"], ["w", "select_word_right_vim"], ["W", "select_word_right"], ["B", "select_word_left"], ["e", "select_word_right_end_vim"], + ["0", "move_begin"], ["^", "smart_move_begin"], - ["$", "select_end"], - [":", "open_command_palette"], - - ["f", "move_to_char", "select_to_char_right"], - ["F", "move_to_char", "select_to_char_left_vim"], - ["t", "move_to_char", "select_till_char_right"], - ["T", "move_to_char", "select_till_char_left_vim"], + ["$", "move_end"], ["p", ["paste_internal_vim"], ["enter_mode", "normal"]], ["P", ["paste_internal_vim"], ["enter_mode", "normal"]], @@ -161,22 +131,11 @@ ["x", ["cut_forward_internal"], ["enter_mode", "normal"]], ["d", ["cut_forward_internal"], ["enter_mode", "normal"]], - ["s", ["enter_mode", "insert"], ["cut_forward_internal"]], + ["s", ["cut_forward_internal"], ["enter_mode", "insert"]], - ["c", ["enter_mode", "insert"], ["cut_forward_internal"]], - ["C", ["enter_mode", "insert"], ["cut_to_end_vim"]], - ["D", "cut_to_end_vim"], - - ["0", "move_begin_or_add_integer_argument_zero"], - ["1", "add_integer_argument_digit", 1], - ["2", "add_integer_argument_digit", 2], - ["3", "add_integer_argument_digit", 3], - ["4", "add_integer_argument_digit", 4], - ["5", "add_integer_argument_digit", 5], - ["6", "add_integer_argument_digit", 6], - ["7", "add_integer_argument_digit", 7], - ["8", "add_integer_argument_digit", 8], - ["9", "add_integer_argument_digit", 9] + ["c", ["cut_forward_internal"], ["enter_mode", "insert"]], + ["C", ["cut_to_end_vim"], ["enter_mode", "insert"]], + ["D", "cut_to_end_vim"] ] }, "visual line": { @@ -191,9 +150,9 @@ ["k", "select_up"], ["j", "select_down"], + ["0", "move_begin"], ["^", "smart_move_begin"], ["$", "move_end"], - [":", "open_command_palette"], ["p", ["paste_internal_vim"], ["enter_mode", "normal"]], ["P", ["paste_internal_vim"], ["enter_mode", "normal"]], @@ -208,36 +167,11 @@ ["x", ["cut_internal_vim"], ["enter_mode", "normal"]], ["d", ["cut_internal_vim"], ["enter_mode", "normal"]], - ["s", ["enter_mode", "insert"], ["cut_internal_vim"]], + ["s", ["cut_internal_vim"], ["enter_mode", "insert"]], - ["c", ["enter_mode", "insert"], ["cut_internal_vim"]], - ["C", ["enter_mode", "insert"], ["cut_to_end_vim"]], - ["D", "cut_to_end_vim"], - - ["0", "move_begin_or_add_integer_argument_zero"], - ["1", "add_integer_argument_digit", 1], - ["2", "add_integer_argument_digit", 2], - ["3", "add_integer_argument_digit", 3], - ["4", "add_integer_argument_digit", 4], - ["5", "add_integer_argument_digit", 5], - ["6", "add_integer_argument_digit", 6], - ["7", "add_integer_argument_digit", 7], - ["8", "add_integer_argument_digit", 8], - ["9", "add_integer_argument_digit", 9] - ] - }, - "visual block": { - "syntax": "vim", - "on_match_failure": "ignore", - "name": "VISUAL BLOCK", - "inherit": "visual", - "line_numbers": "relative", - "cursor": "block", - "selection": "normal", - "init_command": ["enable_selection"], - "press": [ - ["k", "add_cursor_up"], - ["j", "add_cursor_down"] + ["c", ["cut_internal_vim"], ["enter_mode", "insert"]], + ["C", ["cut_to_end_vim"], ["enter_mode", "insert"]], + ["D", "cut_to_end_vim"] ] }, "insert": { @@ -245,8 +179,6 @@ "name": "INSERT", "line_numbers": "absolute", "cursor": "beam", - "init_command": ["pause_undo_history"], - "deinit_command": ["resume_undo_history"], "press": [ ["", ["move_left_vim"], ["enter_mode", "normal"]], ["", "delete_forward"], diff --git a/src/keybind/keybind.zig b/src/keybind/keybind.zig index 0a69f6c..77773b1 100644 --- a/src/keybind/keybind.zig +++ b/src/keybind/keybind.zig @@ -25,31 +25,6 @@ const builtin_keybinds = std.static_string_map.StaticStringMap([]const u8).initC .{ "emacs", @embedFile("builtin/emacs.json") }, }); -var integer_argument: ?usize = null; - -var commands: Commands = undefined; -const Commands = command.Collection(struct { - pub const Target = void; - const Ctx = command.Context; - const Meta = command.Metadata; - const Result = command.Result; - - pub fn add_integer_argument_digit(_: *void, ctx: Ctx) Result { - var digit: usize = undefined; - if (!try ctx.args.match(.{tp.extract(&digit)})) - return error.InvalidIntegerParameterArgument; - if (digit > 9) - return error.InvalidIntegerParameterDigit; - integer_argument = if (integer_argument) |x| x * 10 + digit else digit; - } - pub const add_integer_argument_digit_meta: Meta = .{ .arguments = &.{.integer} }; -}); - -pub fn init() !void { - var v: void = {}; - try commands.init(&v); -} - pub fn mode(mode_name: []const u8, allocator: std.mem.Allocator, opts: anytype) !Mode { return Handler.create(mode_name, allocator, opts) catch |e| switch (e) { error.NotFound => return error.Stop, @@ -86,8 +61,6 @@ const Handler = struct { .line_numbers = self.bindings.line_numbers, .cursor_shape = self.bindings.cursor_shape, .selection_style = self.bindings.selection_style, - .init_command = self.bindings.init_command, - .deinit_command = self.bindings.deinit_command, }; } pub fn deinit(self: *@This()) void { @@ -109,20 +82,8 @@ pub const Mode = struct { keybind_hints: *const KeybindHints, cursor_shape: ?CursorShape = null, selection_style: SelectionStyle, - init_command: ?Command = null, - deinit_command: ?Command = null, - initialized: bool = false, - - pub fn run_init(self: *Mode) void { - if (self.initialized) return; - self.initialized = true; - clear_integer_argument(); - if (self.init_command) |init_command| init_command.execute_const(); - } pub fn deinit(self: *Mode) void { - if (self.deinit_command) |deinit_command| - deinit_command.execute_const(); self.allocator.free(self.mode); self.input_handler.deinit(); if (self.event_handler) |eh| eh.deinit(); @@ -177,11 +138,19 @@ fn get_or_load_namespace(namespace_name: []const u8) LoadError!*const Namespace pub fn set_namespace(namespace_name: []const u8) LoadError!void { const new_namespace = try get_or_load_namespace(namespace_name); if (globals.current_namespace) |old_namespace| - if (old_namespace.deinit_command) |deinit_command| - deinit_command.execute_const(); + if (old_namespace.deinit_command) |deinit| + deinit.execute_const() catch |e| { + const logger = log.logger("keybind"); + logger.print_err("deinit_command", "ERROR: {s} {s}", .{ deinit.command, @errorName(e) }); + logger.deinit(); + }; globals.current_namespace = new_namespace; - if (new_namespace.init_command) |init_command| - init_command.execute_const(); + if (new_namespace.init_command) |init| + init.execute_const() catch |e| { + const logger = log.logger("keybind"); + logger.print_err("init_command", "ERROR: {s} {s}", .{ init.command, @errorName(e) }); + logger.deinit(); + }; } fn get_mode_binding_set(mode_name: []const u8, insert_command: []const u8) LoadError!*const BindingSet { @@ -198,7 +167,7 @@ fn get_mode_binding_set(mode_name: []const u8, insert_command: []const u8) LoadE return binding_set; } -pub const LoadError = (error{ NotFound, NotAnObject } || std.json.ParseError(std.json.Scanner) || parse_flow.ParseError || parse_vim.ParseError || std.json.ParseFromValueError); +const LoadError = (error{ NotFound, NotAnObject } || std.json.ParseError(std.json.Scanner) || parse_flow.ParseError || parse_vim.ParseError || std.json.ParseFromValueError); ///A collection of modes that represent a switchable editor emulation const Namespace = struct { @@ -295,29 +264,13 @@ const Command = struct { }; var buf: [2048]u8 = undefined; @memcpy(buf[0..self.args.len], self.args); - if (integer_argument) |int_arg| { - if (cbor.match(self.args, .{}) catch false and has_integer_argument(id)) { - integer_argument = null; - try command.execute(id, command.fmt(.{int_arg})); - return; - } - } try command.execute(id, .{ .args = .{ .buf = buf[0..self.args.len] } }); } - fn execute_const(self: *const @This()) void { + fn execute_const(self: *const @This()) !void { var buf: [2048]u8 = undefined; @memcpy(buf[0..self.args.len], self.args); - command.executeName(self.command, .{ .args = .{ .buf = buf[0..self.args.len] } }) catch |e| { - const logger = log.logger("keybind"); - logger.print_err("init/deinit_command", "ERROR: {s} {s}", .{ self.command, @errorName(e) }); - logger.deinit(); - }; - } - - fn has_integer_argument(id: command.ID) bool { - const args = command.get_arguments(id) orelse return false; - return args.len == 1 and args[0] == .integer; + try command.executeName(self.command, .{ .args = .{ .buf = buf[0..self.args.len] } }); } fn load(allocator: std.mem.Allocator, tokens: []const std.json.Value) (parse_flow.ParseError || parse_vim.ParseError)!Command { @@ -420,8 +373,6 @@ const BindingSet = struct { selection_style: SelectionStyle, insert_command: []const u8 = "", hints_map: KeybindHints = .{}, - init_command: ?Command = null, - deinit_command: ?Command = null, const KeySyntax = enum { flow, vim }; const OnMatchFailure = enum { insert, ignore }; @@ -440,8 +391,6 @@ const BindingSet = struct { inherit: ?[]const u8 = null, inherits: ?[][]const u8 = null, selection: ?SelectionStyle = null, - init_command: ?[]const std.json.Value = null, - deinit_command: ?[]const std.json.Value = null, }; const parsed = try std.json.parseFromValue(JsonConfig, allocator, mode_bindings, .{ .ignore_unknown_fields = true, @@ -453,8 +402,6 @@ const BindingSet = struct { self.line_numbers = parsed.value.line_numbers; self.cursor_shape = parsed.value.cursor; self.selection_style = parsed.value.selection orelse .normal; - if (parsed.value.init_command) |cmd| self.init_command = try Command.load(allocator, cmd); - if (parsed.value.deinit_command) |cmd| self.deinit_command = try Command.load(allocator, cmd); try self.load_event(allocator, &self.press, input.event.press, parsed.value.press); try self.load_event(allocator, &self.release, input.event.release, parsed.value.release); if (parsed.value.inherits) |sibling_fallbacks| { @@ -769,14 +716,6 @@ pub fn current_key_event_sequence_fmt() KeyEventSequenceFmt { return .{ .key_events = globals.current_sequence.items }; } -pub fn current_integer_argument() ?usize { - return integer_argument; -} - -pub fn clear_integer_argument() void { - integer_argument = null; -} - const expectEqual = std.testing.expectEqual; const parse_test_cases = .{ diff --git a/src/keybind/parse_flow.zig b/src/keybind/parse_flow.zig index 4fe04c9..792aa26 100644 --- a/src/keybind/parse_flow.zig +++ b/src/keybind/parse_flow.zig @@ -29,7 +29,7 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro var iter = std.mem.tokenizeScalar(u8, item, '+'); loop: while (iter.next()) |part| { if (part.len == 0) return parse_error("empty part in '{s}'", .{str}); - const modsInfo = @typeInfo(input.ModSet).@"struct"; + const modsInfo = @typeInfo(input.ModSet).Struct; inline for (modsInfo.fields) |field| { if (std.mem.eql(u8, part, field.name)) { if (@field(mods, field.name)) return parse_error("duplicate modifier '{s}' in '{s}'", .{ part, str }); diff --git a/src/keybind/parse_vim.zig b/src/keybind/parse_vim.zig index e05b585..455cb4a 100644 --- a/src/keybind/parse_vim.zig +++ b/src/keybind/parse_vim.zig @@ -15,7 +15,6 @@ pub const ParseError = error{ InvalidStartOfControlBinding, InvalidStartOfShiftBinding, InvalidStartOfDelBinding, - InvalidStartOfLeftBinding, InvalidStartOfEscBinding, InvalidStartOfHomeBinding, InvalidCRBinding, @@ -75,8 +74,6 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro end, insert, bs, - less_than, - greater_than, }; var state: State = .base; var function_key_number: u8 = 0; @@ -153,22 +150,11 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro state = .up; }, 'L' => { - state = switch (try peek(str, i)) { - 'e' => .left, - 'T' => .less_than, - else => return parse_error( - error.InvalidStartOfLeftBinding, - "str: {s}, i: {} c: {c}", - .{ str, i, str[i] }, - ), - }; + state = .left; }, 'R' => { state = .right; }, - 'G' => { - state = .greater_than; - }, 'I' => { state = .insert; }, @@ -318,22 +304,6 @@ pub fn parse_key_events(allocator: std.mem.Allocator, str: []const u8) ParseErro i += 5; } else return parse_error(error.InvalidRightBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }); }, - .less_than => { - if (std.mem.indexOf(u8, str[i..], "LT") == 0) { - try result.append(from_key_mods('<', modifiers)); - modifiers = 0; - state = .escape_sequence_end; - i += 2; - } else return parse_error(error.InvalidLeftBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }); - }, - .greater_than => { - if (std.mem.indexOf(u8, str[i..], "GT") == 0) { - try result.append(from_key_mods('>', modifiers)); - modifiers = 0; - state = .escape_sequence_end; - i += 2; - } else return parse_error(error.InvalidLeftBinding, "str: {s}, i: {} c: {c}", .{ str, i, str[i] }); - }, .function_key => { switch (str[i]) { '0'...'9' => { diff --git a/src/list_languages.zig b/src/list_languages.zig index 31de403..f752224 100644 --- a/src/list_languages.zig +++ b/src/list_languages.zig @@ -3,7 +3,7 @@ const syntax = @import("syntax"); const builtin = @import("builtin"); const RGB = @import("color").RGB; -const bin_path = @import("bin_path"); +const bin_path = @import("bin_path.zig"); const checkmark_width = if (builtin.os.tag != .windows) 2 else 3; diff --git a/src/location_history.zig b/src/location_history.zig index 98c4816..c64c43f 100644 --- a/src/location_history.zig +++ b/src/location_history.zig @@ -16,7 +16,7 @@ pub const Selection = struct { end: Cursor = Cursor{}, }; -pub fn create() error{ OutOfMemory, ThespianSpawnFailed }!Self { +pub fn create() !Self { return .{ .pid = try Process.create() }; } @@ -45,7 +45,7 @@ const Process = struct { selection: ?Selection = null, }; - pub fn create() error{ OutOfMemory, ThespianSpawnFailed }!tp.pid { + pub fn create() !tp.pid { const self = try outer_a.create(Process); self.* = .{ .arena = std.heap.ArenaAllocator.init(outer_a), @@ -123,13 +123,13 @@ const Process = struct { if (isdupe(self.backwards.getLastOrNull(), entry)) { if (self.current) |current| self.forwards.append(current) catch {}; - if (self.backwards.pop()) |top| - self.allocator.free(top.file_path); + const top = self.backwards.pop(); + self.allocator.free(top.file_path); tp.trace(tp.channel.all, tp.message.fmt(.{ "location", "back", entry.file_path, entry.cursor.row, entry.cursor.col, self.backwards.items.len, self.forwards.items.len })); } else if (isdupe(self.forwards.getLastOrNull(), entry)) { if (self.current) |current| self.backwards.append(current) catch {}; - if (self.forwards.pop()) |top| - self.allocator.free(top.file_path); + const top = self.forwards.pop(); + self.allocator.free(top.file_path); tp.trace(tp.channel.all, tp.message.fmt(.{ "location", "forward", entry.file_path, entry.cursor.row, entry.cursor.col, self.backwards.items.len, self.forwards.items.len })); } else if (self.current) |current| { try self.backwards.append(current); diff --git a/src/log.zig b/src/log.zig index 71d3521..c0df56a 100644 --- a/src/log.zig +++ b/src/log.zig @@ -1,6 +1,8 @@ const std = @import("std"); const tp = @import("thespian"); +const fba = std.heap.FixedBufferAllocator; + const Self = @This(); pub const max_log_message = tp.max_message_size - 128; @@ -9,7 +11,7 @@ allocator: std.mem.Allocator, receiver: Receiver, subscriber: ?tp.pid, heap: [32 + 1024]u8, -fba: std.heap.FixedBufferAllocator, +fba: fba, msg_store: MsgStoreT, const MsgStoreT = std.DoublyLinkedList([]u8); @@ -37,7 +39,7 @@ fn init(args: StartArgs) !*Self { .receiver = Receiver.init(Self.receive, p), .subscriber = null, .heap = undefined, - .fba = std.heap.FixedBufferAllocator.init(&p.heap), + .fba = fba.init(&p.heap), .msg_store = MsgStoreT{}, }; return p; diff --git a/src/main.zig b/src/main.zig index 0d6c119..74d298b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,11 +1,10 @@ const std = @import("std"); const tui = @import("tui"); -const cbor = @import("cbor"); const thespian = @import("thespian"); const flags = @import("flags"); const builtin = @import("builtin"); -const bin_path = @import("bin_path"); +const bin_path = @import("bin_path.zig"); const list_languages = @import("list_languages.zig"); pub const file_link = @import("file_link.zig"); @@ -16,7 +15,6 @@ const c = @cImport({ const build_options = @import("build_options"); const log = @import("log"); -pub const version = @embedFile("version"); pub const version_info = @embedFile("version_info"); pub var max_diff_lines: usize = 50000; @@ -27,7 +25,7 @@ pub const application_title = "Flow Control"; pub const application_subtext = "a programmer's text editor"; pub const application_description = application_title ++ ": " ++ application_subtext; -pub const std_options: std.Options = .{ +pub const std_options = .{ // .log_level = if (builtin.mode == .Debug) .debug else .warn, .log_level = if (builtin.mode == .Debug) .info else .warn, .logFn = log.std_log_function, @@ -35,11 +33,7 @@ pub const std_options: std.Options = .{ const renderer = @import("renderer"); -pub const panic = if (@hasDecl(renderer, "panic")) renderer.panic else default_panic; - -fn default_panic(msg: []const u8, _: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn { - return std.debug.defaultPanic(msg, ret_addr); -} +pub const panic = if (@hasDecl(renderer, "panic")) renderer.panic else std.builtin.default_panic; pub fn main() anyerror!void { if (builtin.os.tag == .linux) { @@ -48,8 +42,8 @@ pub fn main() anyerror!void { } const a = std.heap.c_allocator; - const letter_casing = @import("Buffer").unicode.get_letter_casing(); - _ = letter_casing; // no need to free letter_casing as it is globally static + const case_data = @import("Buffer").unicode.get_case_data(); + _ = case_data; // no need to free case_data as it is globally static const Flags = struct { pub const description = @@ -58,7 +52,7 @@ pub fn main() anyerror!void { \\ \\Pass in file names to be opened with an optional :LINE or :LINE:COL appended to the \\file name to specify a specific location, or pass + separately to set the line. - ; + ; pub const descriptions = .{ .project = "Set project directory (default: cwd)", @@ -149,10 +143,7 @@ pub fn main() anyerror!void { } if (builtin.os.tag != .windows) - if (std.posix.getenv("JITDEBUG")) |_| - thespian.install_debugger() - else if (@hasDecl(renderer, "install_crash_handler")) - renderer.install_crash_handler(); + if (std.posix.getenv("JITDEBUG")) |_| thespian.install_debugger(); if (args.debug_wait) { std.debug.print("press return to start", .{}); @@ -276,7 +267,6 @@ pub fn main() anyerror!void { } var have_project = false; - var have_file = false; if (args.project) |project| { try tui_proc.send(.{ "cmd", "open_project_dir", .{project} }); have_project = true; @@ -290,16 +280,12 @@ pub fn main() anyerror!void { try tui_proc.send(.{ "cmd", "open_project_dir", .{dir.path} }); have_project = true; }, - else => { - have_file = true; - }, + else => {}, }; for (links.items) |link| { try file_link.navigate(tui_proc.ref(), &link); - } - - if (!have_file) { + } else { if (!have_project) try tui_proc.send(.{ "cmd", "open_project_cwd" }); try tui_proc.send(.{ "cmd", "show_home" }); @@ -313,27 +299,7 @@ pub fn main() anyerror!void { if (args.exec) |exec_str| { var cmds = std.mem.splitScalar(u8, exec_str, ';'); - while (cmds.next()) |cmd| { - var count_args_ = std.mem.splitScalar(u8, cmd, ':'); - var count: usize = 0; - while (count_args_.next()) |_| count += 1; - if (count == 0) break; - - var msg = std.ArrayList(u8).init(a); - defer msg.deinit(); - const writer = msg.writer(); - - var cmd_args = std.mem.splitScalar(u8, cmd, ':'); - const cmd_ = cmd_args.next(); - try cbor.writeArrayHeader(writer, 3); - try cbor.writeValue(writer, "cmd"); - try cbor.writeValue(writer, cmd_); - try cbor.writeArrayHeader(writer, count - 1); - - while (cmd_args.next()) |arg| try cbor.writeValue(writer, arg); - - try tui_proc.send_raw(.{ .buf = msg.items }); - } + while (cmds.next()) |cmd| try tui_proc.send(.{ "cmd", cmd, .{} }); } ctx.run(); @@ -377,6 +343,7 @@ fn trace_json(json: thespian.message.json_string_view) callconv(.C) void { extern fn ___tracy_emit_message(txt: [*]const u8, size: usize, callstack: c_int) void; fn trace_to_file(m: thespian.message.c_buffer_type) callconv(.C) void { + const cbor = @import("cbor"); const State = struct { file: std.fs.File, last_time: i64, @@ -474,6 +441,7 @@ fn read_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs: *[][] } fn read_text_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs_: *[][]const u8, file_name: []const u8) !void { + const cbor = @import("cbor"); var file = try std.fs.openFileAbsolute(file_name, .{ .mode = .read_only }); defer file.close(); const text = try file.readToEndAlloc(allocator, 64 * 1024); @@ -509,6 +477,7 @@ fn read_text_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs_: } fn read_json_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs_: *[][]const u8, file_name: []const u8) !void { + const cbor = @import("cbor"); var file = try std.fs.openFileAbsolute(file_name, .{ .mode = .read_only }); defer file.close(); const json = try file.readToEndAlloc(allocator, 64 * 1024); @@ -529,6 +498,7 @@ fn read_cbor_config( file_name: []const u8, cb: []const u8, ) !void { + const cbor = @import("cbor"); var iter = cb; var field_name: []const u8 = undefined; while (cbor.matchString(&iter, &field_name) catch |e| switch (e) { @@ -536,7 +506,7 @@ fn read_cbor_config( else => return e, }) { var known = false; - inline for (@typeInfo(T).@"struct".fields) |field_info| + inline for (@typeInfo(T).Struct.fields) |field_info| if (comptime std.mem.eql(u8, "include_files", field_info.name)) { if (std.mem.eql(u8, field_name, field_info.name)) { known = true; @@ -577,9 +547,7 @@ fn read_nested_include_files(T: type, allocator: std.mem.Allocator, conf: *T, bu }; } -pub const ConfigWriteError = error{ CreateConfigFileFailed, WriteConfigFileFailed }; - -pub fn write_config(conf: anytype, allocator: std.mem.Allocator) (ConfigDirError || ConfigWriteError)!void { +pub fn write_config(conf: anytype, allocator: std.mem.Allocator) !void { config_mutex.lock(); defer config_mutex.unlock(); _ = allocator; @@ -588,22 +556,16 @@ pub fn write_config(conf: anytype, allocator: std.mem.Allocator) (ConfigDirError // return write_json_file(@TypeOf(conf), conf, allocator, try get_app_config_file_name(application_name, @typeName(@TypeOf(conf)))); } -fn write_text_config_file(comptime T: type, data: T, file_name: []const u8) ConfigWriteError!void { - var file = std.fs.createFileAbsolute(file_name, .{ .truncate = true }) catch |e| { - std.log.err("createFileAbsolute failed with {any} for: {s}", .{ e, file_name }); - return error.CreateConfigFileFailed; - }; +fn write_text_config_file(comptime T: type, data: T, file_name: []const u8) !void { + var file = try std.fs.createFileAbsolute(file_name, .{ .truncate = true }); defer file.close(); const writer = file.writer(); - write_config_to_writer(T, data, writer) catch |e| { - std.log.err("write file failed with {any} for: {s}", .{ e, file_name }); - return error.WriteConfigFileFailed; - }; + return write_config_to_writer(T, data, writer); } -pub fn write_config_to_writer(comptime T: type, data: T, writer: anytype) @TypeOf(writer).Error!void { +pub fn write_config_to_writer(comptime T: type, data: T, writer: anytype) !void { const default: T = .{}; - inline for (@typeInfo(T).@"struct".fields) |field_info| { + inline for (@typeInfo(T).Struct.fields) |field_info| { if (config_eql( field_info.type, @field(data, field_info.name), @@ -625,8 +587,8 @@ fn config_eql(comptime T: type, a: T, b: T) bool { else => {}, } switch (@typeInfo(T)) { - .bool, .int, .float, .@"enum" => return a == b, - .optional => |info| { + .Bool, .Int, .Float, .Enum => return a == b, + .Optional => |info| { if (a == null and b == null) return true; if (a == null or b == null) @@ -639,6 +601,7 @@ fn config_eql(comptime T: type, a: T, b: T) bool { } fn write_json_file(comptime T: type, data: T, allocator: std.mem.Allocator, file_name: []const u8) !void { + const cbor = @import("cbor"); var file = try std.fs.createFileAbsolute(file_name, .{ .truncate = true }); defer file.close(); @@ -679,47 +642,11 @@ pub fn list_keybind_namespaces(allocator: std.mem.Allocator) ![]const []const u8 return result.toOwnedSlice(); } -pub fn read_theme(allocator: std.mem.Allocator, theme_name: []const u8) ?[]const u8 { - const file_name = get_theme_file_name(theme_name) catch return null; - var file = std.fs.openFileAbsolute(file_name, .{ .mode = .read_only }) catch return null; - defer file.close(); - return file.readToEndAlloc(allocator, 64 * 1024) catch null; -} - -pub fn write_theme(theme_name: []const u8, content: []const u8) !void { - const file_name = try get_theme_file_name(theme_name); - var file = try std.fs.createFileAbsolute(file_name, .{ .truncate = true }); - defer file.close(); - return file.writeAll(content); -} - -pub fn list_themes(allocator: std.mem.Allocator) ![]const []const u8 { - var dir = try std.fs.openDirAbsolute(try get_theme_directory(), .{ .iterate = true }); - defer dir.close(); - var result = std.ArrayList([]const u8).init(allocator); - var iter = dir.iterateAssumeFirstIteration(); - while (try iter.next()) |entry| { - switch (entry.kind) { - .file, .sym_link => try result.append(try allocator.dupe(u8, std.fs.path.stem(entry.name))), - else => continue, - } - } - return result.toOwnedSlice(); -} - pub fn get_config_dir() ![]const u8 { return get_app_config_dir(application_name); } -pub const ConfigDirError = error{ - NoSpaceLeft, - MakeConfigDirFailed, - MakeHomeConfigDirFailed, - MakeAppConfigDirFailed, - AppConfigDirUnavailable, -}; - -fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 { +fn get_app_config_dir(appname: []const u8) ![]const u8 { const a = std.heap.c_allocator; const local = struct { var config_dir_buffer: [std.posix.PATH_MAX]u8 = undefined; @@ -735,7 +662,7 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 { const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/.config", .{home}); std.fs.makeDirAbsolute(dir) catch |e| switch (e) { error.PathAlreadyExists => {}, - else => return error.MakeHomeConfigDirFailed, + else => return e, }; break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/.config/{s}", .{ home, appname }); } else if (builtin.os.tag == .windows) ret: { @@ -744,7 +671,7 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 { const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/{s}", .{ appdata, appname }); std.fs.makeDirAbsolute(dir) catch |e| switch (e) { error.PathAlreadyExists => {}, - else => return error.MakeAppConfigDirFailed, + else => return e, }; break :ret dir; } else return error.AppConfigDirUnavailable; @@ -753,15 +680,12 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 { local.config_dir = config_dir; std.fs.makeDirAbsolute(config_dir) catch |e| switch (e) { error.PathAlreadyExists => {}, - else => return error.MakeConfigDirFailed, + else => return e, }; var keybind_dir_buffer: [std.posix.PATH_MAX]u8 = undefined; std.fs.makeDirAbsolute(try std.fmt.bufPrint(&keybind_dir_buffer, "{s}/{s}", .{ config_dir, keybind_dir })) catch {}; - var theme_dir_buffer: [std.posix.PATH_MAX]u8 = undefined; - std.fs.makeDirAbsolute(try std.fmt.bufPrint(&theme_dir_buffer, "{s}/{s}", .{ config_dir, theme_dir })) catch {}; - return config_dir; } @@ -856,11 +780,11 @@ fn get_app_state_dir(appname: []const u8) ![]const u8 { return state_dir; } -fn get_app_config_file_name(appname: []const u8, comptime base_name: []const u8) ConfigDirError![]const u8 { +fn get_app_config_file_name(appname: []const u8, comptime base_name: []const u8) ![]const u8 { return get_app_config_dir_file_name(appname, base_name ++ ".json"); } -fn get_app_config_dir_file_name(appname: []const u8, comptime config_file_name: []const u8) ConfigDirError![]const u8 { +fn get_app_config_dir_file_name(appname: []const u8, comptime config_file_name: []const u8) ![]const u8 { const local = struct { var config_file_buffer: [std.posix.PATH_MAX]u8 = undefined; }; @@ -907,28 +831,6 @@ pub fn get_keybind_namespace_file_name(namespace_name: []const u8) ![]const u8 { return try std.fmt.bufPrint(&local.file_buffer, "{s}/{s}.json", .{ dir, namespace_name }); } -const theme_dir = "themes"; - -fn get_theme_directory() ![]const u8 { - const local = struct { - var dir_buffer: [std.posix.PATH_MAX]u8 = undefined; - }; - const a = std.heap.c_allocator; - if (std.process.getEnvVarOwned(a, "FLOW_THEMES_DIR") catch null) |dir| { - defer a.free(dir); - return try std.fmt.bufPrint(&local.dir_buffer, "{s}", .{dir}); - } - return try std.fmt.bufPrint(&local.dir_buffer, "{s}/{s}", .{ try get_app_config_dir(application_name), theme_dir }); -} - -pub fn get_theme_file_name(theme_name: []const u8) ![]const u8 { - const dir = try get_theme_directory(); - const local = struct { - var file_buffer: [std.posix.PATH_MAX]u8 = undefined; - }; - return try std.fmt.bufPrint(&local.file_buffer, "{s}/{s}.json", .{ dir, theme_name }); -} - fn restart() noreturn { var executable: [:0]const u8 = std.mem.span(std.os.argv[0]); var is_basename = true; @@ -975,3 +877,22 @@ pub fn shorten_path(buf: []u8, path: []const u8, removed_prefix: *usize, max_len @memcpy(buf[ellipsis.len .. max_len + ellipsis.len], path[prefix..]); return buf[0 .. max_len + ellipsis.len]; } + +pub fn abbreviate_home(buf: []u8, path: []const u8) []const u8 { + const a = std.heap.c_allocator; + if (builtin.os.tag == .windows) return path; + if (!std.fs.path.isAbsolute(path)) return path; + const homedir = std.posix.getenv("HOME") orelse return path; + const homerelpath = std.fs.path.relative(a, homedir, path) catch return path; + defer a.free(homerelpath); + if (homerelpath.len == 0) { + return "~"; + } else if (homerelpath.len > 3 and std.mem.eql(u8, homerelpath[0..3], "../")) { + return path; + } else { + buf[0] = '~'; + buf[1] = '/'; + @memcpy(buf[2 .. homerelpath.len + 2], homerelpath); + return buf[0 .. homerelpath.len + 2]; + } +} diff --git a/src/project_manager.zig b/src/project_manager.zig index a9c5466..e077023 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -147,13 +147,11 @@ pub fn did_open(file_path: []const u8, file_type: *const FileType, version: usiz return send(.{ "did_open", project, file_path, file_type.name, language_server, version, text_ptr, text.len }); } -pub fn did_change(file_path: []const u8, version: usize, text_dst: []const u8, text_src: []const u8, eol_mode: Buffer.EolMode) (ProjectManagerError || ProjectError)!void { +pub fn did_change(file_path: []const u8, version: usize, root_dst: usize, root_src: usize, eol_mode: Buffer.EolMode) (ProjectManagerError || ProjectError)!void { const project = tp.env.get().str("project"); if (project.len == 0) return error.NoProject; - const text_dst_ptr: usize = if (text_dst.len > 0) @intFromPtr(text_dst.ptr) else 0; - const text_src_ptr: usize = if (text_src.len > 0) @intFromPtr(text_src.ptr) else 0; - return send(.{ "did_change", project, file_path, version, text_dst_ptr, text_dst.len, text_src_ptr, text_src.len, @intFromEnum(eol_mode) }); + return send(.{ "did_change", project, file_path, version, root_dst, root_src, @intFromEnum(eol_mode) }); } pub fn did_save(file_path: []const u8) (ProjectManagerError || ProjectError)!void { @@ -234,13 +232,16 @@ pub fn update_mru(file_path: []const u8, row: usize, col: usize, ephemeral: bool return send(.{ "update_mru", project, file_path, row, col }); } -pub fn get_mru_position(allocator: std.mem.Allocator, file_path: []const u8, ctx: anytype) (ProjectManagerError || ProjectError)!void { +pub fn get_mru_position(allocator: std.mem.Allocator, file_path: []const u8) (ProjectManagerError || ProjectError || CallError || cbor.Error)!?Project.FilePos { + const frame = tracy.initZone(@src(), .{ .name = "get_mru_position" }); + defer frame.deinit(); const project = tp.env.get().str("project"); if (project.len == 0) return error.NoProject; - - const cp = @import("completion.zig"); - return cp.send(allocator, (try get()).pid, .{ "get_mru_position", project, file_path }, ctx); + const rsp = try (try get()).pid.call(allocator, request_timeout, .{ "get_mru_position", project, file_path }); + defer allocator.free(rsp.buf); + var pos: Project.FilePos = undefined; + return if (try cbor.match(rsp.buf, .{ tp.extract(&pos.row), tp.extract(&pos.col) })) pos else null; } const Process = struct { @@ -249,12 +250,13 @@ const Process = struct { logger: log.Logger, receiver: Receiver, projects: ProjectsMap, + walker: ?tp.pid = null, const InvalidArgumentError = error{InvalidArgument}; const UnsupportedError = error{Unsupported}; const Receiver = tp.Receiver(*Process); - const ProjectsMap = std.StringHashMapUnmanaged(*Project); + const ProjectsMap = std.StringHashMap(*Project); const RecentProject = struct { name: []const u8, last_used: i128, @@ -268,7 +270,7 @@ const Process = struct { .parent = tp.self_pid().clone(), .logger = log.logger(module_name), .receiver = Receiver.init(Process.receive, self), - .projects = .empty, + .projects = ProjectsMap.init(allocator), }; return tp.spawn_link(self.allocator, self, Process.start, module_name); } @@ -280,7 +282,7 @@ const Process = struct { p.value_ptr.*.deinit(); self.allocator.destroy(p.value_ptr.*); } - self.projects.deinit(self.allocator); + self.projects.deinit(); self.parent.deinit(); self.logger.deinit(); self.allocator.destroy(self); @@ -324,26 +326,24 @@ const Process = struct { var version: usize = 0; var text_ptr: usize = 0; var text_len: usize = 0; - var text_dst_ptr: usize = 0; - var text_dst_len: usize = 0; - var text_src_ptr: usize = 0; - var text_src_len: usize = 0; var n: usize = 0; var task: []const u8 = undefined; - var context: usize = undefined; + var root_dst: usize = 0; + var root_src: usize = 0; var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf); if (try cbor.match(m.buf, .{ "walk_tree_entry", tp.extract(&project_directory), tp.extract(&path), tp.extract(&high), tp.extract(&low) })) { const mtime = (@as(i128, @intCast(high)) << 64) | @as(i128, @intCast(low)); if (self.projects.get(project_directory)) |project| - project.walk_tree_entry(path, mtime) catch |e| self.logger.err("walk_tree_entry", e); + project.add_pending_file( + path, + mtime, + ) catch |e| self.logger.err("walk_tree_entry", e); } else if (try cbor.match(m.buf, .{ "walk_tree_done", tp.extract(&project_directory) })) { - if (self.projects.get(project_directory)) |project| - project.walk_tree_done() catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; - } else if (try cbor.match(m.buf, .{ "git", tp.extract(&context), tp.more })) { - const project: *Project = @ptrFromInt(context); - project.process_git(m) catch {}; + if (self.walker) |pid| pid.deinit(); + self.walker = null; + self.loaded(project_directory) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; } else if (try cbor.match(m.buf, .{ "update_mru", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) { self.update_mru(project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; } else if (try cbor.match(m.buf, .{ "child", tp.extract(&project_directory), tp.extract(&language_server), "notify", tp.extract(&method), tp.extract_cbor(¶ms_cb) })) { @@ -377,10 +377,8 @@ const Process = struct { } else if (try cbor.match(m.buf, .{ "did_open", tp.extract(&project_directory), tp.extract(&path), tp.extract(&file_type), tp.extract_cbor(&language_server), tp.extract(&version), tp.extract(&text_ptr), tp.extract(&text_len) })) { const text = if (text_len > 0) @as([*]const u8, @ptrFromInt(text_ptr))[0..text_len] else ""; self.did_open(project_directory, path, file_type, language_server, version, text) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; - } else if (try cbor.match(m.buf, .{ "did_change", tp.extract(&project_directory), tp.extract(&path), tp.extract(&version), tp.extract(&text_dst_ptr), tp.extract(&text_dst_len), tp.extract(&text_src_ptr), tp.extract(&text_src_len), tp.extract(&eol_mode) })) { - const text_dst = if (text_dst_len > 0) @as([*]const u8, @ptrFromInt(text_dst_ptr))[0..text_dst_len] else ""; - const text_src = if (text_src_len > 0) @as([*]const u8, @ptrFromInt(text_src_ptr))[0..text_src_len] else ""; - self.did_change(project_directory, path, version, text_dst, text_src, @enumFromInt(eol_mode)) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; + } else if (try cbor.match(m.buf, .{ "did_change", tp.extract(&project_directory), tp.extract(&path), tp.extract(&version), tp.extract(&root_dst), tp.extract(&root_src), tp.extract(&eol_mode) })) { + self.did_change(project_directory, path, version, root_dst, root_src, @enumFromInt(eol_mode)) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; } else if (try cbor.match(m.buf, .{ "did_save", tp.extract(&project_directory), tp.extract(&path) })) { self.did_save(project_directory, path) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; } else if (try cbor.match(m.buf, .{ "did_close", tp.extract(&project_directory), tp.extract(&path) })) { @@ -404,6 +402,7 @@ const Process = struct { } else if (try cbor.match(m.buf, .{ "get_mru_position", tp.extract(&project_directory), tp.extract(&path) })) { self.get_mru_position(from, project_directory, path) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; } else if (try cbor.match(m.buf, .{"shutdown"})) { + if (self.walker) |pid| pid.send(.{"stop"}) catch {}; self.persist_projects(); from.send(.{ "project_manager", "shutdown" }) catch return error.ClientFailed; return error.ExitNormal; @@ -413,8 +412,6 @@ const Process = struct { return; } else if (try cbor.match(m.buf, .{ "exit", "error.FileNotFound", tp.more })) { return; - } else if (try cbor.match(m.buf, .{ "exit", "error.LspFailed", tp.more })) { - return; } else { self.logger.err("receive", tp.unexpected(m)); } @@ -425,9 +422,10 @@ const Process = struct { self.logger.print("opening: {s}", .{project_directory}); const project = try self.allocator.create(Project); project.* = try Project.init(self.allocator, project_directory); - try self.projects.put(self.allocator, try self.allocator.dupe(u8, project_directory), project); + try self.projects.put(try self.allocator.dupe(u8, project_directory), project); + self.walker = try walk_tree_async(self.allocator, project_directory); self.restore_project(project) catch |e| self.logger.err("restore_project", e); - project.query_git(); + project.sort_files_by_mtime(); } else { self.logger.print("switched to: {s}", .{project_directory}); } @@ -443,13 +441,25 @@ const Process = struct { } } + fn loaded(self: *Process, project_directory: []const u8) OutOfMemoryError!void { + const project = self.projects.get(project_directory) orelse return; + try project.merge_pending_files(); + self.logger.print("opened: {s} with {d} files in {d} ms", .{ + project_directory, + project.files.items.len, + std.time.milliTimestamp() - project.open_time, + }); + } + fn request_n_most_recent_file(self: *Process, from: tp.pid_ref, project_directory: []const u8, n: usize) (ProjectError || Project.ClientError)!void { const project = self.projects.get(project_directory) orelse return error.NoProject; + project.sort_files_by_mtime(); return project.request_n_most_recent_file(from, n); } fn request_recent_files(self: *Process, from: tp.pid_ref, project_directory: []const u8, max: usize) (ProjectError || Project.ClientError)!void { const project = self.projects.get(project_directory) orelse return error.NoProject; + project.sort_files_by_mtime(); return project.request_recent_files(from, max); } @@ -480,9 +490,7 @@ const Process = struct { fn request_path_files(self: *Process, from: tp.pid_ref, project_directory: []const u8, max: usize, path: []const u8) (ProjectError || SpawnError || std.fs.Dir.OpenError)!void { const project = self.projects.get(project_directory) orelse return error.NoProject; - var buf = std.ArrayList(u8).init(self.allocator); - defer buf.deinit(); - try request_path_files_async(self.allocator, from, project, max, expand_home(&buf, path)); + try request_path_files_async(self.allocator, from, project, max, path); } fn request_tasks(self: *Process, from: tp.pid_ref, project_directory: []const u8) (ProjectError || Project.ClientError)!void { @@ -507,11 +515,11 @@ const Process = struct { return project.did_open(file_path, file_type, language_server, version, text); } - fn did_change(self: *Process, project_directory: []const u8, file_path: []const u8, version: usize, text_dst: []const u8, text_src: []const u8, eol_mode: Buffer.EolMode) (ProjectError || Project.LspError)!void { + fn did_change(self: *Process, project_directory: []const u8, file_path: []const u8, version: usize, root_dst: usize, root_src: usize, eol_mode: Buffer.EolMode) (ProjectError || Project.LspError)!void { const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".did_change" }); defer frame.deinit(); const project = self.projects.get(project_directory) orelse return error.NoProject; - return project.did_change(file_path, version, text_dst, text_src, eol_mode); + return project.did_change(file_path, version, root_dst, root_src, eol_mode); } fn did_save(self: *Process, project_directory: []const u8, file_path: []const u8) (ProjectError || Project.LspError)!void { @@ -805,6 +813,188 @@ fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_ }.spawn_link(a_, parent_, project_, max_, path_); } +fn walk_tree_async(a_: std.mem.Allocator, root_path_: []const u8) (SpawnError || std.fs.Dir.OpenError)!tp.pid { + return struct { + allocator: std.mem.Allocator, + root_path: []const u8, + parent: tp.pid, + receiver: Receiver, + dir: std.fs.Dir, + walker: FilteredWalker, + + const tree_walker = @This(); + const Receiver = tp.Receiver(*tree_walker); + + fn spawn_link(allocator: std.mem.Allocator, root_path: []const u8) (SpawnError || std.fs.Dir.OpenError)!tp.pid { + const self = try allocator.create(tree_walker); + self.* = .{ + .allocator = allocator, + .root_path = try allocator.dupe(u8, root_path), + .parent = tp.self_pid().clone(), + .receiver = Receiver.init(tree_walker.receive, self), + .dir = try std.fs.cwd().openDir(self.root_path, .{ .iterate = true }), + .walker = try walk_filtered(self.dir, self.allocator), + }; + return tp.spawn_link(allocator, self, tree_walker.start, module_name ++ ".tree_walker"); + } + + fn start(self: *tree_walker) tp.result { + errdefer self.deinit(); + const frame = tracy.initZone(@src(), .{ .name = "project scan" }); + defer frame.deinit(); + tp.receive(&self.receiver); + self.next() catch |e| return tp.exit_error(e, @errorReturnTrace()); + } + + fn deinit(self: *tree_walker) void { + self.walker.deinit(); + self.dir.close(); + self.allocator.free(self.root_path); + self.parent.deinit(); + } + + fn receive(self: *tree_walker, _: tp.pid_ref, m: tp.message) tp.result { + errdefer self.deinit(); + const frame = tracy.initZone(@src(), .{ .name = "project scan" }); + defer frame.deinit(); + + if (try m.match(.{"next"})) { + self.next() catch |e| return tp.exit_error(e, @errorReturnTrace()); + } else if (try m.match(.{"stop"})) { + return tp.exit_normal(); + } else { + return tp.unexpected(m); + } + } + + fn next(self: *tree_walker) !void { + if (try self.walker.next()) |path| { + const stat = self.dir.statFile(path) catch return tp.self_pid().send(.{"next"}); + const mtime = stat.mtime; + const high: i64 = @intCast(mtime >> 64); + const low: i64 = @truncate(mtime); + std.debug.assert(mtime == (@as(i128, @intCast(high)) << 64) | @as(i128, @intCast(low))); + try self.parent.send(.{ "walk_tree_entry", self.root_path, path, high, low }); + return tp.self_pid().send(.{"next"}); + } else { + self.parent.send(.{ "walk_tree_done", self.root_path }) catch {}; + return tp.exit_normal(); + } + } + }.spawn_link(a_, root_path_); +} + +const filtered_dirs = [_][]const u8{ + "build", + ".cache", + ".cargo", + ".git", + ".jj", + "node_modules", + ".npm", + ".rustup", + ".var", + ".zig-cache", + "zig-cache", + "zig-out", +}; + +fn is_filtered_dir(dirname: []const u8) bool { + for (filtered_dirs) |filter| + if (std.mem.eql(u8, filter, dirname)) + return true; + return false; +} + +const FilteredWalker = struct { + allocator: std.mem.Allocator, + stack: std.ArrayListUnmanaged(StackItem), + name_buffer: std.ArrayListUnmanaged(u8), + + const Path = []const u8; + + const StackItem = struct { + iter: std.fs.Dir.Iterator, + dirname_len: usize, + }; + + pub fn next(self: *FilteredWalker) OutOfMemoryError!?Path { + while (self.stack.items.len != 0) { + var top = &self.stack.items[self.stack.items.len - 1]; + var containing = top; + var dirname_len = top.dirname_len; + if (top.iter.next() catch { + var item = self.stack.pop(); + if (self.stack.items.len != 0) { + item.iter.dir.close(); + } + continue; + }) |base| { + self.name_buffer.shrinkRetainingCapacity(dirname_len); + if (self.name_buffer.items.len != 0) { + try self.name_buffer.append(self.allocator, std.fs.path.sep); + dirname_len += 1; + } + try self.name_buffer.appendSlice(self.allocator, base.name); + switch (base.kind) { + .directory => { + if (is_filtered_dir(base.name)) + continue; + var new_dir = top.iter.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) { + error.NameTooLong => @panic("unexpected error.NameTooLong"), // no path sep in base.name + else => continue, + }; + { + errdefer new_dir.close(); + try self.stack.append(self.allocator, .{ + .iter = new_dir.iterateAssumeFirstIteration(), + .dirname_len = self.name_buffer.items.len, + }); + top = &self.stack.items[self.stack.items.len - 1]; + containing = &self.stack.items[self.stack.items.len - 2]; + } + }, + .file => return self.name_buffer.items, + else => continue, + } + } else { + var item = self.stack.pop(); + if (self.stack.items.len != 0) { + item.iter.dir.close(); + } + } + } + return null; + } + + pub fn deinit(self: *FilteredWalker) void { + // Close any remaining directories except the initial one (which is always at index 0) + if (self.stack.items.len > 1) { + for (self.stack.items[1..]) |*item| { + item.iter.dir.close(); + } + } + self.stack.deinit(self.allocator); + self.name_buffer.deinit(self.allocator); + } +}; + +fn walk_filtered(dir: std.fs.Dir, allocator: std.mem.Allocator) !FilteredWalker { + var stack: std.ArrayListUnmanaged(FilteredWalker.StackItem) = .{}; + errdefer stack.deinit(allocator); + + try stack.append(allocator, .{ + .iter = dir.iterate(), + .dirname_len = 0, + }); + + return .{ + .allocator = allocator, + .stack = stack, + .name_buffer = .{}, + }; +} + pub fn normalize_file_path(file_path: []const u8) []const u8 { const project = tp.env.get().str("project"); if (project.len == 0) return file_path; @@ -813,34 +1003,3 @@ pub fn normalize_file_path(file_path: []const u8) []const u8 { return file_path[project.len + 1 ..]; return file_path; } - -pub fn abbreviate_home(buf: []u8, path: []const u8) []const u8 { - const a = std.heap.c_allocator; - if (builtin.os.tag == .windows) return path; - if (!std.fs.path.isAbsolute(path)) return path; - const homedir = std.posix.getenv("HOME") orelse return path; - const homerelpath = std.fs.path.relative(a, homedir, path) catch return path; - defer a.free(homerelpath); - if (homerelpath.len == 0) { - return "~"; - } else if (homerelpath.len > 3 and std.mem.eql(u8, homerelpath[0..3], "../")) { - return path; - } else { - buf[0] = '~'; - buf[1] = '/'; - @memcpy(buf[2 .. homerelpath.len + 2], homerelpath); - return buf[0 .. homerelpath.len + 2]; - } -} - -pub fn expand_home(buf: *std.ArrayList(u8), file_path: []const u8) []const u8 { - if (builtin.os.tag == .windows) return file_path; - if (file_path.len > 0 and file_path[0] == '~') { - if (file_path.len > 1 and file_path[1] != std.fs.path.sep) return file_path; - const homedir = std.posix.getenv("HOME") orelse return file_path; - buf.appendSlice(homedir) catch return file_path; - buf.append(std.fs.path.sep) catch return file_path; - buf.appendSlice(file_path[2..]) catch return file_path; - return buf.items; - } else return file_path; -} diff --git a/src/renderer/vaxis/Cell.zig b/src/renderer/vaxis/Cell.zig index 4ef154b..f32be7a 100644 --- a/src/renderer/vaxis/Cell.zig +++ b/src/renderer/vaxis/Cell.zig @@ -76,16 +76,8 @@ pub fn columns(self: *const Cell) usize { } pub fn dim(self: *Cell, alpha: u8) void { - self.dim_fg(alpha); - self.dim_bg(alpha); -} - -pub fn dim_bg(self: *Cell, alpha: u8) void { - self.cell.style.bg = apply_alpha_value(self.cell.style.bg, alpha); -} - -pub fn dim_fg(self: *Cell, alpha: u8) void { self.cell.style.fg = apply_alpha_value(self.cell.style.fg, alpha); + self.cell.style.bg = apply_alpha_value(self.cell.style.bg, alpha); } fn apply_alpha_value(c: vaxis.Cell.Color, a: u8) vaxis.Cell.Color { diff --git a/src/renderer/vaxis/Plane.zig b/src/renderer/vaxis/Plane.zig index 80df067..e1dd9d3 100644 --- a/src/renderer/vaxis/Plane.zig +++ b/src/renderer/vaxis/Plane.zig @@ -37,7 +37,7 @@ pub const option = enum { }; pub fn init(nopts: *const Options, parent_: Plane) !Plane { - const opts: vaxis.Window.ChildOptions = .{ + const opts = .{ .x_off = @as(i17, @intCast(nopts.x)), .y_off = @as(i17, @intCast(nopts.y)), .width = @as(u16, @intCast(nopts.cols)), @@ -202,25 +202,6 @@ pub fn putstr(self: *Plane, text: []const u8) !usize { return result; } -pub fn putstr_unicode(self: *Plane, text: []const u8) !usize { - var result: usize = 0; - var iter = self.window.screen.unicode.graphemeIterator(text); - while (iter.next()) |grapheme| { - const s_ = grapheme.bytes(text); - const s = switch (s_[0]) { - 0...31 => |code| Buffer.unicode.control_code_to_unicode(code), - else => s_, - }; - self.write_cell(@intCast(self.col), @intCast(self.row), s); - result += 1; - } - return result; -} - -pub fn putchar(self: *Plane, ecg: []const u8) void { - self.write_cell(@intCast(self.col), @intCast(self.row), ecg); -} - pub fn putc(self: *Plane, cell: *const Cell) !usize { return self.putc_yx(@intCast(self.row), @intCast(self.col), cell); } diff --git a/src/renderer/vaxis/input.zig b/src/renderer/vaxis/input.zig index c8bdcf2..ea4c8ea 100644 --- a/src/renderer/vaxis/input.zig +++ b/src/renderer/vaxis/input.zig @@ -1,14 +1,14 @@ const vaxis = @import("vaxis"); const meta = @import("std").meta; -const unicode = @import("std").unicode; +const utf8Encode = @import("std").unicode.utf8Encode; const FormatOptions = @import("std").fmt.FormatOptions; pub const key = vaxis.Key; pub const Key = u21; pub const Mouse = vaxis.Mouse.Button; -pub const MouseType = @typeInfo(Mouse).@"enum".tag_type; +pub const MouseType = @typeInfo(Mouse).Enum.tag_type; pub const mouse = struct { pub const MOTION: Mouse = vaxis.Mouse.Button.none; @@ -74,7 +74,6 @@ pub const KeyEvent = struct { } pub fn eql_unshifted(self: @This(), other: @This()) bool { - if (self.text.len > 0 or other.text.len > 0) return false; const self_mods = self.mods_no_caps(); const other_mods = other.mods_no_caps(); return self.key_unshifted == other.key_unshifted and self_mods == other_mods; @@ -118,7 +117,7 @@ pub const KeyEvent = struct { pub fn from_message( event_: Event, keypress_: Key, - keypress_shifted_: Key, + keypress_shifted: Key, text: []const u8, modifiers: Mods, ) @This() { @@ -129,11 +128,6 @@ pub const KeyEvent = struct { key.left_alt, key.right_alt => modifiers & ~mod.alt, else => modifiers, }; - - var keypress_shifted: Key = keypress_shifted_; - if (text.len > 0) blk: { - keypress_shifted = tryUtf8Decode(text) catch break :blk; - } const keypress, const mods = if (keypress_shifted == keypress_) map_key_to_unshifed_legacy(keypress_shifted, mods_) else @@ -149,27 +143,8 @@ pub const KeyEvent = struct { } }; -fn tryUtf8Decode(bytes: []const u8) !u21 { - return switch (bytes.len) { - 1 => bytes[0], - 2 => blk: { - if (bytes[0] & 0b11100000 != 0b11000000) break :blk error.Utf8Encoding2Invalid; - break :blk unicode.utf8Decode2(bytes[0..2].*); - }, - 3 => blk: { - if (bytes[0] & 0b11110000 != 0b11100000) break :blk error.Utf8Encoding3Invalid; - break :blk unicode.utf8Decode3(bytes[0..3].*); - }, - 4 => blk: { - if (bytes[0] & 0b11111000 != 0b11110000) break :blk error.Utf8Encoding4Invalid; - break :blk unicode.utf8Decode4(bytes[0..4].*); - }, - else => error.Utf8CodepointLengthInvalid, - }; -} - pub fn ucs32_to_utf8(ucs32: []const u32, utf8: []u8) !usize { - return @intCast(try unicode.utf8Encode(@intCast(ucs32[0]), utf8)); + return @intCast(try utf8Encode(@intCast(ucs32[0]), utf8)); } pub const utils = struct { diff --git a/src/renderer/vaxis/renderer.zig b/src/renderer/vaxis/renderer.zig index 8d3cd1f..f768d07 100644 --- a/src/renderer/vaxis/renderer.zig +++ b/src/renderer/vaxis/renderer.zig @@ -12,7 +12,6 @@ pub const Cell = @import("Cell.zig"); pub const CursorShape = vaxis.Cell.CursorShape; pub const style = @import("style.zig").StyleBits; -pub const styles = @import("style.zig"); const Self = @This(); pub const log_name = "vaxis"; @@ -26,7 +25,6 @@ no_alternate: bool, event_buffer: std.ArrayList(u8), input_buffer: std.ArrayList(u8), mods: vaxis.Key.Modifiers = .{}, -queries_done: bool, bracketed_paste: bool = false, bracketed_paste_buffer: std.ArrayList(u8), @@ -41,25 +39,7 @@ logger: log.Logger, loop: Loop, -pub const Error = error{ - UnexpectedRendererEvent, - OutOfMemory, - IntegerTooLarge, - IntegerTooSmall, - InvalidType, - TooShort, - Utf8CannotEncodeSurrogateHalf, - CodepointTooLarge, - TtyInitError, - TtyWriteError, - InvalidFloatType, - InvalidArrayType, - InvalidPIntType, - JsonIncompatibleType, - NotAnObject, -} || std.Thread.SpawnError; - -pub fn init(allocator: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: bool, _: *const fn (ctx: *anyopaque) void) Error!Self { +pub fn init(allocator: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: bool, _: *const fn (ctx: *anyopaque) void) !Self { const opts: vaxis.Vaxis.Options = .{ .kitty_keyboard_flags = .{ .disambiguate = true, @@ -72,7 +52,7 @@ pub fn init(allocator: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: }; return .{ .allocator = allocator, - .tty = vaxis.Tty.init() catch return error.TtyInitError, + .tty = try vaxis.Tty.init(), .vx = try vaxis.init(allocator, opts), .no_alternate = no_alternate, .event_buffer = std.ArrayList(u8).init(allocator), @@ -81,7 +61,6 @@ pub fn init(allocator: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: .handler_ctx = handler_ctx, .logger = log.logger(log_name), .loop = undefined, - .queries_done = false, }; } @@ -95,77 +74,40 @@ pub fn deinit(self: *Self) void { self.event_buffer.deinit(); } -var in_panic: std.atomic.Value(bool) = .init(false); var panic_cleanup: ?struct { allocator: std.mem.Allocator, tty: *vaxis.Tty, vx: *vaxis.Vaxis, } = null; - pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn { - _ = error_return_trace; // TODO: what to do with this in zig-0.14? - in_panic.store(true, .release); const cleanup = panic_cleanup; panic_cleanup = null; if (cleanup) |self| { self.vx.deinit(self.allocator, self.tty.anyWriter()); self.tty.deinit(); } - return std.debug.defaultPanic(msg, ret_addr orelse @returnAddress()); + return std.builtin.default_panic(msg, error_return_trace, ret_addr orelse @returnAddress()); } -pub fn panic_in_progress() bool { - return in_panic.load(.acquire); -} - -pub fn install_crash_handler() void { - if (!std.debug.have_segfault_handling_support) { - @compileError("segfault handler not supported for this target"); - } - const act = std.posix.Sigaction{ - .handler = .{ .sigaction = handle_crash }, - .mask = std.posix.empty_sigset, - .flags = (std.posix.SA.SIGINFO | std.posix.SA.RESTART), - }; - - std.posix.sigaction(std.posix.SIG.BUS, &act, null); - std.posix.sigaction(std.posix.SIG.SEGV, &act, null); - std.posix.sigaction(std.posix.SIG.ABRT, &act, null); - std.posix.sigaction(std.posix.SIG.FPE, &act, null); - std.posix.sigaction(std.posix.SIG.ILL, &act, null); -} - -fn handle_crash(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn { - in_panic.store(true, .release); - const cleanup = panic_cleanup; - panic_cleanup = null; - if (cleanup) |self| { - self.vx.deinit(self.allocator, self.tty.anyWriter()); - self.tty.deinit(); - } - @import("std/debug.zig").handleSegfaultPosix(sig, info, ctx_ptr); -} - -pub fn run(self: *Self) Error!void { +pub fn run(self: *Self) !void { self.vx.sgr = .legacy; self.vx.conpty_hacks = true; panic_cleanup = .{ .allocator = self.allocator, .tty = &self.tty, .vx = &self.vx }; - if (!self.no_alternate) self.vx.enterAltScreen(self.tty.anyWriter()) catch return error.TtyWriteError; + if (!self.no_alternate) try self.vx.enterAltScreen(self.tty.anyWriter()); if (builtin.os.tag == .windows) { try self.resize(.{ .rows = 25, .cols = 80, .x_pixel = 0, .y_pixel = 0 }); // dummy resize to fully init vaxis } else { - self.sigwinch() catch return error.TtyWriteError; + try self.sigwinch(); } - self.vx.setBracketedPaste(self.tty.anyWriter(), true) catch return error.TtyWriteError; - self.vx.queryTerminalSend(self.tty.anyWriter()) catch return error.TtyWriteError; + try self.vx.setBracketedPaste(self.tty.anyWriter(), true); + try self.vx.queryTerminalSend(self.tty.anyWriter()); self.loop = Loop.init(&self.tty, &self.vx); try self.loop.start(); } pub fn render(self: *Self) !void { - if (in_panic.load(.acquire)) return; var bufferedWriter = self.tty.bufferedWriter(); try self.vx.render(bufferedWriter.writer().any()); try bufferedWriter.flush(); @@ -176,8 +118,8 @@ pub fn sigwinch(self: *Self) !void { try self.resize(try vaxis.Tty.getWinsize(self.input_fd_blocking())); } -fn resize(self: *Self, ws: vaxis.Winsize) error{ TtyWriteError, OutOfMemory }!void { - self.vx.resize(self.allocator, self.tty.anyWriter(), ws) catch return error.TtyWriteError; +fn resize(self: *Self, ws: vaxis.Winsize) !void { + try self.vx.resize(self.allocator, self.tty.anyWriter(), ws); self.vx.queueRefresh(); if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{"resize"})); } @@ -201,7 +143,7 @@ pub fn input_fd_blocking(self: Self) i32 { return self.tty.fd; } -pub fn process_renderer_event(self: *Self, msg: []const u8) Error!void { +pub fn process_renderer_event(self: *Self, msg: []const u8) !void { var input_: []const u8 = undefined; var text_: []const u8 = undefined; if (!try cbor.match(msg, .{ "RDR", cbor.extract(&input_), cbor.extract(&text_) })) @@ -210,15 +152,6 @@ pub fn process_renderer_event(self: *Self, msg: []const u8) Error!void { const event = std.mem.bytesAsValue(vaxis.Event, input_); switch (event.*) { .key_press => |key__| { - // Check for a cursor position response for our explicity width query. This will - // always be an F3 key with shift = true, and we must be looking for queries - if (key__.codepoint == vaxis.Key.f3 and key__.mods.shift and !self.queries_done) { - self.logger.print("explicit width capability detected", .{}); - self.vx.caps.explicit_width = true; - self.vx.caps.unicode = .unicode; - self.vx.screen.width_method = .unicode; - return; - } const key_ = filter_mods(normalize_shifted_alphas(key__)); try self.sync_mod_state(key_.codepoint, key_.mods); const cbor_msg = try self.fmtmsg(.{ @@ -320,9 +253,8 @@ pub fn process_renderer_event(self: *Self, msg: []const u8) Error!void { self.vx.caps.sgr_pixels = true; }, .cap_da1 => { - self.queries_done = true; self.vx.enableDetectedFeatures(self.tty.anyWriter()) catch |e| self.logger.err("enable features", e); - self.vx.setMouseMode(self.tty.anyWriter(), true) catch return error.TtyWriteError; + try self.vx.setMouseMode(self.tty.anyWriter(), true); }, .cap_kitty_keyboard => { self.logger.print("kitty keyboard capability detected", .{}); @@ -341,7 +273,7 @@ pub fn process_renderer_event(self: *Self, msg: []const u8) Error!void { } } -fn fmtmsg(self: *Self, value: anytype) std.ArrayList(u8).Writer.Error![]const u8 { +fn fmtmsg(self: *Self, value: anytype) ![]const u8 { self.event_buffer.clearRetainingCapacity(); try cbor.writeValue(self.event_buffer.writer(), value); return self.event_buffer.items; @@ -350,10 +282,8 @@ fn fmtmsg(self: *Self, value: anytype) std.ArrayList(u8).Writer.Error![]const u8 fn handle_bracketed_paste_input(self: *Self, cbor_msg: []const u8) !bool { var keypress: input.Key = undefined; var egc_: input.Key = undefined; - var mods: usize = undefined; - if (try cbor.match(cbor_msg, .{ "I", cbor.number, cbor.extract(&keypress), cbor.extract(&egc_), cbor.string, cbor.extract(&mods) })) { + if (try cbor.match(cbor_msg, .{ "I", cbor.number, cbor.extract(&keypress), cbor.extract(&egc_), cbor.string, 0 })) { switch (keypress) { - 106 => if (mods == 4) try self.bracketed_paste_buffer.appendSlice("\n") else try self.bracketed_paste_buffer.appendSlice("j"), input.key.enter => try self.bracketed_paste_buffer.appendSlice("\n"), input.key.tab => try self.bracketed_paste_buffer.appendSlice("\t"), else => if (!input.is_non_input_key(keypress)) { @@ -385,7 +315,7 @@ fn handle_bracketed_paste_end(self: *Self) !void { if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{ "system_clipboard", self.bracketed_paste_buffer.items })); } -fn handle_bracketed_paste_error(self: *Self, e: Error) !void { +fn handle_bracketed_paste_error(self: *Self, e: anytype) !void { self.logger.err("bracketed paste", e); self.bracketed_paste_buffer.clearAndFree(); self.bracketed_paste = false; @@ -564,7 +494,7 @@ const Loop = struct { } /// spawns the input thread to read input from the tty - pub fn start(self: *Loop) std.Thread.SpawnError!void { + pub fn start(self: *Loop) !void { if (self.thread) |_| return; self.thread = try std.Thread.spawn(.{}, Loop.ttyRun, .{self}); } @@ -607,7 +537,7 @@ const Loop = struct { switch (builtin.os.tag) { .windows => { var parser: vaxis.Parser = .{ - .graphemes = &self.vaxis.unicode.graphemes, + .grapheme_data = &self.vaxis.unicode.width_data.g_data, }; const a = self.vaxis.opts.system_clipboard_allocator orelse @panic("no tty allocator"); while (!self.should_quit) { @@ -616,7 +546,7 @@ const Loop = struct { }, else => { var parser: vaxis.Parser = .{ - .graphemes = &self.vaxis.unicode.graphemes, + .grapheme_data = &self.vaxis.unicode.width_data.g_data, }; const a = self.vaxis.opts.system_clipboard_allocator orelse @panic("no tty allocator"); diff --git a/src/renderer/vaxis/std/debug.zig b/src/renderer/vaxis/std/debug.zig deleted file mode 100644 index d155d5a..0000000 --- a/src/renderer/vaxis/std/debug.zig +++ /dev/null @@ -1,1732 +0,0 @@ -const builtin = @import("builtin"); -const std = @import("std"); -const math = std.math; -const mem = std.mem; -const io = std.io; -const posix = std.posix; -const fs = std.fs; -const testing = std.testing; -const root = @import("root"); -const File = std.fs.File; -const windows = std.os.windows; -const native_arch = builtin.cpu.arch; -const native_os = builtin.os.tag; -const native_endian = native_arch.endian(); - -pub const MemoryAccessor = std.debug.MemoryAccessor; -pub const FixedBufferReader = std.debug.FixedBufferReader.zig; -pub const Dwarf = std.debug.Dwarf; -pub const Pdb = std.debug.Pdb; -pub const SelfInfo = std.debug.SelfInfo; -pub const Info = std.debug.Info; -pub const Coverage = std.debug.Coverage; - -pub const simple_panic = std.debug.simple_panic; -pub const no_panic = std.debug.no_panic; - -/// A fully-featured panic handler namespace which lowers all panics to calls to `panicFn`. -/// Safety panics will use formatted printing to provide a meaningful error message. -/// The signature of `panicFn` should match that of `defaultPanic`. -pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type { - return struct { - pub const call = panicFn; - pub fn sentinelMismatch(expected: anytype, found: @TypeOf(expected)) noreturn { - @branchHint(.cold); - std.debug.panicExtra(@returnAddress(), "sentinel mismatch: expected {any}, found {any}", .{ - expected, found, - }); - } - pub fn unwrapError(err: anyerror) noreturn { - @branchHint(.cold); - std.debug.panicExtra(@returnAddress(), "attempt to unwrap error: {s}", .{@errorName(err)}); - } - pub fn outOfBounds(index: usize, len: usize) noreturn { - @branchHint(.cold); - std.debug.panicExtra(@returnAddress(), "index out of bounds: index {d}, len {d}", .{ index, len }); - } - pub fn startGreaterThanEnd(start: usize, end: usize) noreturn { - @branchHint(.cold); - std.debug.panicExtra(@returnAddress(), "start index {d} is larger than end index {d}", .{ start, end }); - } - pub fn inactiveUnionField(active: anytype, accessed: @TypeOf(active)) noreturn { - @branchHint(.cold); - std.debug.panicExtra(@returnAddress(), "access of union field '{s}' while field '{s}' is active", .{ - @tagName(accessed), @tagName(active), - }); - } - pub fn sliceCastLenRemainder(src_len: usize) noreturn { - @branchHint(.cold); - std.debug.panicExtra(@returnAddress(), "slice length '{d}' does not divide exactly into destination elements", .{src_len}); - } - pub fn reachedUnreachable() noreturn { - @branchHint(.cold); - call("reached unreachable code", @returnAddress()); - } - pub fn unwrapNull() noreturn { - @branchHint(.cold); - call("attempt to use null value", @returnAddress()); - } - pub fn castToNull() noreturn { - @branchHint(.cold); - call("cast causes pointer to be null", @returnAddress()); - } - pub fn incorrectAlignment() noreturn { - @branchHint(.cold); - call("incorrect alignment", @returnAddress()); - } - pub fn invalidErrorCode() noreturn { - @branchHint(.cold); - call("invalid error code", @returnAddress()); - } - pub fn castTruncatedData() noreturn { - @branchHint(.cold); - call("integer cast truncated bits", @returnAddress()); - } - pub fn negativeToUnsigned() noreturn { - @branchHint(.cold); - call("attempt to cast negative value to unsigned integer", @returnAddress()); - } - pub fn integerOverflow() noreturn { - @branchHint(.cold); - call("integer overflow", @returnAddress()); - } - pub fn shlOverflow() noreturn { - @branchHint(.cold); - call("left shift overflowed bits", @returnAddress()); - } - pub fn shrOverflow() noreturn { - @branchHint(.cold); - call("right shift overflowed bits", @returnAddress()); - } - pub fn divideByZero() noreturn { - @branchHint(.cold); - call("division by zero", @returnAddress()); - } - pub fn exactDivisionRemainder() noreturn { - @branchHint(.cold); - call("exact division produced remainder", @returnAddress()); - } - pub fn integerPartOutOfBounds() noreturn { - @branchHint(.cold); - call("integer part of floating point value out of bounds", @returnAddress()); - } - pub fn corruptSwitch() noreturn { - @branchHint(.cold); - call("switch on corrupt value", @returnAddress()); - } - pub fn shiftRhsTooBig() noreturn { - @branchHint(.cold); - call("shift amount is greater than the type size", @returnAddress()); - } - pub fn invalidEnumValue() noreturn { - @branchHint(.cold); - call("invalid enum value", @returnAddress()); - } - pub fn forLenMismatch() noreturn { - @branchHint(.cold); - call("for loop over objects with non-equal lengths", @returnAddress()); - } - pub fn memcpyLenMismatch() noreturn { - @branchHint(.cold); - call("@memcpy arguments have non-equal lengths", @returnAddress()); - } - pub fn memcpyAlias() noreturn { - @branchHint(.cold); - call("@memcpy arguments alias", @returnAddress()); - } - pub fn noreturnReturned() noreturn { - @branchHint(.cold); - call("'noreturn' function returned", @returnAddress()); - } - }; -} - -pub const SourceLocation = std.debug.SourceLocation; - -pub const Symbol = std.debug.Symbol; - -/// Deprecated because it returns the optimization mode of the standard -/// library, when the caller probably wants to use the optimization mode of -/// their own module. -pub const runtime_safety = switch (builtin.mode) { - .Debug, .ReleaseSafe => true, - .ReleaseFast, .ReleaseSmall => false, -}; - -pub const sys_can_stack_trace = switch (builtin.cpu.arch) { - // Observed to go into an infinite loop. - // TODO: Make this work. - .mips, - .mipsel, - .mips64, - .mips64el, - .s390x, - => false, - - // `@returnAddress()` in LLVM 10 gives - // "Non-Emscripten WebAssembly hasn't implemented __builtin_return_address". - .wasm32, - .wasm64, - => native_os == .emscripten, - - // `@returnAddress()` is unsupported in LLVM 13. - .bpfel, - .bpfeb, - => false, - - else => true, -}; - -/// Allows the caller to freely write to stderr until `unlockStdErr` is called. -/// -/// During the lock, any `std.Progress` information is cleared from the terminal. -pub fn lockStdErr() void { - std.Progress.lockStdErr(); -} - -pub fn unlockStdErr() void { - std.Progress.unlockStdErr(); -} - -/// Print to stderr, unbuffered, and silently returning on failure. Intended -/// for use in "printf debugging." Use `std.log` functions for proper logging. -pub fn print(comptime fmt: []const u8, args: anytype) void { - lockStdErr(); - defer unlockStdErr(); - const stderr = io.getStdErr().writer(); - nosuspend stderr.print(fmt, args) catch return; -} - -pub fn getStderrMutex() *std.Thread.Mutex { - @compileError("deprecated. call std.debug.lockStdErr() and std.debug.unlockStdErr() instead which will integrate properly with std.Progress"); -} - -/// TODO multithreaded awareness -var self_debug_info: ?SelfInfo = null; - -pub fn getSelfDebugInfo() !*SelfInfo { - if (self_debug_info) |*info| { - return info; - } else { - self_debug_info = try SelfInfo.open(getDebugInfoAllocator()); - return &self_debug_info.?; - } -} - -/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned. -/// Obtains the stderr mutex while dumping. -pub fn dumpHex(bytes: []const u8) void { - lockStdErr(); - defer unlockStdErr(); - dumpHexFallible(bytes) catch {}; -} - -/// Prints a hexadecimal view of the bytes, unbuffered, returning any error that occurs. -pub fn dumpHexFallible(bytes: []const u8) !void { - const stderr = std.io.getStdErr(); - const ttyconf = std.io.tty.detectConfig(stderr); - const writer = stderr.writer(); - try dumpHexInternal(bytes, ttyconf, writer); -} - -fn dumpHexInternal(bytes: []const u8, ttyconf: std.io.tty.Config, writer: anytype) !void { - var chunks = mem.window(u8, bytes, 16, 16); - while (chunks.next()) |window| { - // 1. Print the address. - const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10; - try ttyconf.setColor(writer, .dim); - // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more. - // Also, make sure all lines are aligned by padding the address. - try writer.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 }); - try ttyconf.setColor(writer, .reset); - - // 2. Print the bytes. - for (window, 0..) |byte, index| { - try writer.print("{X:0>2} ", .{byte}); - if (index == 7) try writer.writeByte(' '); - } - try writer.writeByte(' '); - if (window.len < 16) { - var missing_columns = (16 - window.len) * 3; - if (window.len < 8) missing_columns += 1; - try writer.writeByteNTimes(' ', missing_columns); - } - - // 3. Print the characters. - for (window) |byte| { - if (std.ascii.isPrint(byte)) { - try writer.writeByte(byte); - } else { - // Related: https://github.com/ziglang/zig/issues/7600 - if (ttyconf == .windows_api) { - try writer.writeByte('.'); - continue; - } - - // Let's print some common control codes as graphical Unicode symbols. - // We don't want to do this for all control codes because most control codes apart from - // the ones that Zig has escape sequences for are likely not very useful to print as symbols. - switch (byte) { - '\n' => try writer.writeAll("␊"), - '\r' => try writer.writeAll("␍"), - '\t' => try writer.writeAll("␉"), - else => try writer.writeByte('.'), - } - } - } - try writer.writeByte('\n'); - } -} - -test dumpHexInternal { - const bytes: []const u8 = &.{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x12, 0x13 }; - var output = std.ArrayList(u8).init(std.testing.allocator); - defer output.deinit(); - try dumpHexInternal(bytes, .no_color, output.writer()); - const expected = try std.fmt.allocPrint(std.testing.allocator, - \\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........ - \\{x:0>[2]} 01 12 13 ... - \\ - , .{ - @intFromPtr(bytes.ptr), - @intFromPtr(bytes.ptr) + 16, - @sizeOf(usize) * 2, - }); - defer std.testing.allocator.free(expected); - try std.testing.expectEqualStrings(expected, output.items); -} - -/// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned. -/// TODO multithreaded awareness -pub fn dumpCurrentStackTrace(start_addr: ?usize) void { - nosuspend { - if (builtin.target.cpu.arch.isWasm()) { - if (native_os == .wasi) { - const stderr = io.getStdErr().writer(); - stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return; - } - return; - } - const stderr = io.getStdErr().writer(); - if (builtin.strip_debug_info) { - stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; - return; - } - const debug_info = getSelfDebugInfo() catch |err| { - stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; - return; - }; - writeCurrentStackTrace(stderr, debug_info, io.tty.detectConfig(io.getStdErr()), start_addr) catch |err| { - stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; - return; - }; - } -} - -pub const have_ucontext = posix.ucontext_t != void; - -/// Platform-specific thread state. This contains register state, and on some platforms -/// information about the stack. This is not safe to trivially copy, because some platforms -/// use internal pointers within this structure. To make a copy, use `copyContext`. -pub const ThreadContext = blk: { - if (native_os == .windows) { - break :blk windows.CONTEXT; - } else if (have_ucontext) { - break :blk posix.ucontext_t; - } else { - break :blk void; - } -}; - -/// Copies one context to another, updating any internal pointers -pub fn copyContext(source: *const ThreadContext, dest: *ThreadContext) void { - if (!have_ucontext) return {}; - dest.* = source.*; - relocateContext(dest); -} - -/// Updates any internal pointers in the context to reflect its current location -pub fn relocateContext(context: *ThreadContext) void { - return switch (native_os) { - .macos => { - context.mcontext = &context.__mcontext_data; - }, - else => {}, - }; -} - -pub const have_getcontext = @TypeOf(posix.system.getcontext) != void; - -/// Capture the current context. The register values in the context will reflect the -/// state after the platform `getcontext` function returns. -/// -/// It is valid to call this if the platform doesn't have context capturing support, -/// in that case false will be returned. -pub inline fn getContext(context: *ThreadContext) bool { - if (native_os == .windows) { - context.* = std.mem.zeroes(windows.CONTEXT); - windows.ntdll.RtlCaptureContext(context); - return true; - } - - const result = have_getcontext and posix.system.getcontext(context) == 0; - if (native_os == .macos) { - assert(context.mcsize == @sizeOf(std.c.mcontext_t)); - - // On aarch64-macos, the system getcontext doesn't write anything into the pc - // register slot, it only writes lr. This makes the context consistent with - // other aarch64 getcontext implementations which write the current lr - // (where getcontext will return to) into both the lr and pc slot of the context. - if (native_arch == .aarch64) context.mcontext.ss.pc = context.mcontext.ss.lr; - } - - return result; -} - -/// Tries to print the stack trace starting from the supplied base pointer to stderr, -/// unbuffered, and ignores any error returned. -/// TODO multithreaded awareness -pub fn dumpStackTraceFromBase(context: *ThreadContext) void { - nosuspend { - if (builtin.target.cpu.arch.isWasm()) { - if (native_os == .wasi) { - const stderr = io.getStdErr().writer(); - stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return; - } - return; - } - const stderr = io.getStdErr().writer(); - if (builtin.strip_debug_info) { - stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; - return; - } - const debug_info = getSelfDebugInfo() catch |err| { - stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; - return; - }; - const tty_config = io.tty.detectConfig(io.getStdErr()); - if (native_os == .windows) { - // On x86_64 and aarch64, the stack will be unwound using RtlVirtualUnwind using the context - // provided by the exception handler. On x86, RtlVirtualUnwind doesn't exist. Instead, a new backtrace - // will be captured and frames prior to the exception will be filtered. - // The caveat is that RtlCaptureStackBackTrace does not include the KiUserExceptionDispatcher frame, - // which is where the IP in `context` points to, so it can't be used as start_addr. - // Instead, start_addr is recovered from the stack. - const start_addr = if (builtin.cpu.arch == .x86) @as(*const usize, @ptrFromInt(context.getRegs().bp + 4)).* else null; - writeStackTraceWindows(stderr, debug_info, tty_config, context, start_addr) catch return; - return; - } - - var it = StackIterator.initWithContext(null, debug_info, context) catch return; - defer it.deinit(); - - // DWARF unwinding on aarch64-macos is not complete so we need to get pc address from mcontext - const pc_addr = if (builtin.target.os.tag.isDarwin() and native_arch == .aarch64) - context.mcontext.ss.pc - else - it.unwind_state.?.dwarf_context.pc; - printSourceAtAddress(debug_info, stderr, pc_addr, tty_config) catch return; - - while (it.next()) |return_address| { - printLastUnwindError(&it, debug_info, stderr, tty_config); - - // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, - // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid - // an overflow. We do not need to signal `StackIterator` as it will correctly detect this - // condition on the subsequent iteration and return `null` thus terminating the loop. - // same behaviour for x86-windows-msvc - const address = if (return_address == 0) return_address else return_address - 1; - printSourceAtAddress(debug_info, stderr, address, tty_config) catch return; - } else printLastUnwindError(&it, debug_info, stderr, tty_config); - } -} - -/// Returns a slice with the same pointer as addresses, with a potentially smaller len. -/// On Windows, when first_address is not null, we ask for at least 32 stack frames, -/// and then try to find the first address. If addresses.len is more than 32, we -/// capture that many stack frames exactly, and then look for the first address, -/// chopping off the irrelevant frames and shifting so that the returned addresses pointer -/// equals the passed in addresses pointer. -pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackTrace) void { - if (native_os == .windows) { - const addrs = stack_trace.instruction_addresses; - const first_addr = first_address orelse { - stack_trace.index = walkStackWindows(addrs[0..], null); - return; - }; - var addr_buf_stack: [32]usize = undefined; - const addr_buf = if (addr_buf_stack.len > addrs.len) addr_buf_stack[0..] else addrs; - const n = walkStackWindows(addr_buf[0..], null); - const first_index = for (addr_buf[0..n], 0..) |addr, i| { - if (addr == first_addr) { - break i; - } - } else { - stack_trace.index = 0; - return; - }; - const end_index = @min(first_index + addrs.len, n); - const slice = addr_buf[first_index..end_index]; - // We use a for loop here because slice and addrs may alias. - for (slice, 0..) |addr, i| { - addrs[i] = addr; - } - stack_trace.index = slice.len; - } else { - // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required). - // A new path for loading SelfInfo needs to be created which will only attempt to parse in-memory sections, because - // stopping to load other debug info (ie. source line info) from disk here is not required for unwinding. - var it = StackIterator.init(first_address, null); - defer it.deinit(); - for (stack_trace.instruction_addresses, 0..) |*addr, i| { - addr.* = it.next() orelse { - stack_trace.index = i; - return; - }; - } - stack_trace.index = stack_trace.instruction_addresses.len; - } -} - -/// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned. -/// TODO multithreaded awareness -pub fn dumpStackTrace(stack_trace: std.builtin.StackTrace) void { - nosuspend { - if (builtin.target.cpu.arch.isWasm()) { - if (native_os == .wasi) { - const stderr = io.getStdErr().writer(); - stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return; - } - return; - } - const stderr = io.getStdErr().writer(); - if (builtin.strip_debug_info) { - stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; - return; - } - const debug_info = getSelfDebugInfo() catch |err| { - stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; - return; - }; - writeStackTrace(stack_trace, stderr, debug_info, io.tty.detectConfig(io.getStdErr())) catch |err| { - stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; - return; - }; - } -} - -/// Invokes detectable illegal behavior when `ok` is `false`. -/// -/// In Debug and ReleaseSafe modes, calls to this function are always -/// generated, and the `unreachable` statement triggers a panic. -/// -/// In ReleaseFast and ReleaseSmall modes, calls to this function are optimized -/// away, and in fact the optimizer is able to use the assertion in its -/// heuristics. -/// -/// Inside a test block, it is best to use the `std.testing` module rather than -/// this function, because this function may not detect a test failure in -/// ReleaseFast and ReleaseSmall mode. Outside of a test block, this assert -/// function is the correct function to use. -pub fn assert(ok: bool) void { - if (!ok) unreachable; // assertion failure -} - -/// Invokes detectable illegal behavior when the provided slice is not mapped -/// or lacks read permissions. -pub fn assertReadable(slice: []const volatile u8) void { - if (!runtime_safety) return; - for (slice) |*byte| _ = byte.*; -} - -/// Equivalent to `@panic` but with a formatted message. -pub fn panic(comptime format: []const u8, args: anytype) noreturn { - @branchHint(.cold); - panicExtra(@returnAddress(), format, args); -} - -/// Equivalent to `@panic` but with a formatted message, and with an explicitly -/// provided return address. -pub fn panicExtra( - ret_addr: ?usize, - comptime format: []const u8, - args: anytype, -) noreturn { - @branchHint(.cold); - - const size = 0x1000; - const trunc_msg = "(msg truncated)"; - var buf: [size + trunc_msg.len]u8 = undefined; - // a minor annoyance with this is that it will result in the NoSpaceLeft - // error being part of the @panic stack trace (but that error should - // only happen rarely) - const msg = std.fmt.bufPrint(buf[0..size], format, args) catch |err| switch (err) { - error.NoSpaceLeft => blk: { - @memcpy(buf[size..], trunc_msg); - break :blk &buf; - }, - }; - std.builtin.panic.call(msg, ret_addr); -} - -/// Non-zero whenever the program triggered a panic. -/// The counter is incremented/decremented atomically. -var panicking = std.atomic.Value(u8).init(0); - -/// Counts how many times the panic handler is invoked by this thread. -/// This is used to catch and handle panics triggered by the panic handler. -threadlocal var panic_stage: usize = 0; - -/// Dumps a stack trace to standard error, then aborts. -pub fn defaultPanic( - msg: []const u8, - first_trace_addr: ?usize, -) noreturn { - @branchHint(.cold); - - // For backends that cannot handle the language features depended on by the - // default panic handler, we have a simpler panic handler: - if (builtin.zig_backend == .stage2_wasm or - builtin.zig_backend == .stage2_arm or - builtin.zig_backend == .stage2_aarch64 or - builtin.zig_backend == .stage2_x86 or - (builtin.zig_backend == .stage2_x86_64 and (builtin.target.ofmt != .elf and builtin.target.ofmt != .macho)) or - builtin.zig_backend == .stage2_sparc64 or - builtin.zig_backend == .stage2_spirv64) - { - @trap(); - } - - switch (builtin.os.tag) { - .freestanding, .other => { - @trap(); - }, - .uefi => { - const uefi = std.os.uefi; - - var utf16_buffer: [1000]u16 = undefined; - const len_minus_3 = std.unicode.utf8ToUtf16Le(&utf16_buffer, msg) catch 0; - utf16_buffer[len_minus_3..][0..3].* = .{ '\r', '\n', 0 }; - const len = len_minus_3 + 3; - const exit_msg = utf16_buffer[0 .. len - 1 :0]; - - // Output to both std_err and con_out, as std_err is easier - // to read in stuff like QEMU at times, but, unlike con_out, - // isn't visible on actual hardware if directly booted into - inline for ([_]?*uefi.protocol.SimpleTextOutput{ uefi.system_table.std_err, uefi.system_table.con_out }) |o| { - if (o) |out| { - _ = out.setAttribute(uefi.protocol.SimpleTextOutput.red); - _ = out.outputString(exit_msg); - _ = out.setAttribute(uefi.protocol.SimpleTextOutput.white); - } - } - - if (uefi.system_table.boot_services) |bs| { - // ExitData buffer must be allocated using boot_services.allocatePool (spec: page 220) - const exit_data: []u16 = uefi.raw_pool_allocator.alloc(u16, exit_msg.len + 1) catch @trap(); - @memcpy(exit_data, exit_msg[0..exit_data.len]); // Includes null terminator. - _ = bs.exit(uefi.handle, .aborted, exit_data.len, exit_data.ptr); - } - @trap(); - }, - .cuda, .amdhsa => std.posix.abort(), - .plan9 => { - var status: [std.os.plan9.ERRMAX]u8 = undefined; - const len = @min(msg.len, status.len - 1); - @memcpy(status[0..len], msg[0..len]); - status[len] = 0; - std.os.plan9.exits(status[0..len :0]); - }, - else => {}, - } - - if (enable_segfault_handler) { - // If a segfault happens while panicking, we want it to actually segfault, not trigger - // the handler. - resetSegfaultHandler(); - } - - // Note there is similar logic in handleSegfaultPosix and handleSegfaultWindowsExtra. - nosuspend switch (panic_stage) { - 0 => { - panic_stage = 1; - - _ = panicking.fetchAdd(1, .seq_cst); - - { - lockStdErr(); - defer unlockStdErr(); - - const stderr = io.getStdErr().writer(); - if (builtin.single_threaded) { - stderr.print("panic: ", .{}) catch posix.abort(); - } else { - const current_thread_id = std.Thread.getCurrentId(); - stderr.print("thread {} panic: ", .{current_thread_id}) catch posix.abort(); - } - stderr.print("{s}\n", .{msg}) catch posix.abort(); - - if (@errorReturnTrace()) |t| dumpStackTrace(t.*); - dumpCurrentStackTrace(first_trace_addr orelse @returnAddress()); - } - - waitForOtherThreadToFinishPanicking(); - }, - 1 => { - panic_stage = 2; - - // A panic happened while trying to print a previous panic message. - // We're still holding the mutex but that's fine as we're going to - // call abort(). - io.getStdErr().writeAll("aborting due to recursive panic\n") catch {}; - }, - else => {}, // Panicked while printing the recursive panic message. - }; - - posix.abort(); -} - -/// Must be called only after adding 1 to `panicking`. There are three callsites. -fn waitForOtherThreadToFinishPanicking() void { - if (panicking.fetchSub(1, .seq_cst) != 1) { - // Another thread is panicking, wait for the last one to finish - // and call abort() - if (builtin.single_threaded) unreachable; - - // Sleep forever without hammering the CPU - var futex = std.atomic.Value(u32).init(0); - while (true) std.Thread.Futex.wait(&futex, 0); - unreachable; - } -} - -pub fn writeStackTrace( - stack_trace: std.builtin.StackTrace, - out_stream: anytype, - debug_info: *SelfInfo, - tty_config: io.tty.Config, -) !void { - if (builtin.strip_debug_info) return error.MissingDebugInfo; - var frame_index: usize = 0; - var frames_left: usize = @min(stack_trace.index, stack_trace.instruction_addresses.len); - - while (frames_left != 0) : ({ - frames_left -= 1; - frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len; - }) { - const return_address = stack_trace.instruction_addresses[frame_index]; - try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_config); - } - - if (stack_trace.index > stack_trace.instruction_addresses.len) { - const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len; - - tty_config.setColor(out_stream, .bold) catch {}; - try out_stream.print("({d} additional stack frames skipped...)\n", .{dropped_frames}); - tty_config.setColor(out_stream, .reset) catch {}; - } -} - -pub const UnwindError = if (have_ucontext) - @typeInfo(@typeInfo(@TypeOf(StackIterator.next_unwind)).@"fn".return_type.?).error_union.error_set -else - void; - -pub const StackIterator = struct { - // Skip every frame before this address is found. - first_address: ?usize, - // Last known value of the frame pointer register. - fp: usize, - ma: MemoryAccessor = MemoryAccessor.init, - - // When SelfInfo and a register context is available, this iterator can unwind - // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer), - // using DWARF and MachO unwind info. - unwind_state: if (have_ucontext) ?struct { - debug_info: *SelfInfo, - dwarf_context: SelfInfo.UnwindContext, - last_error: ?UnwindError = null, - failed: bool = false, - } else void = if (have_ucontext) null else {}, - - pub fn init(first_address: ?usize, fp: ?usize) StackIterator { - if (native_arch.isSPARC()) { - // Flush all the register windows on stack. - asm volatile (if (std.Target.sparc.featureSetHas(builtin.cpu.features, .v9)) - "flushw" - else - "ta 3" // ST_FLUSH_WINDOWS - ::: "memory"); - } - - return StackIterator{ - .first_address = first_address, - // TODO: this is a workaround for #16876 - //.fp = fp orelse @frameAddress(), - .fp = fp orelse blk: { - const fa = @frameAddress(); - break :blk fa; - }, - }; - } - - pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t) !StackIterator { - // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that - // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder. - if (builtin.target.os.tag.isDarwin() and native_arch == .aarch64) - return init(first_address, @truncate(context.mcontext.ss.fp)); - - if (SelfInfo.supports_unwinding) { - var iterator = init(first_address, null); - iterator.unwind_state = .{ - .debug_info = debug_info, - .dwarf_context = try SelfInfo.UnwindContext.init(debug_info.allocator, context), - }; - return iterator; - } - - return init(first_address, null); - } - - pub fn deinit(it: *StackIterator) void { - it.ma.deinit(); - if (have_ucontext and it.unwind_state != null) it.unwind_state.?.dwarf_context.deinit(); - } - - pub fn getLastError(it: *StackIterator) ?struct { - err: UnwindError, - address: usize, - } { - if (!have_ucontext) return null; - if (it.unwind_state) |*unwind_state| { - if (unwind_state.last_error) |err| { - unwind_state.last_error = null; - return .{ - .err = err, - .address = unwind_state.dwarf_context.pc, - }; - } - } - - return null; - } - - // Offset of the saved BP wrt the frame pointer. - const fp_offset = if (native_arch.isRISCV()) - // On RISC-V the frame pointer points to the top of the saved register - // area, on pretty much every other architecture it points to the stack - // slot where the previous frame pointer is saved. - 2 * @sizeOf(usize) - else if (native_arch.isSPARC()) - // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS. - 14 * @sizeOf(usize) - else - 0; - - const fp_bias = if (native_arch.isSPARC()) - // On SPARC frame pointers are biased by a constant. - 2047 - else - 0; - - // Positive offset of the saved PC wrt the frame pointer. - const pc_offset = if (native_arch == .powerpc64le) - 2 * @sizeOf(usize) - else - @sizeOf(usize); - - pub fn next(it: *StackIterator) ?usize { - var address = it.next_internal() orelse return null; - - if (it.first_address) |first_address| { - while (address != first_address) { - address = it.next_internal() orelse return null; - } - it.first_address = null; - } - - return address; - } - - fn next_unwind(it: *StackIterator) !usize { - const unwind_state = &it.unwind_state.?; - const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc); - switch (native_os) { - .macos, .ios, .watchos, .tvos, .visionos => { - // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding - // via DWARF before attempting to use the compact unwind info will produce incorrect results. - if (module.unwind_info) |unwind_info| { - if (SelfInfo.unwindFrameMachO( - unwind_state.debug_info.allocator, - module.base_address, - &unwind_state.dwarf_context, - &it.ma, - unwind_info, - module.eh_frame, - )) |return_address| { - return return_address; - } else |err| { - if (err != error.RequiresDWARFUnwind) return err; - } - } else return error.MissingUnwindInfo; - }, - else => {}, - } - - if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| { - return SelfInfo.unwindFrameDwarf( - unwind_state.debug_info.allocator, - di, - module.base_address, - &unwind_state.dwarf_context, - &it.ma, - null, - ); - } else return error.MissingDebugInfo; - } - - fn next_internal(it: *StackIterator) ?usize { - if (have_ucontext) { - if (it.unwind_state) |*unwind_state| { - if (!unwind_state.failed) { - if (unwind_state.dwarf_context.pc == 0) return null; - defer it.fp = unwind_state.dwarf_context.getFp() catch 0; - if (it.next_unwind()) |return_address| { - return return_address; - } else |err| { - unwind_state.last_error = err; - unwind_state.failed = true; - - // Fall back to fp-based unwinding on the first failure. - // We can't attempt it again for other modules higher in the - // stack because the full register state won't have been unwound. - } - } - } - } - - const fp = if (comptime native_arch.isSPARC()) - // On SPARC the offset is positive. (!) - math.add(usize, it.fp, fp_offset) catch return null - else - math.sub(usize, it.fp, fp_offset) catch return null; - - // Sanity check. - if (fp == 0 or !mem.isAligned(fp, @alignOf(usize))) return null; - const new_fp = math.add(usize, it.ma.load(usize, fp) orelse return null, fp_bias) catch - return null; - - // Sanity check: the stack grows down thus all the parent frames must be - // be at addresses that are greater (or equal) than the previous one. - // A zero frame pointer often signals this is the last frame, that case - // is gracefully handled by the next call to next_internal. - if (new_fp != 0 and new_fp < it.fp) return null; - const new_pc = it.ma.load(usize, math.add(usize, fp, pc_offset) catch return null) orelse - return null; - - it.fp = new_fp; - - return new_pc; - } -}; - -pub fn writeCurrentStackTrace( - out_stream: anytype, - debug_info: *SelfInfo, - tty_config: io.tty.Config, - start_addr: ?usize, -) !void { - if (native_os == .windows) { - var context: ThreadContext = undefined; - assert(getContext(&context)); - return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr); - } - var context: ThreadContext = undefined; - const has_context = getContext(&context); - - var it = (if (has_context) blk: { - break :blk StackIterator.initWithContext(start_addr, debug_info, &context) catch null; - } else null) orelse StackIterator.init(start_addr, null); - defer it.deinit(); - - while (it.next()) |return_address| { - printLastUnwindError(&it, debug_info, out_stream, tty_config); - - // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, - // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid - // an overflow. We do not need to signal `StackIterator` as it will correctly detect this - // condition on the subsequent iteration and return `null` thus terminating the loop. - // same behaviour for x86-windows-msvc - const address = return_address -| 1; - try printSourceAtAddress(debug_info, out_stream, address, tty_config); - } else printLastUnwindError(&it, debug_info, out_stream, tty_config); -} - -pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize { - if (builtin.cpu.arch == .x86) { - // RtlVirtualUnwind doesn't exist on x86 - return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null); - } - - const tib = &windows.teb().NtTib; - - var context: windows.CONTEXT = undefined; - if (existing_context) |context_ptr| { - context = context_ptr.*; - } else { - context = std.mem.zeroes(windows.CONTEXT); - windows.ntdll.RtlCaptureContext(&context); - } - - var i: usize = 0; - var image_base: windows.DWORD64 = undefined; - var history_table: windows.UNWIND_HISTORY_TABLE = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE); - - while (i < addresses.len) : (i += 1) { - const current_regs = context.getRegs(); - if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &history_table)) |runtime_function| { - var handler_data: ?*anyopaque = null; - var establisher_frame: u64 = undefined; - _ = windows.ntdll.RtlVirtualUnwind( - windows.UNW_FLAG_NHANDLER, - image_base, - current_regs.ip, - runtime_function, - &context, - &handler_data, - &establisher_frame, - null, - ); - } else { - // leaf function - context.setIp(@as(*usize, @ptrFromInt(current_regs.sp)).*); - context.setSp(current_regs.sp + @sizeOf(usize)); - } - - const next_regs = context.getRegs(); - if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) { - break; - } - - if (next_regs.ip == 0) { - break; - } - - addresses[i] = next_regs.ip; - } - - return i; -} - -pub fn writeStackTraceWindows( - out_stream: anytype, - debug_info: *SelfInfo, - tty_config: io.tty.Config, - context: *const windows.CONTEXT, - start_addr: ?usize, -) !void { - var addr_buf: [1024]usize = undefined; - const n = walkStackWindows(addr_buf[0..], context); - const addrs = addr_buf[0..n]; - const start_i: usize = if (start_addr) |saddr| blk: { - for (addrs, 0..) |addr, i| { - if (addr == saddr) break :blk i; - } - return; - } else 0; - for (addrs[start_i..]) |addr| { - try printSourceAtAddress(debug_info, out_stream, addr - 1, tty_config); - } -} - -fn printUnknownSource(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { - const module_name = debug_info.getModuleNameForAddress(address); - return printLineInfo( - out_stream, - null, - address, - "???", - module_name orelse "???", - tty_config, - printLineFromFileAnyOs, - ); -} - -fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, out_stream: anytype, tty_config: io.tty.Config) void { - if (!have_ucontext) return; - if (it.getLastError()) |unwind_error| { - printUnwindError(debug_info, out_stream, unwind_error.address, unwind_error.err, tty_config) catch {}; - } -} - -fn printUnwindError(debug_info: *SelfInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { - const module_name = debug_info.getModuleNameForAddress(address) orelse "???"; - try tty_config.setColor(out_stream, .dim); - if (err == error.MissingDebugInfo) { - try out_stream.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address }); - } else { - try out_stream.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, err }); - } - try tty_config.setColor(out_stream, .reset); -} - -pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { - const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), - else => return err, - }; - - const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), - else => return err, - }; - defer if (symbol_info.source_location) |sl| debug_info.allocator.free(sl.file_name); - - return printLineInfo( - out_stream, - symbol_info.source_location, - address, - symbol_info.name, - symbol_info.compile_unit_name, - tty_config, - printLineFromFileAnyOs, - ); -} - -fn printLineInfo( - out_stream: anytype, - source_location: ?SourceLocation, - address: usize, - symbol_name: []const u8, - compile_unit_name: []const u8, - tty_config: io.tty.Config, - comptime printLineFromFile: anytype, -) !void { - nosuspend { - try tty_config.setColor(out_stream, .bold); - - if (source_location) |*sl| { - try out_stream.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); - } else { - try out_stream.writeAll("???:?:?"); - } - - try tty_config.setColor(out_stream, .reset); - try out_stream.writeAll(": "); - try tty_config.setColor(out_stream, .dim); - try out_stream.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); - try tty_config.setColor(out_stream, .reset); - try out_stream.writeAll("\n"); - - // Show the matching source code line if possible - if (source_location) |sl| { - if (printLineFromFile(out_stream, sl)) { - if (sl.column > 0) { - // The caret already takes one char - const space_needed = @as(usize, @intCast(sl.column - 1)); - - try out_stream.writeByteNTimes(' ', space_needed); - try tty_config.setColor(out_stream, .green); - try out_stream.writeAll("^"); - try tty_config.setColor(out_stream, .reset); - } - try out_stream.writeAll("\n"); - } else |err| switch (err) { - error.EndOfFile, error.FileNotFound => {}, - error.BadPathName => {}, - error.AccessDenied => {}, - else => return err, - } - } - } -} - -fn printLineFromFileAnyOs(out_stream: anytype, source_location: SourceLocation) !void { - // Need this to always block even in async I/O mode, because this could potentially - // be called from e.g. the event loop code crashing. - var f = try fs.cwd().openFile(source_location.file_name, .{}); - defer f.close(); - // TODO fstat and make sure that the file has the correct size - - var buf: [4096]u8 = undefined; - var amt_read = try f.read(buf[0..]); - const line_start = seek: { - var current_line_start: usize = 0; - var next_line: usize = 1; - while (next_line != source_location.line) { - const slice = buf[current_line_start..amt_read]; - if (mem.indexOfScalar(u8, slice, '\n')) |pos| { - next_line += 1; - if (pos == slice.len - 1) { - amt_read = try f.read(buf[0..]); - current_line_start = 0; - } else current_line_start += pos + 1; - } else if (amt_read < buf.len) { - return error.EndOfFile; - } else { - amt_read = try f.read(buf[0..]); - current_line_start = 0; - } - } - break :seek current_line_start; - }; - const slice = buf[line_start..amt_read]; - if (mem.indexOfScalar(u8, slice, '\n')) |pos| { - const line = slice[0 .. pos + 1]; - mem.replaceScalar(u8, line, '\t', ' '); - return out_stream.writeAll(line); - } else { // Line is the last inside the buffer, and requires another read to find delimiter. Alternatively the file ends. - mem.replaceScalar(u8, slice, '\t', ' '); - try out_stream.writeAll(slice); - while (amt_read == buf.len) { - amt_read = try f.read(buf[0..]); - if (mem.indexOfScalar(u8, buf[0..amt_read], '\n')) |pos| { - const line = buf[0 .. pos + 1]; - mem.replaceScalar(u8, line, '\t', ' '); - return out_stream.writeAll(line); - } else { - const line = buf[0..amt_read]; - mem.replaceScalar(u8, line, '\t', ' '); - try out_stream.writeAll(line); - } - } - // Make sure printing last line of file inserts extra newline - try out_stream.writeByte('\n'); - } -} - -test printLineFromFileAnyOs { - var output = std.ArrayList(u8).init(std.testing.allocator); - defer output.deinit(); - const output_stream = output.writer(); - - const allocator = std.testing.allocator; - const join = std.fs.path.join; - const expectError = std.testing.expectError; - const expectEqualStrings = std.testing.expectEqualStrings; - - var test_dir = std.testing.tmpDir(.{}); - defer test_dir.cleanup(); - // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths. - const test_dir_path = try join(allocator, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] }); - defer allocator.free(test_dir_path); - - // Cases - { - const path = try join(allocator, &.{ test_dir_path, "one_line.zig" }); - defer allocator.free(path); - try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" }); - - try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); - - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); - try expectEqualStrings("no new lines in this file, but one is printed anyway\n", output.items); - output.clearRetainingCapacity(); - } - { - const path = try fs.path.join(allocator, &.{ test_dir_path, "three_lines.zig" }); - defer allocator.free(path); - try test_dir.dir.writeFile(.{ - .sub_path = "three_lines.zig", - .data = - \\1 - \\2 - \\3 - , - }); - - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); - try expectEqualStrings("1\n", output.items); - output.clearRetainingCapacity(); - - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 3, .column = 0 }); - try expectEqualStrings("3\n", output.items); - output.clearRetainingCapacity(); - } - { - const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{}); - defer file.close(); - const path = try fs.path.join(allocator, &.{ test_dir_path, "line_overlaps_page_boundary.zig" }); - defer allocator.free(path); - - const overlap = 10; - var writer = file.writer(); - try writer.writeByteNTimes('a', std.heap.page_size_min - overlap); - try writer.writeByte('\n'); - try writer.writeByteNTimes('a', overlap); - - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); - try expectEqualStrings(("a" ** overlap) ++ "\n", output.items); - output.clearRetainingCapacity(); - } - { - const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{}); - defer file.close(); - const path = try fs.path.join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); - defer allocator.free(path); - - var writer = file.writer(); - try writer.writeByteNTimes('a', std.heap.page_size_max); - - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); - try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", output.items); - output.clearRetainingCapacity(); - } - { - const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{}); - defer file.close(); - const path = try fs.path.join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); - defer allocator.free(path); - - var writer = file.writer(); - try writer.writeByteNTimes('a', 3 * std.heap.page_size_max); - - try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); - - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); - try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", output.items); - output.clearRetainingCapacity(); - - try writer.writeAll("a\na"); - - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); - try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", output.items); - output.clearRetainingCapacity(); - - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); - try expectEqualStrings("a\n", output.items); - output.clearRetainingCapacity(); - } - { - const file = try test_dir.dir.createFile("file_of_newlines.zig", .{}); - defer file.close(); - const path = try fs.path.join(allocator, &.{ test_dir_path, "file_of_newlines.zig" }); - defer allocator.free(path); - - var writer = file.writer(); - const real_file_start = 3 * std.heap.page_size_min; - try writer.writeByteNTimes('\n', real_file_start); - try writer.writeAll("abc\ndef"); - - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 }); - try expectEqualStrings("abc\n", output.items); - output.clearRetainingCapacity(); - - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 }); - try expectEqualStrings("def\n", output.items); - output.clearRetainingCapacity(); - } -} - -/// TODO multithreaded awareness -var debug_info_allocator: ?mem.Allocator = null; -var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined; -fn getDebugInfoAllocator() mem.Allocator { - if (debug_info_allocator) |a| return a; - - debug_info_arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator); - const allocator = debug_info_arena_allocator.allocator(); - debug_info_allocator = allocator; - return allocator; -} - -/// Whether or not the current target can print useful debug information when a segfault occurs. -pub const have_segfault_handling_support = switch (native_os) { - .linux, - .macos, - .netbsd, - .solaris, - .illumos, - .windows, - => true, - - .freebsd, .openbsd => have_ucontext, - else => false, -}; - -const enable_segfault_handler = std.options.enable_segfault_handler; -pub const default_enable_segfault_handler = runtime_safety and have_segfault_handling_support; - -pub fn maybeEnableSegfaultHandler() void { - if (enable_segfault_handler) { - attachSegfaultHandler(); - } -} - -var windows_segfault_handle: ?windows.HANDLE = null; - -pub fn updateSegfaultHandler(act: ?*const posix.Sigaction) void { - posix.sigaction(posix.SIG.SEGV, act, null); - posix.sigaction(posix.SIG.ILL, act, null); - posix.sigaction(posix.SIG.BUS, act, null); - posix.sigaction(posix.SIG.FPE, act, null); -} - -/// Attaches a global SIGSEGV handler which calls `@panic("segmentation fault");` -pub fn attachSegfaultHandler() void { - if (!have_segfault_handling_support) { - @compileError("segfault handler not supported for this target"); - } - if (native_os == .windows) { - windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); - return; - } - var act = posix.Sigaction{ - .handler = .{ .sigaction = handleSegfaultPosix }, - .mask = posix.empty_sigset, - .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND), - }; - - updateSegfaultHandler(&act); -} - -fn resetSegfaultHandler() void { - if (native_os == .windows) { - if (windows_segfault_handle) |handle| { - assert(windows.kernel32.RemoveVectoredExceptionHandler(handle) != 0); - windows_segfault_handle = null; - } - return; - } - var act = posix.Sigaction{ - .handler = .{ .handler = posix.SIG.DFL }, - .mask = posix.empty_sigset, - .flags = 0, - }; - updateSegfaultHandler(&act); -} - -pub fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn { - // Reset to the default handler so that if a segfault happens in this handler it will crash - // the process. Also when this handler returns, the original instruction will be repeated - // and the resulting segfault will crash the process rather than continually dump stack traces. - resetSegfaultHandler(); - - const addr = switch (native_os) { - .linux => @intFromPtr(info.fields.sigfault.addr), - .freebsd, .macos => @intFromPtr(info.addr), - .netbsd => @intFromPtr(info.info.reason.fault.addr), - .openbsd => @intFromPtr(info.data.fault.addr), - .solaris, .illumos => @intFromPtr(info.reason.fault.addr), - else => unreachable, - }; - - const code = if (native_os == .netbsd) info.info.code else info.code; - nosuspend switch (panic_stage) { - 0 => { - panic_stage = 1; - _ = panicking.fetchAdd(1, .seq_cst); - - { - lockStdErr(); - defer unlockStdErr(); - - dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr); - } - - waitForOtherThreadToFinishPanicking(); - }, - else => { - // panic mutex already locked - dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr); - }, - }; - - // We cannot allow the signal handler to return because when it runs the original instruction - // again, the memory may be mapped and undefined behavior would occur rather than repeating - // the segfault. So we simply abort here. - posix.abort(); -} - -fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void { - const stderr = io.getStdErr().writer(); - _ = switch (sig) { - posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL - // x86_64 doesn't have a full 64-bit virtual address space. - // Addresses outside of that address space are non-canonical - // and the CPU won't provide the faulting address to us. - // This happens when accessing memory addresses such as 0xaaaaaaaaaaaaaaaa - // but can also happen when no addressable memory is involved; - // for example when reading/writing model-specific registers - // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode). - stderr.print("General protection exception (no address available)\n", .{}) - else - stderr.print("Segmentation fault at address 0x{x}\n", .{addr}), - posix.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}), - posix.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}), - posix.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}), - else => unreachable, - } catch posix.abort(); - - switch (native_arch) { - .x86, - .x86_64, - .arm, - .armeb, - .thumb, - .thumbeb, - .aarch64, - .aarch64_be, - => { - // Some kernels don't align `ctx_ptr` properly. Handle this defensively. - const ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr); - var new_ctx: posix.ucontext_t = ctx.*; - if (builtin.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) { - // The kernel incorrectly writes the contents of `__mcontext_data` right after `mcontext`, - // rather than after the 8 bytes of padding that are supposed to sit between the two. Copy the - // contents to the right place so that the `mcontext` pointer will be correct after the - // `relocateContext` call below. - new_ctx.__mcontext_data = @as(*align(1) extern struct { - onstack: c_int, - sigmask: std.c.sigset_t, - stack: std.c.stack_t, - link: ?*std.c.ucontext_t, - mcsize: u64, - mcontext: *std.c.mcontext_t, - __mcontext_data: std.c.mcontext_t align(@sizeOf(usize)), // Disable padding after `mcontext`. - }, @ptrCast(ctx)).__mcontext_data; - } - relocateContext(&new_ctx); - dumpStackTraceFromBase(&new_ctx); - }, - else => {}, - } -} - -fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long { - switch (info.ExceptionRecord.ExceptionCode) { - windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, 0, "Unaligned Memory Access"), - windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, 1, null), - windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, 2, null), - windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, 0, "Stack Overflow"), - else => return windows.EXCEPTION_CONTINUE_SEARCH, - } -} - -fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) noreturn { - comptime assert(windows.CONTEXT != void); - nosuspend switch (panic_stage) { - 0 => { - panic_stage = 1; - _ = panicking.fetchAdd(1, .seq_cst); - - { - lockStdErr(); - defer unlockStdErr(); - - dumpSegfaultInfoWindows(info, msg, label); - } - - waitForOtherThreadToFinishPanicking(); - }, - 1 => { - panic_stage = 2; - io.getStdErr().writeAll("aborting due to recursive panic\n") catch {}; - }, - else => {}, - }; - posix.abort(); -} - -fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void { - const stderr = io.getStdErr().writer(); - _ = switch (msg) { - 0 => stderr.print("{s}\n", .{label.?}), - 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}), - 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{info.ContextRecord.getRegs().ip}), - else => unreachable, - } catch posix.abort(); - - dumpStackTraceFromBase(info.ContextRecord); -} - -pub fn dumpStackPointerAddr(prefix: []const u8) void { - const sp = asm ("" - : [argc] "={rsp}" (-> usize), - ); - print("{s} sp = 0x{x}\n", .{ prefix, sp }); -} - -test "manage resources correctly" { - if (builtin.strip_debug_info) return error.SkipZigTest; - - if (native_os == .wasi) return error.SkipZigTest; - - if (native_os == .windows) { - // https://github.com/ziglang/zig/issues/13963 - return error.SkipZigTest; - } - - // self-hosted debug info is still too buggy - if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest; - - const writer = std.io.null_writer; - var di = try SelfInfo.open(testing.allocator); - defer di.deinit(); - try printSourceAtAddress(&di, writer, showMyTrace(), io.tty.detectConfig(std.io.getStdErr())); -} - -noinline fn showMyTrace() usize { - return @returnAddress(); -} - -/// This API helps you track where a value originated and where it was mutated, -/// or any other points of interest. -/// In debug mode, it adds a small size penalty (104 bytes on 64-bit architectures) -/// to the aggregate that you add it to. -/// In release mode, it is size 0 and all methods are no-ops. -/// This is a pre-made type with default settings. -/// For more advanced usage, see `ConfigurableTrace`. -pub const Trace = ConfigurableTrace(2, 4, builtin.mode == .Debug); - -pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize, comptime is_enabled: bool) type { - return struct { - addrs: [actual_size][stack_frame_count]usize, - notes: [actual_size][]const u8, - index: Index, - - const actual_size = if (enabled) size else 0; - const Index = if (enabled) usize else u0; - - pub const init: @This() = .{ - .addrs = undefined, - .notes = undefined, - .index = 0, - }; - - pub const enabled = is_enabled; - - pub const add = if (enabled) addNoInline else addNoOp; - - pub noinline fn addNoInline(t: *@This(), note: []const u8) void { - comptime assert(enabled); - return addAddr(t, @returnAddress(), note); - } - - pub inline fn addNoOp(t: *@This(), note: []const u8) void { - _ = t; - _ = note; - comptime assert(!enabled); - } - - pub fn addAddr(t: *@This(), addr: usize, note: []const u8) void { - if (!enabled) return; - - if (t.index < size) { - t.notes[t.index] = note; - t.addrs[t.index] = [1]usize{0} ** stack_frame_count; - var stack_trace: std.builtin.StackTrace = .{ - .index = 0, - .instruction_addresses = &t.addrs[t.index], - }; - captureStackTrace(addr, &stack_trace); - } - // Keep counting even if the end is reached so that the - // user can find out how much more size they need. - t.index += 1; - } - - pub fn dump(t: @This()) void { - if (!enabled) return; - - const tty_config = io.tty.detectConfig(std.io.getStdErr()); - const stderr = io.getStdErr().writer(); - const end = @min(t.index, size); - const debug_info = getSelfDebugInfo() catch |err| { - stderr.print( - "Unable to dump stack trace: Unable to open debug info: {s}\n", - .{@errorName(err)}, - ) catch return; - return; - }; - for (t.addrs[0..end], 0..) |frames_array, i| { - stderr.print("{s}:\n", .{t.notes[i]}) catch return; - var frames_array_mutable = frames_array; - const frames = mem.sliceTo(frames_array_mutable[0..], 0); - const stack_trace: std.builtin.StackTrace = .{ - .index = frames.len, - .instruction_addresses = frames, - }; - writeStackTrace(stack_trace, stderr, debug_info, tty_config) catch continue; - } - if (t.index > end) { - stderr.print("{d} more traces not shown; consider increasing trace size\n", .{ - t.index - end, - }) catch return; - } - } - - pub fn format( - t: @This(), - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - if (fmt.len != 0) std.fmt.invalidFmtError(fmt, t); - _ = options; - if (enabled) { - try writer.writeAll("\n"); - t.dump(); - try writer.writeAll("\n"); - } else { - return writer.writeAll("(value tracing disabled)"); - } - } - }; -} - -pub const SafetyLock = struct { - state: State = if (runtime_safety) .unlocked else .unknown, - - pub const State = if (runtime_safety) enum { unlocked, locked } else enum { unknown }; - - pub fn lock(l: *SafetyLock) void { - if (!runtime_safety) return; - assert(l.state == .unlocked); - l.state = .locked; - } - - pub fn unlock(l: *SafetyLock) void { - if (!runtime_safety) return; - assert(l.state == .locked); - l.state = .unlocked; - } - - pub fn assertUnlocked(l: SafetyLock) void { - if (!runtime_safety) return; - assert(l.state == .unlocked); - } - - pub fn assertLocked(l: SafetyLock) void { - if (!runtime_safety) return; - assert(l.state == .locked); - } -}; - -test SafetyLock { - var safety_lock: SafetyLock = .{}; - safety_lock.assertUnlocked(); - safety_lock.lock(); - safety_lock.assertLocked(); - safety_lock.unlock(); - safety_lock.assertUnlocked(); -} - -/// Detect whether the program is being executed in the Valgrind virtual machine. -/// -/// When Valgrind integrations are disabled, this returns comptime-known false. -/// Otherwise, the result is runtime-known. -pub inline fn inValgrind() bool { - if (@inComptime()) return false; - if (!builtin.valgrind_support) return false; - return std.valgrind.runningOnValgrind() > 0; -} - -test { - _ = &Dwarf; - _ = &MemoryAccessor; - _ = &FixedBufferReader; - _ = &Pdb; - _ = &SelfInfo; - _ = &dumpHex; -} diff --git a/src/renderer/vaxis/style.zig b/src/renderer/vaxis/style.zig index 589fa44..4420bd5 100644 --- a/src/renderer/vaxis/style.zig +++ b/src/renderer/vaxis/style.zig @@ -4,11 +4,11 @@ pub const StyleBits = packed struct(u5) { undercurl: bool = false, underline: bool = false, italic: bool = false, -}; -pub const struck: StyleBits = .{ .struck = true }; -pub const bold: StyleBits = .{ .bold = true }; -pub const undercurl: StyleBits = .{ .undercurl = true }; -pub const underline: StyleBits = .{ .underline = true }; -pub const italic: StyleBits = .{ .italic = true }; -pub const normal: StyleBits = .{}; + pub const struck: StyleBits = .{ .struck = true }; + pub const bold: StyleBits = .{ .bold = true }; + pub const undercurl: StyleBits = .{ .undercurl = true }; + pub const underline: StyleBits = .{ .underline = true }; + pub const italic: StyleBits = .{ .italic = true }; + pub const normal: StyleBits = .{}; +}; diff --git a/src/renderer/win32/renderer.zig b/src/renderer/win32/renderer.zig index 293ce68..6c63388 100644 --- a/src/renderer/win32/renderer.zig +++ b/src/renderer/win32/renderer.zig @@ -18,55 +18,8 @@ pub const StyleBits = @import("tuirenderer").style; const gui = @import("gui"); const DropWriter = gui.DropWriter; pub const style = StyleBits; -pub const styles = @import("tuirenderer").styles; -pub const Error = error{ - UnexpectedRendererEvent, - OutOfMemory, - IntegerTooLarge, - IntegerTooSmall, - InvalidType, - TooShort, - Utf8CannotEncodeSurrogateHalf, - CodepointTooLarge, - VaxisResizeError, - InvalidFloatType, - InvalidArrayType, - InvalidPIntType, - JsonIncompatibleType, - NotAnObject, -} || std.Thread.SpawnError; - -pub const panic = messageBoxThenPanic(.{ .title = "Flow Panic" }); - -threadlocal var thread_is_panicing = false; -fn messageBoxThenPanic( - opt: struct { - title: [:0]const u8, - style: win32.MESSAGEBOX_STYLE = .{ .ICONASTERISK = 1 }, - // TODO: add option/logic to include the stacktrace in the messagebox - }, -) std.builtin.PanicFn { - return struct { - pub fn panic( - msg: []const u8, - _: ?*std.builtin.StackTrace, - ret_addr: ?usize, - ) noreturn { - if (!thread_is_panicing) { - thread_is_panicing = true; - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - const msg_z: [:0]const u8 = if (std.fmt.allocPrintZ( - arena.allocator(), - "{s}", - .{msg}, - )) |msg_z| msg_z else |_| "failed allocate error message"; - _ = win32.MessageBoxA(null, msg_z, opt.title, opt.style); - } - std.debug.defaultPanic(msg, ret_addr); - } - }.panic; -} +pub const panic = win32.messageBoxThenPanic(.{ .title = "Flow Panic" }); allocator: std.mem.Allocator, vx: vaxis.Vaxis, @@ -82,7 +35,7 @@ thread: ?std.Thread = null, hwnd: ?win32.HWND = null, title_buf: std.ArrayList(u16), -style_: ?Style = null, +style: ?Style = null, const global = struct { var init_called: bool = false; @@ -97,7 +50,7 @@ pub fn init( handler_ctx: *anyopaque, no_alternate: bool, dispatch_initialized: *const fn (ctx: *anyopaque) void, -) Error!Self { +) !Self { std.debug.assert(!global.init_called); global.init_called = true; @@ -133,16 +86,16 @@ pub fn deinit(self: *Self) void { self.title_buf.deinit(); } -pub fn run(self: *Self) Error!void { +pub fn run(self: *Self) !void { if (self.thread) |_| return; // dummy resize to fully init vaxis const drop_writer = DropWriter{}; - self.vx.resize( + try self.vx.resize( self.allocator, drop_writer.writer().any(), .{ .rows = 25, .cols = 80, .x_pixel = 0, .y_pixel = 0 }, - ) catch return error.VaxisResizeError; + ); self.thread = try gui.start(); } @@ -181,7 +134,7 @@ pub fn stdplane(self: *Self) Plane { return plane; } -pub fn process_renderer_event(self: *Self, msg: []const u8) Error!void { +pub fn process_renderer_event(self: *Self, msg: []const u8) !void { const Input = struct { kind: u8, codepoint: u21, @@ -391,12 +344,12 @@ fn update_window_title(self: *Self) void { } pub fn set_terminal_style(self: *Self, style_: Style) void { - self.style_ = style_; + self.style = style_; self.update_window_style(); } fn update_window_style(self: *Self) void { const hwnd = self.hwnd orelse return; - if (self.style_) |style_| { + if (self.style) |style_| { if (style_.bg) |color| gui.set_window_background(hwnd, @intCast(color.color)); } } diff --git a/src/ripgrep.zig b/src/ripgrep.zig index c6c14a3..3b86f2a 100644 --- a/src/ripgrep.zig +++ b/src/ripgrep.zig @@ -2,7 +2,8 @@ const std = @import("std"); const tp = @import("thespian"); const cbor = @import("cbor"); const log = @import("log"); -const bin_path = @import("bin_path"); + +pub const ripgrep_binary = "rg"; pid: ?tp.pid, stdin_behavior: std.process.Child.StdIo, @@ -120,13 +121,12 @@ const Process = struct { errdefer self.deinit(); _ = tp.set_trap(true); const args = tp.message.fmt(.{ - get_ripgrep(), + ripgrep_binary, // "--line-buffered", "--fixed-strings", "--json", "--smart-case", self.query, - "./.", // never search stdin }); self.sp = tp.subprocess.init(self.allocator, args, module_name, self.stdin_behavior) catch |e| return tp.exit_error(e, @errorReturnTrace()); tp.receive(&self.receiver); @@ -134,7 +134,7 @@ const Process = struct { fn receive(self: *Process, _: tp.pid_ref, m: tp.message) tp.result { errdefer self.deinit(); - var bytes: []const u8 = ""; + var bytes: []u8 = ""; if (try m.match(.{ "input", tp.extract(&bytes) })) { const sp = self.sp orelse return tp.exit_error(error.Closed, null); @@ -155,7 +155,7 @@ const Process = struct { } } - fn handle_output(self: *Process, bytes: []const u8) !void { + fn handle_output(self: *Process, bytes: []u8) !void { try self.output.appendSlice(bytes); } @@ -182,9 +182,9 @@ const Process = struct { } else if (try m.match(.{ tp.any, tp.any, "exited", 1 })) { self.logger.print("no matches found", .{}); } else if (try m.match(.{ tp.any, tp.any, "error.FileNotFound", 1 })) { - self.logger.print_err(get_ripgrep(), "'{s}' executable not found", .{get_ripgrep()}); + self.logger.print_err(ripgrep_binary, "'{s}' executable not found", .{ripgrep_binary}); } else if (try m.match(.{ tp.any, tp.any, tp.extract(&err_msg), tp.extract(&exit_code) })) { - self.logger.print_err(get_ripgrep(), "terminated {s} exitcode: {d}", .{ err_msg, exit_code }); + self.logger.print_err(ripgrep_binary, "terminated {s} exitcode: {d}", .{ err_msg, exit_code }); } } } @@ -244,9 +244,7 @@ const Process = struct { .integer => |i| i, else => return, } else return; - if (path) |p_| { - const prefix = "././"; - const p = if (p_.len >= prefix.len and std.mem.eql(u8, p_[0..prefix.len], prefix)) p_[prefix.len..] else p_; + if (path) |p| { const match_text = if (lines) |l| if (l[l.len - 1] == '\n') l[0 .. l.len - 1] else l else @@ -258,18 +256,3 @@ const Process = struct { self.match_count += 1; } }; - -const rg_binary = "rg"; -const default_rg_binary = "/bin/" ++ rg_binary; - -var rg_path: ?struct { - path: ?[:0]const u8 = null, -} = null; - -pub fn get_ripgrep() []const u8 { - const allocator = std.heap.c_allocator; - if (rg_path) |p| return p.path orelse default_rg_binary; - const path = bin_path.find_binary_in_path(allocator, rg_binary) catch default_rg_binary; - rg_path = .{ .path = path }; - return path orelse default_rg_binary; -} diff --git a/src/shell.zig b/src/shell.zig index 69746cb..04f6217 100644 --- a/src/shell.zig +++ b/src/shell.zig @@ -21,11 +21,6 @@ pub const Error = error{ IntegerTooSmall, InvalidType, TooShort, - InvalidFloatType, - InvalidArrayType, - InvalidPIntType, - JsonIncompatibleType, - NotAnObject, }; pub const OutputHandler = fn (context: usize, parent: tp.pid_ref, arg0: []const u8, output: []const u8) void; @@ -36,7 +31,6 @@ pub const Handlers = struct { out: *const OutputHandler, err: ?*const OutputHandler = null, exit: *const ExitHandler = log_exit_handler, - log_execute: bool = true, }; pub fn execute(allocator: std.mem.Allocator, argv: tp.message, handlers: Handlers) Error!void { @@ -96,7 +90,6 @@ pub fn log_handler(context: usize, parent: tp.pid_ref, arg0: []const u8, output: _ = parent; _ = arg0; const logger = log.logger(@typeName(Self)); - defer logger.deinit(); var it = std.mem.splitScalar(u8, output, '\n'); while (it.next()) |line| if (line.len > 0) logger.print("{s}", .{line}); } @@ -105,7 +98,6 @@ pub fn log_err_handler(context: usize, parent: tp.pid_ref, arg0: []const u8, out _ = context; _ = parent; const logger = log.logger(@typeName(Self)); - defer logger.deinit(); var it = std.mem.splitScalar(u8, output, '\n'); while (it.next()) |line| logger.print_err(arg0, "{s}", .{line}); } @@ -114,7 +106,6 @@ pub fn log_exit_handler(context: usize, parent: tp.pid_ref, arg0: []const u8, er _ = context; _ = parent; const logger = log.logger(@typeName(Self)); - defer logger.deinit(); if (exit_code > 0) { logger.print_err(arg0, "'{s}' terminated {s} exitcode: {d}", .{ arg0, err_msg, exit_code }); } else { @@ -126,7 +117,6 @@ pub fn log_exit_err_handler(context: usize, parent: tp.pid_ref, arg0: []const u8 _ = context; _ = parent; const logger = log.logger(@typeName(Self)); - defer logger.deinit(); if (exit_code > 0) { logger.print_err(arg0, "'{s}' terminated {s} exitcode: {d}", .{ arg0, err_msg, exit_code }); } @@ -169,25 +159,20 @@ const Process = struct { } fn deinit(self: *Process) void { - if (self.sp) |*sp| { - defer self.sp = null; - sp.deinit(); - } + if (self.sp) |*sp| sp.deinit(); self.parent.deinit(); self.logger.deinit(); self.allocator.free(self.arg0); self.allocator.free(self.argv.buf); + self.close() catch {}; self.allocator.destroy(self); } - fn close(self: *Process) void { - defer self.sp = null; - if (self.sp) |*sp| sp.close() catch {}; - } - - fn term(self: *Process) void { - defer self.sp = null; - if (self.sp) |*sp| sp.term() catch {}; + fn close(self: *Process) tp.result { + if (self.sp) |*sp| { + defer self.sp = null; + try sp.close(); + } } fn start(self: *Process) tp.result { @@ -195,31 +180,27 @@ const Process = struct { _ = tp.set_trap(true); var buf: [1024]u8 = undefined; const json = self.argv.to_json(&buf) catch |e| return tp.exit_error(e, @errorReturnTrace()); - if (self.handlers.log_execute) - self.logger.print("shell: execute {s}", .{json}); + self.logger.print("shell: execute {s}", .{json}); self.sp = tp.subprocess.init(self.allocator, self.argv, module_name, self.stdin_behavior) catch |e| return tp.exit_error(e, @errorReturnTrace()); tp.receive(&self.receiver); } fn receive(self: *Process, _: tp.pid_ref, m: tp.message) tp.result { errdefer self.deinit(); - var bytes: []const u8 = ""; + var bytes: []u8 = ""; if (try m.match(.{ "input", tp.extract(&bytes) })) { const sp = self.sp orelse return tp.exit_error(error.Closed, null); try sp.send(bytes); } else if (try m.match(.{"close"})) { - self.close(); + try self.close(); } else if (try m.match(.{ module_name, "stdout", tp.extract(&bytes) })) { self.handlers.out(self.handlers.context, self.parent.ref(), self.arg0, bytes); } else if (try m.match(.{ module_name, "stderr", tp.extract(&bytes) })) { (self.handlers.err orelse self.handlers.out)(self.handlers.context, self.parent.ref(), self.arg0, bytes); } else if (try m.match(.{ module_name, "term", tp.more })) { - defer self.sp = null; self.handle_terminated(m) catch |e| return tp.exit_error(e, @errorReturnTrace()); - return tp.exit_normal(); } else if (try m.match(.{ "exit", "normal" })) { - self.term(); return tp.exit_normal(); } else { self.logger.err("receive", tp.unexpected(m)); diff --git a/src/syntax/build.zig b/src/syntax/build.zig index 2b3a149..c74f15f 100644 --- a/src/syntax/build.zig +++ b/src/syntax/build.zig @@ -9,146 +9,119 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const tree_sitter_dep = b.dependency("tree_sitter", .{ + const tree_sitter_dep = b.dependency("tree-sitter", .{ .target = target, .optimize = optimize, }); - const tree_sitter_host_dep = b.dependency("tree_sitter", .{ - .target = b.graph.host, - .optimize = optimize, - }); + const imports: []const std.Build.Module.Import = if (use_tree_sitter) &.{ + .{ .name = "build_options", .module = options_mod }, + .{ .name = "treez", .module = tree_sitter_dep.module("treez") }, + ts_queryfile(b, tree_sitter_dep, "queries/cmake/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-agda/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-astro/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-bash/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-c-sharp/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-c/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-cpp/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-css/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-diff/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-dockerfile/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-elixir/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-git-rebase/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-gitcommit/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-gleam/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-go/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-fish/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-haskell/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-hare/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-html/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-java/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-javascript/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-jsdoc/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-json/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-julia/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-kdl/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-lua/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-mail/queries/mail/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-make/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-markdown/tree-sitter-markdown/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-markdown/tree-sitter-markdown-inline/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-nasm/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-nim/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-ninja/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-nix/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-nu/queries/nu/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-ocaml/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-odin/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-openscad/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-org/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-php/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-python/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-purescript/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-regex/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-ruby/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-rust/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-ssh-config/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-scala/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-scheme/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-superhtml/tree-sitter-superhtml/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-sql/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-swift/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-toml/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-typescript/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-typst/queries/typst/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-vim/queries/vim/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-xml/queries/dtd/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-xml/queries/xml/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-yaml/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-zig/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-ziggy/tree-sitter-ziggy/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-ziggy/tree-sitter-ziggy-schema/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "nvim-treesitter/queries/verilog/highlights.scm"), - const cbor_dep = b.dependency("cbor", .{ - .target = target, - .optimize = optimize, - }); + ts_queryfile(b, tree_sitter_dep, "queries/cmake/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-astro/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-cpp/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-elixir/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-gitcommit/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-hare/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-html/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-javascript/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-kdl/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-lua/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-markdown/tree-sitter-markdown-inline/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-markdown/tree-sitter-markdown/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-nasm/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-nix/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-nu/queries/nu/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-odin/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-openscad/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-php/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-purescript/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-purescript/vim_queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-rust/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-superhtml/tree-sitter-superhtml/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-swift/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-typst/queries/typst/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-vim/queries/vim/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-zig/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "nvim-treesitter/queries/verilog/injections.scm"), + } else &.{ + .{ .name = "build_options", .module = options_mod }, + }; - const ts_bin_query_gen = b.addExecutable(.{ - .name = "ts_bin_query_gen", - .target = b.graph.host, - .root_source_file = b.path("src/ts_bin_query_gen.zig"), - }); - ts_bin_query_gen.linkLibC(); - ts_bin_query_gen.root_module.addImport("cbor", cbor_dep.module("cbor")); - ts_bin_query_gen.root_module.addImport("treez", tree_sitter_host_dep.module("treez")); - ts_bin_query_gen.root_module.addImport("build_options", options_mod); - - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "queries/cmake/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-agda/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-astro/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-bash/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-c-sharp/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-c/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-cpp/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-css/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-diff/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-dockerfile/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-elixir/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-git-rebase/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-gitcommit/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-gleam/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-go/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-fish/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-haskell/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-hare/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-html/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-hurl/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-java/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-javascript/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-jsdoc/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-json/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-julia/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-kdl/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-lua/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-mail/queries/mail/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-make/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-markdown/tree-sitter-markdown/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-markdown/tree-sitter-markdown-inline/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-nasm/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-nim/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-ninja/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-nix/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-nu/queries/nu/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-ocaml/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-odin/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-openscad/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-org/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-php/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-powershell/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-proto/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-python/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-purescript/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-regex/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-rpmspec/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-ruby/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-rust/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-ssh-config/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-scala/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-scheme/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-superhtml/tree-sitter-superhtml/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-sql/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-swift/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-toml/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-typescript/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-typst/queries/typst/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-uxntal/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-vim/queries/vim/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-xml/queries/dtd/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-xml/queries/xml/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-yaml/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-zig/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-ziggy/tree-sitter-ziggy/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-ziggy/tree-sitter-ziggy-schema/queries/highlights.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "nvim-treesitter/queries/verilog/highlights.scm"); - - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "queries/cmake/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-astro/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-cpp/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-elixir/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-gitcommit/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-hare/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-html/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-hurl/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-javascript/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-kdl/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-lua/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-markdown/tree-sitter-markdown-inline/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-markdown/tree-sitter-markdown/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-nasm/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-nix/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-nu/queries/nu/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-odin/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-openscad/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-php/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-purescript/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-purescript/vim_queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-rust/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-superhtml/tree-sitter-superhtml/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-swift/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-typst/queries/typst/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-uxntal/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-vim/queries/vim/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-zig/queries/injections.scm"); - ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "nvim-treesitter/queries/verilog/injections.scm"); - - const syntax_mod = b.addModule("syntax", .{ + _ = b.addModule("syntax", .{ .root_source_file = b.path("src/syntax.zig"), - .imports = &.{ - .{ .name = "build_options", .module = options_mod }, - .{ .name = "cbor", .module = cbor_dep.module("cbor") }, - .{ .name = "treez", .module = tree_sitter_dep.module("treez") }, - }, + .imports = imports, }); - - if (use_tree_sitter) { - const ts_bin_query_gen_step = b.addRunArtifact(ts_bin_query_gen); - const output = ts_bin_query_gen_step.addOutputFileArg("bin_queries.cbor"); - syntax_mod.addAnonymousImport("syntax_bin_queries", .{ .root_source_file = output }); - } } -fn ts_queryfile(b: *std.Build, dep: *std.Build.Dependency, bin_gen: *std.Build.Step.Compile, comptime sub_path: []const u8) void { - const module = b.createModule(.{ .root_source_file = dep.path(sub_path) }); - bin_gen.root_module.addImport(sub_path, module); +fn ts_queryfile(b: *std.Build, dep: *std.Build.Dependency, comptime sub_path: []const u8) std.Build.Module.Import { + return .{ + .name = sub_path, + .module = b.createModule(.{ + .root_source_file = dep.path(sub_path), + }), + }; } diff --git a/src/syntax/build.zig.zon b/src/syntax/build.zig.zon index 70ca5d8..0f161c8 100644 --- a/src/syntax/build.zig.zon +++ b/src/syntax/build.zig.zon @@ -1,17 +1,11 @@ .{ - .name = .flow_syntax, - .version = "0.1.0", - .fingerprint = 0x3ba2584ea1cec85f, - .minimum_zig_version = "0.14.1", + .name = "flow-syntax", + .version = "0.0.1", .dependencies = .{ - .tree_sitter = .{ - .url = "https://github.com/neurocyte/tree-sitter/releases/download/master-1c3ad59bd98ee430b166054030dac4c46d641e39/source.tar.gz", - .hash = "N-V-__8AANMzUiemOR2eNnrtlMmAGHFqij6VYtDUiaFfn6Dw", - }, - .cbor = .{ - .url = "https://github.com/neurocyte/cbor/archive/1fccb83c70cd84e1dff57cc53f7db8fb99909a94.tar.gz", - .hash = "cbor-1.0.0-RcQE_HvqAACcrLH7t3IDZOshgY2xqJA_UX330MvwSepb", + .@"tree-sitter" = .{ + .url = "https://github.com/neurocyte/tree-sitter/releases/download/master-86dd4d2536f2748c5b4ea0e1e70678039a569aac/source.tar.gz", + .hash = "1220e9fba96c468283129e977767472dee00b16f356e5912431cec8f1a009b6691a2", }, }, .paths = .{ diff --git a/src/syntax/src/QueryCache.zig b/src/syntax/src/QueryCache.zig deleted file mode 100644 index a9dad05..0000000 --- a/src/syntax/src/QueryCache.zig +++ /dev/null @@ -1,192 +0,0 @@ -const std = @import("std"); -const build_options = @import("build_options"); - -const treez = if (build_options.use_tree_sitter) - @import("treez") -else - @import("treez_dummy.zig"); - -const Self = @This(); - -pub const tss = @import("ts_serializer.zig"); -pub const FileType = @import("file_type.zig"); -const Query = treez.Query; - -allocator: std.mem.Allocator, -mutex: ?std.Thread.Mutex, -highlights: std.StringHashMapUnmanaged(*CacheEntry) = .{}, -injections: std.StringHashMapUnmanaged(*CacheEntry) = .{}, -errors: std.StringHashMapUnmanaged(*CacheEntry) = .{}, -ref_count: usize = 1, - -const CacheEntry = struct { - mutex: ?std.Thread.Mutex, - query: ?*Query, - query_arena: ?*std.heap.ArenaAllocator, - query_type: QueryType, - file_type: *const FileType, - - fn destroy(self: *@This(), allocator: std.mem.Allocator) void { - if (self.query_arena) |a| { - a.deinit(); - allocator.destroy(a); - } else if (self.query) |q| - q.destroy(); - self.query_arena = null; - self.query = null; - } -}; - -pub const QueryType = enum { - highlights, - errors, - injections, -}; - -const QueryParseError = error{ - InvalidSyntax, - InvalidNodeType, - InvalidField, - InvalidCapture, - InvalidStructure, - InvalidLanguage, -}; - -const CacheError = error{ - NotFound, - OutOfMemory, -}; - -pub const Error = CacheError || QueryParseError || QuerySerializeError; - -pub fn create(allocator: std.mem.Allocator, opts: struct { lock: bool = false }) !*Self { - const self = try allocator.create(Self); - self.* = .{ - .allocator = allocator, - .mutex = if (opts.lock) .{} else null, - }; - return self; -} - -pub fn deinit(self: *Self) void { - self.release_ref_unlocked_and_maybe_destroy(); -} - -fn add_ref_locked(self: *Self) void { - std.debug.assert(self.ref_count > 0); - self.ref_count += 1; -} - -fn release_ref_unlocked_and_maybe_destroy(self: *Self) void { - { - if (self.mutex) |*mtx| mtx.lock(); - defer if (self.mutex) |*mtx| mtx.unlock(); - self.ref_count -= 1; - if (self.ref_count > 0) return; - } - - release_cache_entry_hash_map(self.allocator, &self.highlights); - release_cache_entry_hash_map(self.allocator, &self.errors); - release_cache_entry_hash_map(self.allocator, &self.injections); - self.allocator.destroy(self); -} - -fn release_cache_entry_hash_map(allocator: std.mem.Allocator, hash_map: *std.StringHashMapUnmanaged(*CacheEntry)) void { - var iter = hash_map.iterator(); - while (iter.next()) |p| { - allocator.free(p.key_ptr.*); - p.value_ptr.*.destroy(allocator); - allocator.destroy(p.value_ptr.*); - } - hash_map.deinit(allocator); -} - -fn get_cache_entry(self: *Self, file_type: *const FileType, comptime query_type: QueryType) CacheError!*CacheEntry { - if (self.mutex) |*mtx| mtx.lock(); - defer if (self.mutex) |*mtx| mtx.unlock(); - - const hash = switch (query_type) { - .highlights => &self.highlights, - .errors => &self.errors, - .injections => &self.injections, - }; - - return if (hash.get(file_type.name)) |entry| entry else blk: { - const entry_ = try hash.getOrPut(self.allocator, try self.allocator.dupe(u8, file_type.name)); - - const q = try self.allocator.create(CacheEntry); - q.* = .{ - .query = null, - .query_arena = null, - .mutex = if (self.mutex) |_| .{} else null, - .file_type = file_type, - .query_type = query_type, - }; - entry_.value_ptr.* = q; - - break :blk q; - }; -} - -fn get_cached_query(self: *Self, entry: *CacheEntry) Error!?*Query { - if (entry.mutex) |*mtx| mtx.lock(); - defer if (entry.mutex) |*mtx| mtx.unlock(); - - return if (entry.query) |query| query else blk: { - const lang = entry.file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{entry.file_type.name}); - const queries = FileType.queries.get(entry.file_type.name) orelse return null; - const query_bin = switch (entry.query_type) { - .highlights => queries.highlights_bin, - .errors => queries.errors_bin, - .injections => queries.injections_bin orelse return null, - }; - const query, const arena = try deserialize_query(query_bin, lang, self.allocator); - entry.query = query; - entry.query_arena = arena; - break :blk entry.query.?; - }; -} - -fn pre_load_internal(self: *Self, file_type: *const FileType, comptime query_type: QueryType) Error!void { - _ = try self.get_cached_query(try self.get_cache_entry(file_type, query_type)); -} - -pub fn pre_load(self: *Self, lang_name: []const u8) Error!void { - const file_type = FileType.get_by_name(lang_name) orelse return; - _ = try self.pre_load_internal(file_type, .highlights); - _ = try self.pre_load_internal(file_type, .errors); - _ = try self.pre_load_internal(file_type, .injections); -} - -fn ReturnType(comptime query_type: QueryType) type { - return switch (query_type) { - .highlights => *Query, - .errors => *Query, - .injections => ?*Query, - }; -} - -pub fn get(self: *Self, file_type: *const FileType, comptime query_type: QueryType) Error!ReturnType(query_type) { - const query = try self.get_cached_query(try self.get_cache_entry(file_type, query_type)); - self.add_ref_locked(); - return switch (@typeInfo(ReturnType(query_type))) { - .optional => |_| query, - else => query.?, - }; -} - -pub fn release(self: *Self, query: *Query, comptime query_type: QueryType) void { - _ = query; - _ = query_type; - self.release_ref_unlocked_and_maybe_destroy(); -} - -pub const QuerySerializeError = (tss.SerializeError || tss.DeserializeError); - -fn deserialize_query(query_bin: []const u8, language: ?*const treez.Language, allocator: std.mem.Allocator) QuerySerializeError!struct { *Query, *std.heap.ArenaAllocator } { - var ts_query_out, const arena = try tss.fromCbor(query_bin, allocator); - ts_query_out.language = @intFromPtr(language); - - const query_out: *Query = @alignCast(@ptrCast(ts_query_out)); - return .{ query_out, arena }; -} diff --git a/src/syntax/src/file_type.zig b/src/syntax/src/file_type.zig index 4c45c64..dfecb1b 100644 --- a/src/syntax/src/file_type.zig +++ b/src/syntax/src/file_type.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const cbor = @import("cbor"); const build_options = @import("build_options"); const treez = if (build_options.use_tree_sitter) @@ -15,6 +14,8 @@ name: []const u8, description: []const u8, lang_fn: LangFn, extensions: []const []const u8, +highlights: [:0]const u8, +injections: ?[:0]const u8, first_line_matches: ?FirstLineMatch = null, comment: []const u8, formatter: ?[]const []const u8, @@ -87,7 +88,7 @@ fn ft_func_name(comptime lang: []const u8) []const u8 { const LangFn = *const fn () callconv(.C) ?*const treez.Language; -pub const FirstLineMatch = struct { +const FirstLineMatch = struct { prefix: ?[]const u8 = null, content: ?[]const u8 = null, }; @@ -104,7 +105,7 @@ fn vec(comptime args: anytype) []const []const u8 { fn load_file_types(comptime Namespace: type) []const FileType { comptime switch (@typeInfo(Namespace)) { - .@"struct" => |info| { + .Struct => |info| { var count = 0; for (info.decls) |_| { // @compileLog(decl.name, @TypeOf(@field(Namespace, decl.name))); @@ -123,6 +124,22 @@ fn load_file_types(comptime Namespace: type) []const FileType { .lang_fn = if (@hasField(@TypeOf(args), "parser")) args.parser else get_parser(lang), .extensions = vec(args.extensions), .comment = args.comment, + .highlights = if (build_options.use_tree_sitter) + if (@hasField(@TypeOf(args), "highlights")) + @embedFile(args.highlights) + else if (@hasField(@TypeOf(args), "highlights_list")) + @embedFile(args.highlights_list[0]) ++ "\n" ++ @embedFile(args.highlights_list[1]) + else + @embedFile("tree-sitter-" ++ lang ++ "/queries/highlights.scm") + else + "", + .injections = if (build_options.use_tree_sitter) + if (@hasField(@TypeOf(args), "injections")) + @embedFile(args.injections) + else + null + else + null, .first_line_matches = if (@hasField(@TypeOf(args), "first_line_matches")) args.first_line_matches else null, .formatter = if (@hasField(@TypeOf(args), "formatter")) vec(args.formatter) else null, .language_server = if (@hasField(@TypeOf(args), "language_server")) vec(args.language_server) else null, @@ -135,68 +152,3 @@ fn load_file_types(comptime Namespace: type) []const FileType { else => @compileError("expected tuple or struct type"), }; } - -pub const FileTypeQueries = struct { - highlights_bin: []const u8, - errors_bin: []const u8, - injections_bin: ?[]const u8, -}; - -pub const queries = std.static_string_map.StaticStringMap(FileTypeQueries).initComptime(load_queries()); - -fn load_queries() []const struct { []const u8, FileTypeQueries } { - if (!build_options.use_tree_sitter) return &.{}; - @setEvalBranchQuota(32000); - const queries_cb = @embedFile("syntax_bin_queries"); - var iter: []const u8 = queries_cb; - var len = cbor.decodeMapHeader(&iter) catch |e| { - @compileLog("cbor.decodeMapHeader", e); - @compileError("invalid syntax_bin_queries"); - }; - var construct_types: [len]struct { []const u8, FileTypeQueries } = undefined; - var i = 0; - while (len > 0) : (len -= 1) { - var lang: []const u8 = undefined; - if (!try cbor.matchString(&iter, &lang)) - @compileError("invalid language name field"); - construct_types[i] = .{ lang, .{ - .highlights_bin = blk: { - var iter_: []const u8 = iter; - break :blk get_query_value_bin(&iter_, "highlights") orelse @compileError("missing highlights for " ++ lang); - }, - .errors_bin = blk: { - var iter_: []const u8 = iter; - break :blk get_query_value_bin(&iter_, "errors") orelse @compileError("missing errors query for " ++ lang); - }, - .injections_bin = blk: { - var iter_: []const u8 = iter; - break :blk get_query_value_bin(&iter_, "injections"); - }, - } }; - try cbor.skipValue(&iter); - i += 1; - } - const types = construct_types; - return &types; -} - -fn get_query_value_bin(iter: *[]const u8, comptime query: []const u8) ?[]const u8 { - var len = cbor.decodeMapHeader(iter) catch |e| { - @compileLog("cbor.decodeMapHeader", e); - @compileError("invalid query map in syntax_bin_queries"); - }; - while (len > 0) : (len -= 1) { - var query_name: []const u8 = undefined; - if (!try cbor.matchString(iter, &query_name)) - @compileError("invalid query name field"); - if (std.mem.eql(u8, query_name, query)) { - var query_value: []const u8 = undefined; - if (try cbor.matchValue(iter, cbor.extract(&query_value))) - return query_value; - @compileError("invalid query value field"); - } else { - try cbor.skipValue(iter); - } - } - return null; -} diff --git a/src/syntax/src/file_types.zig b/src/syntax/src/file_types.zig index e19b5e5..b33100a 100644 --- a/src/syntax/src/file_types.zig +++ b/src/syntax/src/file_types.zig @@ -1,6 +1,3 @@ -const file_type = @import("file_type.zig"); -const FirstLineMatch = file_type.FirstLineMatch; - pub const agda = .{ .description = "Agda", .extensions = .{"agda"}, @@ -21,7 +18,7 @@ pub const bash = .{ .icon = "󱆃", .extensions = .{ "sh", "bash", ".profile" }, .comment = "#", - .first_line_matches = FirstLineMatch{ .prefix = "#!", .content = "sh" }, + .first_line_matches = .{ .prefix = "#!", .content = "sh" }, .formatter = .{ "shfmt", "--indent", "4" }, .language_server = .{ "bash-language-server", "start" }, }; @@ -42,14 +39,13 @@ pub const @"c-sharp" = .{ .extensions = .{"cs"}, .comment = "//", .language_server = .{ "omnisharp", "-lsp" }, - .formatter = .{ "csharpier", "format" }, }; pub const conf = .{ .description = "Config", .color = 0x000000, .icon = "", - .extensions = .{ "conf", "log", "config", ".gitconfig", "gui_config" }, + .extensions = .{ "conf", "config", ".gitconfig", "gui_config" }, .highlights = fish.highlights, .comment = "#", .parser = fish.parser, @@ -206,15 +202,6 @@ pub const superhtml = .{ .formatter = .{ "superhtml", "fmt", "--stdin-super" }, }; -pub const hurl = .{ - .description = "Hurl", - .color = 0xff0087, - .icon = "ï‚”", - .extensions = .{"hurl"}, - .comment = "#", - .injections = "tree-sitter-hurl/queries/injections.scm", -}; - pub const java = .{ .description = "Java", .color = 0xEA2D2E, @@ -266,7 +253,7 @@ pub const lua = .{ .extensions = .{"lua"}, .comment = "--", .injections = "tree-sitter-lua/queries/injections.scm", - .first_line_matches = FirstLineMatch{ .prefix = "--", .content = "lua" }, + .first_line_matches = .{ .prefix = "--", .content = "lua" }, .language_server = .{"lua-lsp"}, }; @@ -276,7 +263,7 @@ pub const mail = .{ .extensions = .{ "eml", "mbox" }, .comment = ">", .highlights = "tree-sitter-mail/queries/mail/highlights.scm", - .first_line_matches = FirstLineMatch{ .prefix = "From" }, + .first_line_matches = .{ .prefix = "From" }, }; pub const make = .{ @@ -405,20 +392,6 @@ pub const php = .{ .language_server = .{ "intelephense", "--stdio" }, }; -pub const powershell = .{ - .description = "PowerShell", - .color = 0x0873c5, - .icon = "", - .extensions = .{"ps1"}, - .comment = "#", -}; - -pub const proto = .{ - .description = "protobuf (proto)", - .extensions = .{"proto"}, - .comment = "//", -}; - pub const purescript = .{ .description = "PureScript", .color = 0x14161a, @@ -434,7 +407,7 @@ pub const python = .{ .icon = "󰌠", .extensions = .{ "py", "pyi" }, .comment = "#", - .first_line_matches = FirstLineMatch{ .prefix = "#!", .content = "python" }, + .first_line_matches = .{ .prefix = "#!", .content = "python" }, .language_server = .{"pylsp"}, }; @@ -444,14 +417,6 @@ pub const regex = .{ .comment = "#", }; -pub const rpmspec = .{ - .description = "RPM spec", - .color = 0xff0000, - .icon = "󱄛", - .extensions = .{"spec"}, - .comment = "#", -}; - pub const ruby = .{ .description = "Ruby", .color = 0xd91404, @@ -539,12 +504,6 @@ pub const typst = .{ .injections = "tree-sitter-typst/queries/typst/injections.scm", }; -pub const uxntal = .{ - .description = "Uxntal", - .extensions = .{"tal"}, - .comment = "(", -}; - pub const vim = .{ .description = "Vimscript", .color = 0x007f00, @@ -561,7 +520,7 @@ pub const xml = .{ .extensions = .{"xml"}, .comment = "