diff --git a/build.zig.zon b/build.zig.zon index 19890f5..eb235c9 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,7 +5,10 @@ .fingerprint = 0x52c0d670590aa80f, .dependencies = .{ - .syntax = .{ .path = "src/syntax" }, + .syntax = .{ + .url = "git+https://github.com/neurocyte/flow-syntax?ref=zig-0.14#410d19e633f237cd1602175450bd7d3bb03a1898", + .hash = "flow_syntax-0.1.0-X8jOoT4OAQDibKKzYlJls3u5KczVh__cWYN7vTqCE1o3", + }, .flags = .{ .url = "https://github.com/n0s4/flags/archive/372501d1576b5723829bcba98e41361132c7b618.tar.gz", .hash = "flags-0.8.0-AAAAAJV0AACuGBBnpUnHqZzAhoGTp4ibFROBQQQZGRqx", diff --git a/src/LSP.zig b/src/LSP.zig index 58012a3..185e95d 100644 --- a/src/LSP.zig +++ b/src/LSP.zig @@ -3,7 +3,6 @@ const tp = @import("thespian"); const cbor = @import("cbor"); const root = @import("root"); const tracy = @import("tracy"); -const log = @import("log"); allocator: std.mem.Allocator, pid: tp.pid, @@ -172,6 +171,7 @@ const Process = struct { project: [:0]const u8, sp_tag: [:0]const u8, log_file: ?std.fs.File = null, + log_file_path: ?[]const u8 = null, next_id: i32 = 0, requests: std.StringHashMap(tp.pid), state: enum { init, running } = .init, @@ -188,10 +188,8 @@ const Process = struct { } else if (try cbor.match(cmd.buf, .{ tp.extract(&tag), tp.more })) { // } else { - const logger = log.logger("LSP"); - defer logger.deinit(); var buf: [1024]u8 = undefined; - logger.print_err("create", "invalid command: {d} {s}", .{ cmd.buf.len, cmd.to_json(&buf) catch "{command too large}" }); + send_msg(tp.self_pid().clone(), tag, .err, "invalid command: {d} {s}", .{ cmd.buf.len, cmd.to_json(&buf) catch "{command too large}" }); return error.InvalidLspCommand; } const self = try allocator.create(Process); @@ -227,6 +225,7 @@ const Process = struct { self.close() catch {}; self.write_log("### terminated LSP process ###\n", .{}); if (self.log_file) |file| file.close(); + if (self.log_file_path) |file_path| self.allocator.free(file_path); } fn close(self: *Process) error{CloseFailed}!void { @@ -245,6 +244,20 @@ const Process = struct { } } + fn msg(self: *const Process, comptime fmt: anytype, args: anytype) void { + send_msg(self.parent, self.tag, .msg, fmt, args); + } + + fn err_msg(self: *const Process, comptime fmt: anytype, args: anytype) void { + send_msg(self.parent, self.tag, .err, fmt, args); + } + + fn send_msg(proc: tp.pid, tag: []const u8, type_: enum { msg, err }, comptime fmt: anytype, args: anytype) void { + var buf: [@import("log").max_log_message]u8 = undefined; + const output = std.fmt.bufPrint(&buf, fmt, args) catch "MESSAGE TOO LARGE"; + proc.send(.{ "lsp", type_, tag, output }) catch {}; + } + fn start(self: *Process) tp.result { const frame = tracy.initZone(@src(), .{ .name = module_name ++ " start" }); defer frame.deinit(); @@ -255,8 +268,9 @@ const Process = struct { var log_file_path = std.ArrayList(u8).init(self.allocator); defer log_file_path.deinit(); const state_dir = root.get_state_dir() catch |e| return tp.exit_error(e, @errorReturnTrace()); - log_file_path.writer().print("{s}/lsp-{s}.log", .{ state_dir, self.tag }) catch |e| return tp.exit_error(e, @errorReturnTrace()); + log_file_path.writer().print("{s}{c}lsp-{s}.log", .{ state_dir, std.fs.path.sep, self.tag }) catch |e| return tp.exit_error(e, @errorReturnTrace()); self.log_file = std.fs.createFileAbsolute(log_file_path.items, .{ .truncate = true }) catch |e| return tp.exit_error(e, @errorReturnTrace()); + self.log_file_path = log_file_path.toOwnedSlice() catch null; } fn receive(self: *Process, from: tp.pid_ref, m: tp.message) tp.result { @@ -440,18 +454,14 @@ const Process = struct { } fn handle_not_found(self: *Process) error{ExitNormal}!void { - const logger = log.logger("LSP"); - defer logger.deinit(); - logger.print_err("init", "'{s}' executable not found", .{self.tag}); + self.err_msg("'{s}' executable not found", .{self.tag}); self.write_log("### '{s}' executable not found ###\n", .{self.tag}); self.parent.send(.{ sp_tag, self.tag, "not found" }) catch {}; return error.ExitNormal; } fn handle_terminated(self: *Process, err: []const u8, code: u32) error{ExitNormal}!void { - const logger = log.logger("LSP"); - defer logger.deinit(); - logger.print("terminated: {s} {d}", .{ err, code }); + self.msg("terminated: {s} {d}", .{ err, code }); self.write_log("### subprocess terminated {s} {d} ###\n", .{ err, code }); self.parent.send(.{ sp_tag, self.tag, "done" }) catch {}; return error.ExitNormal; @@ -463,9 +473,9 @@ const Process = struct { const id = self.next_id; self.next_id += 1; - var msg = std.ArrayList(u8).init(self.allocator); - defer msg.deinit(); - const msg_writer = msg.writer(); + var request = std.ArrayList(u8).init(self.allocator); + defer request.deinit(); + const msg_writer = request.writer(); try cbor.writeMapHeader(msg_writer, 4); try cbor.writeValue(msg_writer, "jsonrpc"); try cbor.writeValue(msg_writer, "2.0"); @@ -476,7 +486,7 @@ const Process = struct { try cbor.writeValue(msg_writer, "params"); _ = try msg_writer.write(params_cb); - const json = try cbor.toJsonAlloc(self.allocator, msg.items); + const json = try cbor.toJsonAlloc(self.allocator, request.items); defer self.allocator.free(json); var output = std.ArrayList(u8).init(self.allocator); defer output.deinit(); @@ -499,9 +509,9 @@ const Process = struct { fn send_response(self: *Process, cbor_id: []const u8, result_cb: []const u8) (error{Closed} || SendError || cbor.Error || cbor.JsonEncodeError)!void { const sp = if (self.sp) |*sp| sp else return error.Closed; - var msg = std.ArrayList(u8).init(self.allocator); - defer msg.deinit(); - const msg_writer = msg.writer(); + var response = std.ArrayList(u8).init(self.allocator); + defer response.deinit(); + const msg_writer = response.writer(); try cbor.writeMapHeader(msg_writer, 3); try cbor.writeValue(msg_writer, "jsonrpc"); try cbor.writeValue(msg_writer, "2.0"); @@ -510,7 +520,7 @@ const Process = struct { try cbor.writeValue(msg_writer, "result"); _ = try msg_writer.write(result_cb); - const json = try cbor.toJsonAlloc(self.allocator, msg.items); + const json = try cbor.toJsonAlloc(self.allocator, response.items); defer self.allocator.free(json); var output = std.ArrayList(u8).init(self.allocator); defer output.deinit(); @@ -528,9 +538,9 @@ const Process = struct { fn send_error_response(self: *Process, cbor_id: []const u8, error_code: ErrorCode, message: []const u8) (error{Closed} || SendError || cbor.Error || cbor.JsonEncodeError)!void { const sp = if (self.sp) |*sp| sp else return error.Closed; - var msg = std.ArrayList(u8).init(self.allocator); - defer msg.deinit(); - const msg_writer = msg.writer(); + var response = std.ArrayList(u8).init(self.allocator); + defer response.deinit(); + const msg_writer = response.writer(); try cbor.writeMapHeader(msg_writer, 3); try cbor.writeValue(msg_writer, "jsonrpc"); try cbor.writeValue(msg_writer, "2.0"); @@ -543,7 +553,7 @@ const Process = struct { try cbor.writeValue(msg_writer, "message"); try cbor.writeValue(msg_writer, message); - const json = try cbor.toJsonAlloc(self.allocator, msg.items); + const json = try cbor.toJsonAlloc(self.allocator, response.items); defer self.allocator.free(json); var output = std.ArrayList(u8).init(self.allocator); defer output.deinit(); @@ -563,9 +573,9 @@ const Process = struct { const have_params = !(cbor.match(params_cb, cbor.null_) catch false); - var msg = std.ArrayList(u8).init(self.allocator); - defer msg.deinit(); - const msg_writer = msg.writer(); + var notification = std.ArrayList(u8).init(self.allocator); + defer notification.deinit(); + const msg_writer = notification.writer(); try cbor.writeMapHeader(msg_writer, 3); try cbor.writeValue(msg_writer, "jsonrpc"); try cbor.writeValue(msg_writer, "2.0"); @@ -578,7 +588,7 @@ const Process = struct { try cbor.writeMapHeader(msg_writer, 0); } - const json = try cbor.toJsonAlloc(self.allocator, msg.items); + const json = try cbor.toJsonAlloc(self.allocator, notification.items); defer self.allocator.free(json); var output = std.ArrayList(u8).init(self.allocator); defer output.deinit(); @@ -617,9 +627,9 @@ const Process = struct { const json = if (params) |p| try cbor.toJsonPrettyAlloc(self.allocator, p) else null; defer if (json) |p| self.allocator.free(p); self.write_log("### RECV req: {s}\nmethod: {s}\n{s}\n###\n", .{ json_id, method, json orelse "no params" }); - var msg = std.ArrayList(u8).init(self.allocator); - defer msg.deinit(); - const writer = msg.writer(); + var request = std.ArrayList(u8).init(self.allocator); + defer request.deinit(); + const writer = request.writer(); try cbor.writeArrayHeader(writer, 7); try cbor.writeValue(writer, sp_tag); try cbor.writeValue(writer, self.project); @@ -628,7 +638,7 @@ const Process = struct { try cbor.writeValue(writer, method); try writer.writeAll(cbor_id); if (params) |p| _ = try writer.write(p) else try cbor.writeValue(writer, null); - self.parent.send_raw(.{ .buf = msg.items }) catch return error.SendFailed; + self.parent.send_raw(.{ .buf = request.items }) catch return error.SendFailed; } fn receive_lsp_response(self: *Process, cbor_id: []const u8, result: ?[]const u8, err: ?[]const u8) Error!void { @@ -640,9 +650,9 @@ const Process = struct { defer if (json_err) |p| self.allocator.free(p); self.write_log("### RECV rsp: {s} {s}\n{s}\n###\n", .{ json_id, if (json_err) |_| "error" else "response", json_err orelse json orelse "no result" }); const from = self.requests.get(cbor_id) orelse return; - var msg = std.ArrayList(u8).init(self.allocator); - defer msg.deinit(); - const writer = msg.writer(); + var response = std.ArrayList(u8).init(self.allocator); + defer response.deinit(); + const writer = response.writer(); try cbor.writeArrayHeader(writer, 4); try cbor.writeValue(writer, sp_tag); try cbor.writeValue(writer, self.tag); @@ -653,16 +663,16 @@ const Process = struct { try cbor.writeValue(writer, "result"); _ = try writer.write(result_); } - from.send_raw(.{ .buf = msg.items }) catch return error.SendFailed; + from.send_raw(.{ .buf = response.items }) catch return error.SendFailed; } fn receive_lsp_notification(self: *Process, method: []const u8, params: ?[]const u8) Error!void { const json = if (params) |p| try cbor.toJsonPrettyAlloc(self.allocator, p) else null; defer if (json) |p| self.allocator.free(p); self.write_log("### RECV notify:\nmethod: {s}\n{s}\n###\n", .{ method, json orelse "no params" }); - var msg = std.ArrayList(u8).init(self.allocator); - defer msg.deinit(); - const writer = msg.writer(); + var notification = std.ArrayList(u8).init(self.allocator); + defer notification.deinit(); + const writer = notification.writer(); try cbor.writeArrayHeader(writer, 6); try cbor.writeValue(writer, sp_tag); try cbor.writeValue(writer, self.project); @@ -670,7 +680,7 @@ const Process = struct { try cbor.writeValue(writer, "notify"); try cbor.writeValue(writer, method); if (params) |p| _ = try writer.write(p) else try cbor.writeValue(writer, null); - self.parent.send_raw(.{ .buf = msg.items }) catch return error.SendFailed; + self.parent.send_raw(.{ .buf = notification.items }) catch return error.SendFailed; } fn write_log(self: *Process, comptime format: []const u8, args: anytype) void { diff --git a/src/Project.zig b/src/Project.zig index 23eaae1..ad71f8c 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -8,6 +8,7 @@ const Buffer = @import("Buffer"); const fuzzig = @import("fuzzig"); const tracy = @import("tracy"); const git = @import("git"); +const file_type_config = @import("file_type_config"); const builtin = @import("builtin"); const LSP = @import("LSP.zig"); @@ -52,6 +53,9 @@ pub const LspOrClientError = (LspError || ClientError); const File = struct { path: []const u8, + type: []const u8, + icon: []const u8, + color: u24, mtime: i128, pos: FilePos = .{}, visited: bool = false, @@ -341,7 +345,7 @@ pub fn request_n_most_recent_file(self: *Self, from: tp.pid_ref, n: usize) Clien pub fn request_recent_files(self: *Self, from: tp.pid_ref, max: usize) ClientError!void { defer from.send(.{ "PRJ", "recent_done", self.longest_file_path, "" }) catch {}; for (self.files.items, 0..) |file, i| { - from.send(.{ "PRJ", "recent", self.longest_file_path, file.path }) catch return error.ClientFailed; + from.send(.{ "PRJ", "recent", self.longest_file_path, file.path, file.type, file.icon, file.color }) catch return error.ClientFailed; if (i >= max) return; } } @@ -356,7 +360,7 @@ fn simple_query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: [ defer self.allocator.free(matches); var n: usize = 0; while (n < query.len) : (n += 1) matches[n] = idx + n; - from.send(.{ "PRJ", "recent", self.longest_file_path, file.path, matches }) catch return error.ClientFailed; + from.send(.{ "PRJ", "recent", self.longest_file_path, file.path, file.type, file.icon, file.color, matches }) catch return error.ClientFailed; i += 1; if (i >= max) return i; } @@ -379,6 +383,9 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co const Match = struct { path: []const u8, + type: []const u8, + icon: []const u8, + color: u24, score: i32, matches: []const usize, }; @@ -389,6 +396,9 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co if (match.score) |score| { (try matches.addOne()).* = .{ .path = file.path, + .type = file.type, + .icon = file.icon, + .color = file.color, .score = score, .matches = try self.allocator.dupe(usize, match.matches), }; @@ -404,13 +414,24 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co std.mem.sort(Match, matches.items, {}, less_fn); for (matches.items[0..@min(max, matches.items.len)]) |match| - from.send(.{ "PRJ", "recent", self.longest_file_path, match.path, match.matches }) catch return error.ClientFailed; + from.send(.{ "PRJ", "recent", self.longest_file_path, match.path, match.type, match.icon, match.color, match.matches }) catch return error.ClientFailed; return @min(max, matches.items.len); } -pub fn walk_tree_entry(self: *Self, file_path: []const u8, mtime: i128) OutOfMemoryError!void { +pub fn walk_tree_entry( + self: *Self, + file_path: []const u8, + mtime: i128, +) OutOfMemoryError!void { + const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(file_path); 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(self.allocator)).* = .{ + .path = try self.allocator.dupe(u8, file_path), + .type = file_type, + .icon = file_icon, + .color = file_color, + .mtime = mtime, + }; } pub fn walk_tree_done(self: *Self, parent: tp.pid_ref) OutOfMemoryError!void { @@ -420,6 +441,35 @@ pub fn walk_tree_done(self: *Self, parent: tp.pid_ref) OutOfMemoryError!void { return self.loaded(parent); } +fn default_ft() struct { []const u8, []const u8, u24 } { + return .{ + file_type_config.default.name, + file_type_config.default.icon, + file_type_config.default.color, + }; +} + +pub fn guess_path_file_type(path: []const u8, file_name: []const u8) struct { []const u8, []const u8, u24 } { + var buf: [4096]u8 = undefined; + const file_path = std.fmt.bufPrint(&buf, "{s}{}{s}", .{ path, std.fs.path.sep, file_name }) catch return default_ft(); + return guess_file_type(file_path); +} + +pub fn guess_file_type(file_path: []const u8) struct { []const u8, []const u8, u24 } { + var buf: [1024]u8 = undefined; + const content: []const u8 = blk: { + const file = std.fs.cwd().openFile(file_path, .{ .mode = .read_only }) catch break :blk &.{}; + defer file.close(); + const size = file.read(&buf) catch break :blk &.{}; + break :blk buf[0..size]; + }; + return if (file_type_config.guess_file_type(file_path, content)) |ft| .{ + ft.name, + ft.icon orelse file_type_config.default.icon, + ft.color orelse file_type_config.default.color, + } else default_ft(); +} + fn merge_pending_files(self: *Self) OutOfMemoryError!void { defer self.sort_files_by_mtime(); const existing = try self.files.toOwnedSlice(self.allocator); @@ -469,9 +519,13 @@ fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usi } return; } + const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(file_path); if (row != 0) { (try self.files.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, file_path), + .type = file_type, + .icon = file_icon, + .color = file_color, .mtime = mtime, .pos = .{ .row = row, .col = col }, .visited = true, @@ -479,6 +533,9 @@ fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usi } else { (try self.files.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, file_path), + .type = file_type, + .icon = file_icon, + .color = file_color, .mtime = mtime, }; } @@ -1473,7 +1530,16 @@ 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: *Self, params_cb: []const u8) !void { + return self.show_or_log_message(.show, params_cb); +} + +pub fn log_message(self: *Self, params_cb: []const u8) !void { + return self.show_or_log_message(.log, params_cb); +} + +fn show_or_log_message(self: *Self, operation: enum { show, log }, params_cb: []const u8) !void { + if (!tp.env.get().is("lsp_verbose")) return; var type_: i32 = 0; var message: ?[]const u8 = null; var iter = params_cb; @@ -1493,7 +1559,14 @@ pub fn show_message(self: *Self, _: tp.pid_ref, params_cb: []const u8) !void { if (type_ <= 2) self.logger_lsp.err_msg("lsp", msg) else - self.logger_lsp.print("{s}", .{msg}); + self.logger_lsp.print("{s}: {s}", .{ @tagName(operation), msg }); +} + +pub fn show_notification(self: *Self, method: []const u8, params_cb: []const u8) !void { + if (!tp.env.get().is("lsp_verbose")) return; + const params = try cbor.toJsonAlloc(self.allocator, params_cb); + defer self.allocator.free(params); + self.logger_lsp.print("LSP notification: {s} -> {s}", .{ method, params }); } pub fn register_capability(self: *Self, from: tp.pid_ref, cbor_id: []const u8, params_cb: []const u8) ClientError!void { @@ -1941,7 +2014,14 @@ pub fn process_git(self: *Self, parent: tp.pid_ref, m: tp.message) (OutOfMemoryE } 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 }; + const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(path); + (try self.pending.addOne(self.allocator)).* = .{ + .path = try self.allocator.dupe(u8, path), + .type = file_type, + .icon = file_icon, + .color = file_color, + .mtime = stat.mtime, + }; } else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.null_ })) { self.state.workspace_files = .done; try self.loaded(parent); diff --git a/src/buffer/Manager.zig b/src/buffer/Manager.zig index 9097d58..86422b8 100644 --- a/src/buffer/Manager.zig +++ b/src/buffer/Manager.zig @@ -80,7 +80,7 @@ pub fn extract_state(self: *Self, iter: *[]const u8) !void { } } -pub fn get_buffer_for_file(self: *Self, file_path: []const u8) ?*Buffer { +pub fn get_buffer_for_file(self: *const Self, file_path: []const u8) ?*Buffer { return self.buffers.get(file_path); } diff --git a/src/command.zig b/src/command.zig index 8577a9a..16ad9f6 100644 --- a/src/command.zig +++ b/src/command.zig @@ -33,6 +33,7 @@ const Vtable = struct { pub const Metadata = struct { description: []const u8 = &[_]u8{}, arguments: []const ArgumentType = &[_]ArgumentType{}, + icon: ?[]const u8 = null, }; pub const ArgumentType = enum { @@ -188,6 +189,11 @@ pub fn get_arguments(id: ID) ?[]const ArgumentType { return (commands.items[id] orelse return null).meta.arguments; } +pub fn get_icon(id: ID) ?[]const u8 { + if (id >= commands.items.len) return null; + return (commands.items[id] orelse return null).meta.icon; +} + const suppressed_errors = std.StaticStringMap(void).initComptime(.{ .{ "enable_fast_scroll", void }, .{ "disable_fast_scroll", void }, diff --git a/src/config.zig b/src/config.zig index f2a65e6..bb12d9a 100644 --- a/src/config.zig +++ b/src/config.zig @@ -36,6 +36,13 @@ show_fileicons: bool = true, start_debugger_on_crash: bool = false, +widget_style: WidgetStyle = .compact, +palette_style: WidgetStyle = .bars_top_bottom, +panel_style: WidgetStyle = .compact, +home_style: WidgetStyle = .bars_top_bottom, + +lsp_output: enum { quiet, verbose } = .quiet, + include_files: []const u8 = "", pub const DigitStyle = enum { @@ -56,3 +63,25 @@ pub const IndentMode = enum { spaces, tabs, }; + +pub const WidgetType = enum { + none, + palette, + panel, + home, +}; + +pub const WidgetStyle = enum { + bars_top_bottom, + bars_left_right, + thick_boxed, + extra_thick_boxed, + dotted_boxed, + rounded_boxed, + double_boxed, + single_double_top_bottom_boxed, + single_double_left_right_boxed, + boxed, + spacious, + compact, +}; diff --git a/src/file_type_config.zig b/src/file_type_config.zig index 008bb9d..944ea16 100644 --- a/src/file_type_config.zig +++ b/src/file_type_config.zig @@ -20,6 +20,8 @@ pub const default = struct { pub const color = 0x000000; }; +pub const folder_icon = ""; + fn from_file_type(file_type: syntax.FileType) @This() { return .{ .name = file_type.name, @@ -84,7 +86,7 @@ pub fn get(file_type_name: []const u8) !?@This() { break :file_type if (syntax.FileType.get_by_name_static(file_type_name)) |ft| from_file_type(ft) else null; } }; - try cache.put(cache_allocator, file_type_name, file_type); + try cache.put(cache_allocator, try cache_allocator.dupe(u8, file_type_name), file_type); break :self file_type; }; } diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 339972b..30ddc9c 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -1,6 +1,7 @@ { "project": { "press": [ + ["ctrl+alt+shift+r", "restart"], ["ctrl+e", "find_file"], ["ctrl+shift+n", "create_new_file"], ["ctrl+r", "open_recent_project"], @@ -28,7 +29,8 @@ ["f10", "theme_next"], ["f11", "toggle_panel"], ["f12", "toggle_inputview"], - ["alt+!", "select_task"], + ["alt+!", "run_task"], + ["ctrl+1", "add_task"], ["ctrl+tab", "next_tab"], ["ctrl+shift+tab", "previous_tab"], ["ctrl+shift+e", "switch_buffers"], @@ -38,6 +40,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+shift+t", "set_session_tab_width"], ["alt+d", ["shell_execute_insert", "date", "--iso-8601"]], ["ctrl+alt+shift+d", ["shell_execute_insert", "date", "--iso-8601=seconds"]] ] @@ -162,6 +165,7 @@ ["shift+f11", "toggle_highlight_columns"], ["ctrl+f11", "toggle_inspector_view"], ["f12", "goto_definition"], + ["ctrl+.", "completion"], ["f34", "toggle_whitespace_mode"], ["escape", "cancel"], ["enter", "smart_insert_line"], @@ -236,6 +240,8 @@ ["page_down", "select_page_down"], ["ctrl+page_up", "select_scroll_page_up"], ["ctrl+page_down", "select_scroll_page_down"], + ["ctrl+b", "move_to_char", "select_to_char_left"], + ["ctrl+t", "move_to_char", "select_to_char_right"], ["ctrl+space", "enter_mode", "normal"], ["ctrl+x", ["cut"], ["enter_mode", "normal"], ["cancel"]], ["ctrl+c", ["copy"], ["enter_mode", "normal"], ["cancel"]], @@ -249,6 +255,7 @@ "inherit": "project", "on_match_failure": "ignore", "press": [ + ["alt+f9", "home_next_widget_style"], ["ctrl+e", "find_file"], ["f", "find_file"], ["e", "find_file"], @@ -279,9 +286,12 @@ }, "overlay/palette": { "press": [ + ["alt+f9", "overlay_next_widget_style"], + ["alt+!", "add_task"], ["ctrl+j", "toggle_panel"], ["ctrl+q", "quit"], ["ctrl+w", "close_file"], + ["ctrl+shift+f", "find_in_files"], ["ctrl+p", "palette_menu_up"], ["ctrl+n", "palette_menu_down"], ["ctrl+e", "palette_menu_down"], @@ -328,7 +338,7 @@ ["right_control", "palette_menu_activate_quick"] ] }, - "mini/goto": { + "mini/numeric": { "press": [ ["ctrl+q", "quit"], ["ctrl+v", "system_paste"], @@ -338,7 +348,7 @@ ["ctrl+l", "scroll_view_center_cycle"], ["ctrl+space", "mini_mode_cancel"], ["escape", "mini_mode_cancel"], - ["enter", "exit_mini_mode"], + ["enter", "mini_mode_select"], ["backspace", "mini_mode_delete_backwards"] ] }, @@ -347,6 +357,8 @@ ["ctrl+g", "mini_mode_cancel"], ["ctrl+c", "mini_mode_cancel"], ["ctrl+l", "scroll_view_center_cycle"], + ["tab", "mini_mode_insert_bytes", "\t"], + ["enter", "mini_mode_insert_bytes", "\n"], ["escape", "mini_mode_cancel"], ["backspace", "mini_mode_cancel"] ] diff --git a/src/log.zig b/src/log.zig index 4c4e038..808f9d8 100644 --- a/src/log.zig +++ b/src/log.zig @@ -11,6 +11,8 @@ subscriber: ?tp.pid, heap: [32 + 1024]u8, fba: std.heap.FixedBufferAllocator, msg_store: MsgStore, +no_stdout: bool = false, +no_stderr: bool = false, const MsgStore = std.DoublyLinkedList; const MsgStoreEntry = struct { @@ -85,12 +87,23 @@ fn store_reset(self: *Self) void { fn receive(self: *Self, from: tp.pid_ref, m: tp.message) tp.result { errdefer self.deinit(); - if (try m.match(.{ "log", tp.more })) { + var output: []const u8 = undefined; + if (try m.match(.{ "log", "error", tp.string, "std.log", "->", tp.extract(&output) })) { if (self.subscriber) |subscriber| { subscriber.send_raw(m) catch {}; } else { self.store(m); } + if (!self.no_stderr) + std.io.getStdErr().writer().print("{s}\n", .{output}) catch {}; + } else if (try m.match(.{ "log", tp.string, tp.extract(&output) })) { + if (self.subscriber) |subscriber| { + subscriber.send_raw(m) catch {}; + } else { + self.store(m); + } + if (!self.no_stdout) + std.io.getStdOut().writer().print("{s}\n", .{output}) catch {}; } else if (try m.match(.{"subscribe"})) { // log("subscribed"); if (self.subscriber) |*s| s.deinit(); @@ -101,6 +114,14 @@ fn receive(self: *Self, from: tp.pid_ref, m: tp.message) tp.result { if (self.subscriber) |*s| s.deinit(); self.subscriber = null; self.store_reset(); + } else if (try m.match(.{ "stdout", "enable" })) { + self.no_stdout = false; + } else if (try m.match(.{ "stdout", "disable" })) { + self.no_stdout = true; + } else if (try m.match(.{ "stderr", "enable" })) { + self.no_stderr = false; + } else if (try m.match(.{ "stderr", "disable" })) { + self.no_stderr = true; } else if (try m.match(.{"shutdown"})) { return tp.exit_normal(); } @@ -208,6 +229,14 @@ pub fn unsubscribe() tp.result { return tp.env.get().proc("log").send(.{"unsubscribe"}); } +pub fn stdout(state: enum { enable, disable }) void { + tp.env.get().proc("log").send(.{ "stdout", state }) catch {}; +} + +pub fn stderr(state: enum { enable, disable }) void { + tp.env.get().proc("log").send(.{ "stderr", state }) catch {}; +} + var std_log_pid: ?tp.pid_ref = null; pub fn set_std_log_pid(pid: ?tp.pid_ref) void { diff --git a/src/main.zig b/src/main.zig index f337409..e914235 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,6 +6,7 @@ const color = @import("color"); const flags = @import("flags"); const builtin = @import("builtin"); const bin_path = @import("bin_path"); +const sep = std.fs.path.sep; const list_languages = @import("list_languages.zig"); pub const file_link = @import("file_link.zig"); @@ -330,7 +331,12 @@ pub fn main() anyerror!void { try cbor.writeValue(writer, cmd_); try cbor.writeArrayHeader(writer, count - 1); - while (cmd_args.next()) |arg| try cbor.writeValue(writer, arg); + while (cmd_args.next()) |arg| { + if (std.fmt.parseInt(isize, arg, 10) catch null) |i| + try cbor.writeValue(writer, i) + else + try cbor.writeValue(writer, arg); + } try tui_proc.send_raw(.{ .buf = msg.items }); } @@ -398,7 +404,7 @@ fn trace_to_file(m: thespian.message.c_buffer_type) callconv(.C) void { const a = std.heap.c_allocator; var path = std.ArrayList(u8).init(a); defer path.deinit(); - path.writer().print("{s}/trace.log", .{get_state_dir() catch return}) catch return; + path.writer().print("{s}{c}trace.log", .{ get_state_dir() catch return, sep }) catch return; const file = std.fs.createFileAbsolute(path.items, .{ .truncate = true }) catch return; State.state = .{ .file = file, @@ -502,12 +508,12 @@ pub fn parse_text_config_file(T: type, allocator: std.mem.Allocator, conf: *T, b lineno += 1; if (line.len == 0 or line[0] == '#') continue; - const sep = std.mem.indexOfScalar(u8, line, ' ') orelse { + const spc = std.mem.indexOfScalar(u8, line, ' ') orelse { std.log.err("{s}:{}: {s} missing value", .{ file_name, lineno, line }); continue; }; - const name = line[0..sep]; - const value_str = line[sep + 1 ..]; + const name = line[0..spc]; + const value_str = line[spc + 1 ..]; const cb = cbor.fromJsonAlloc(allocator, value_str) catch { std.log.err("{s}:{}: {s} has bad value: {s}", .{ file_name, lineno, name, value_str }); continue; @@ -781,6 +787,11 @@ pub const ConfigDirError = error{ AppConfigDirUnavailable, }; +fn make_dir_error(path: []const u8, err: anytype) @TypeOf(err) { + std.log.err("failed to create directory: '{s}'", .{path}); + return err; +} + fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 { const a = std.heap.c_allocator; const local = struct { @@ -791,22 +802,22 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 { dir else if (std.process.getEnvVarOwned(a, "XDG_CONFIG_HOME") catch null) |xdg| ret: { defer a.free(xdg); - break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/{s}", .{ xdg, appname }); + break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}{c}{s}", .{ xdg, sep, appname }); } else if (std.process.getEnvVarOwned(a, "HOME") catch null) |home| ret: { defer a.free(home); - const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/.config", .{home}); + const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}{c}.config", .{ home, sep }); std.fs.makeDirAbsolute(dir) catch |e| switch (e) { error.PathAlreadyExists => {}, - else => return error.MakeHomeConfigDirFailed, + else => return make_dir_error(dir, error.MakeHomeConfigDirFailed), }; - break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/.config/{s}", .{ home, appname }); + break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}{c}.config{c}{s}", .{ home, sep, sep, appname }); } else if (builtin.os.tag == .windows) ret: { if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| { defer a.free(appdata); - const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/{s}", .{ appdata, appname }); + const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}{c}{s}", .{ appdata, sep, appname }); std.fs.makeDirAbsolute(dir) catch |e| switch (e) { error.PathAlreadyExists => {}, - else => return error.MakeAppConfigDirFailed, + else => return make_dir_error(dir, error.MakeAppConfigDirFailed), }; break :ret dir; } else return error.AppConfigDirUnavailable; @@ -815,14 +826,14 @@ 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 make_dir_error(config_dir, error.MakeConfigDirFailed), }; 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 {}; + std.fs.makeDirAbsolute(try std.fmt.bufPrint(&keybind_dir_buffer, "{s}{c}{s}", .{ config_dir, sep, 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 {}; + std.fs.makeDirAbsolute(try std.fmt.bufPrint(&theme_dir_buffer, "{s}{c}{s}", .{ config_dir, sep, theme_dir })) catch {}; return config_dir; } @@ -841,22 +852,22 @@ fn get_app_cache_dir(appname: []const u8) ![]const u8 { dir else if (std.process.getEnvVarOwned(a, "XDG_CACHE_HOME") catch null) |xdg| ret: { defer a.free(xdg); - break :ret try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}/{s}", .{ xdg, appname }); + break :ret try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}{c}{s}", .{ xdg, sep, appname }); } else if (std.process.getEnvVarOwned(a, "HOME") catch null) |home| ret: { defer a.free(home); - const dir = try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}/.cache", .{home}); + const dir = try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}{c}.cache", .{ home, sep }); std.fs.makeDirAbsolute(dir) catch |e| switch (e) { error.PathAlreadyExists => {}, - else => return e, + else => return make_dir_error(dir, e), }; - break :ret try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}/.cache/{s}", .{ home, appname }); + break :ret try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}{c}.cache{c}{s}", .{ home, sep, sep, appname }); } else if (builtin.os.tag == .windows) ret: { if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| { defer a.free(appdata); - const dir = try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}/{s}", .{ appdata, appname }); + const dir = try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}{c}{s}", .{ appdata, sep, appname }); std.fs.makeDirAbsolute(dir) catch |e| switch (e) { error.PathAlreadyExists => {}, - else => return e, + else => return make_dir_error(dir, e), }; break :ret dir; } else return error.AppCacheDirUnavailable; @@ -865,7 +876,7 @@ fn get_app_cache_dir(appname: []const u8) ![]const u8 { local.cache_dir = cache_dir; std.fs.makeDirAbsolute(cache_dir) catch |e| switch (e) { error.PathAlreadyExists => {}, - else => return e, + else => return make_dir_error(cache_dir, e), }; return cache_dir; } @@ -884,27 +895,27 @@ fn get_app_state_dir(appname: []const u8) ![]const u8 { dir else if (std.process.getEnvVarOwned(a, "XDG_STATE_HOME") catch null) |xdg| ret: { defer a.free(xdg); - break :ret try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/{s}", .{ xdg, appname }); + break :ret try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}{s}", .{ xdg, sep, appname }); } else if (std.process.getEnvVarOwned(a, "HOME") catch null) |home| ret: { defer a.free(home); - var dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/.local", .{home}); + var dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}.local", .{ home, sep }); std.fs.makeDirAbsolute(dir) catch |e| switch (e) { error.PathAlreadyExists => {}, - else => return e, + else => return make_dir_error(dir, e), }; - dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/.local/state", .{home}); + dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}.local{c}state", .{ home, sep, sep }); std.fs.makeDirAbsolute(dir) catch |e| switch (e) { error.PathAlreadyExists => {}, - else => return e, + else => return make_dir_error(dir, e), }; - break :ret try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/.local/state/{s}", .{ home, appname }); + break :ret try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}.local{c}state{c}{s}", .{ home, sep, sep, sep, appname }); } else if (builtin.os.tag == .windows) ret: { if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| { defer a.free(appdata); - const dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/{s}", .{ appdata, appname }); + const dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}{s}", .{ appdata, sep, appname }); std.fs.makeDirAbsolute(dir) catch |e| switch (e) { error.PathAlreadyExists => {}, - else => return e, + else => return make_dir_error(dir, e), }; break :ret dir; } else return error.AppCacheDirUnavailable; @@ -913,7 +924,7 @@ fn get_app_state_dir(appname: []const u8) ![]const u8 { local.state_dir = state_dir; std.fs.makeDirAbsolute(state_dir) catch |e| switch (e) { error.PathAlreadyExists => {}, - else => return e, + else => return make_dir_error(state_dir, e), }; return state_dir; } @@ -926,7 +937,7 @@ fn get_app_config_dir_file_name(appname: []const u8, comptime config_file_name: const local = struct { var config_file_buffer: [std.posix.PATH_MAX]u8 = undefined; }; - return std.fmt.bufPrint(&local.config_file_buffer, "{s}/{s}", .{ try get_app_config_dir(appname), config_file_name }); + return std.fmt.bufPrint(&local.config_file_buffer, "{s}{c}{s}", .{ try get_app_config_dir(appname), sep, config_file_name }); } pub fn get_config_file_name(T: type) ![]const u8 { @@ -942,7 +953,7 @@ pub fn get_restore_file_name() ![]const u8 { const restore_file = if (local.restore_file) |file| file else - try std.fmt.bufPrint(&local.restore_file_buffer, "{s}/{s}", .{ try get_app_state_dir(application_name), restore_file_name }); + try std.fmt.bufPrint(&local.restore_file_buffer, "{s}{c}{s}", .{ try get_app_state_dir(application_name), sep, restore_file_name }); local.restore_file = restore_file; return restore_file; } @@ -958,7 +969,7 @@ fn get_keybind_namespaces_directory() ![]const u8 { 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), keybind_dir }); + return try std.fmt.bufPrint(&local.dir_buffer, "{s}{c}{s}", .{ try get_app_config_dir(application_name), sep, keybind_dir }); } pub fn get_keybind_namespace_file_name(namespace_name: []const u8) ![]const u8 { @@ -966,7 +977,7 @@ pub fn get_keybind_namespace_file_name(namespace_name: []const u8) ![]const u8 { const local = struct { var file_buffer: [std.posix.PATH_MAX]u8 = undefined; }; - return try std.fmt.bufPrint(&local.file_buffer, "{s}/{s}.json", .{ dir, namespace_name }); + return try std.fmt.bufPrint(&local.file_buffer, "{s}{c}{s}.json", .{ dir, sep, namespace_name }); } const theme_dir = "themes"; @@ -980,7 +991,7 @@ fn get_theme_directory() ![]const u8 { 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 }); + return try std.fmt.bufPrint(&local.dir_buffer, "{s}{c}{s}", .{ try get_app_config_dir(application_name), sep, theme_dir }); } pub fn get_theme_file_name(theme_name: []const u8) ![]const u8 { @@ -988,7 +999,7 @@ pub fn get_theme_file_name(theme_name: []const u8) ![]const u8 { 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 }); + return try std.fmt.bufPrint(&local.file_buffer, "{s}{c}{s}.json", .{ dir, sep, theme_name }); } fn restart() noreturn { diff --git a/src/project_manager.zig b/src/project_manager.zig index c29a78a..44e2f31 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -27,7 +27,7 @@ const OutOfMemoryError = error{OutOfMemory}; const FileSystemError = error{FileSystem}; const SetCwdError = if (builtin.os.tag == .windows) error{UnrecognizedVolume} else error{}; const CallError = tp.CallError; -const ProjectManagerError = (SpawnError || error{ProjectManagerFailed}); +const ProjectManagerError = (SpawnError || error{ ProjectManagerFailed, InvalidProjectDirectory }); pub fn get() SpawnError!Self { const pid = tp.env.get().proc(module_name); @@ -63,6 +63,7 @@ pub fn open(rel_project_directory: []const u8) (ProjectManagerError || FileSyste const project_directory = std.fs.cwd().realpath(rel_project_directory, &path_buf) catch "(none)"; const current_project = tp.env.get().str("project"); if (std.mem.eql(u8, current_project, project_directory)) return; + if (!root.is_directory(project_directory)) return error.InvalidProjectDirectory; var dir = try std.fs.openDirAbsolute(project_directory, .{}); try dir.setAsCwd(); dir.close(); @@ -97,9 +98,9 @@ pub fn request_recent_files(max: usize) (ProjectManagerError || ProjectError)!vo return send(.{ "request_recent_files", project, max }); } -pub fn request_recent_projects(allocator: std.mem.Allocator) (ProjectError || CallError)!tp.message { +pub fn request_recent_projects() (ProjectManagerError || ProjectError)!void { const project = tp.env.get().str("project"); - return (try get()).pid.call(allocator, request_timeout, .{ "request_recent_projects", project }); + return send(.{ "request_recent_projects", project }); } pub fn query_recent_files(max: usize, query: []const u8) (ProjectManagerError || ProjectError)!void { @@ -332,6 +333,8 @@ const Process = struct { var n: usize = 0; var task: []const u8 = undefined; var context: usize = undefined; + var tag: []const u8 = undefined; + var message: []const u8 = undefined; var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf); @@ -404,6 +407,11 @@ const Process = struct { self.hover(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; } 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, .{ "lsp", "msg", tp.extract(&tag), tp.extract(&message) })) { + if (tp.env.get().is("lsp_verbose")) + self.logger.print("{s}: {s}", .{ tag, message }); + } else if (try cbor.match(m.buf, .{ "lsp", "err", tp.extract(&tag), tp.extract(&message) })) { + self.logger.print("{s} error: {s}", .{ tag, message }); } else if (try cbor.match(m.buf, .{"shutdown"})) { self.persist_projects(); from.send(.{ "project_manager", "shutdown" }) catch return error.ClientFailed; @@ -461,6 +469,9 @@ const Process = struct { self.sort_projects_by_last_used(&recent_projects); var message = std.ArrayList(u8).init(self.allocator); const writer = message.writer(); + try cbor.writeArrayHeader(writer, 3); + try cbor.writeValue(writer, "PRJ"); + try cbor.writeValue(writer, "recent_projects"); try cbor.writeArrayHeader(writer, recent_projects.items.len); for (recent_projects.items) |project| { try cbor.writeArrayHeader(writer, 2); @@ -468,6 +479,7 @@ const Process = struct { try cbor.writeValue(writer, if (self.projects.get(project.name)) |_| true else false); } from.send_raw(.{ .buf = message.items }) catch return error.ClientFailed; + self.logger.print("{d} projects found", .{recent_projects.items.len}); } fn query_recent_files(self: *Process, from: tp.pid_ref, project_directory: []const u8, max: usize, query: []const u8) (ProjectError || Project.ClientError)!void { @@ -603,14 +615,11 @@ const Process = struct { return if (std.mem.eql(u8, method, "textDocument/publishDiagnostics")) project.publish_diagnostics(self.parent.ref(), params_cb) else if (std.mem.eql(u8, method, "window/showMessage")) - project.show_message(self.parent.ref(), params_cb) + project.show_message(params_cb) else if (std.mem.eql(u8, method, "window/logMessage")) - project.show_message(self.parent.ref(), params_cb) - else { - const params = try cbor.toJsonAlloc(self.allocator, params_cb); - defer self.allocator.free(params); - self.logger.print("LSP notification: {s} -> {s}", .{ method, params }); - }; + project.log_message(params_cb) + else + project.show_notification(method, params_cb); } fn dispatch_request(self: *Process, from: tp.pid_ref, project_directory: []const u8, language_server: []const u8, method: []const u8, cbor_id: []const u8, params_cb: []const u8) (ProjectError || Project.ClientError || cbor.Error || cbor.JsonEncodeError || UnsupportedError)!void { @@ -793,12 +802,19 @@ fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_ var iter = self.dir.iterateAssumeFirstIteration(); errdefer |e| self.parent.send(.{ "PRJ", "path_error", self.project_name, self.path, e }) catch {}; while (try iter.next()) |entry| { - switch (entry.kind) { - .directory => try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, "DIR", entry.name }), - .sym_link => try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, "LINK", entry.name }), - .file => try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, "FILE", entry.name }), + const event_type = switch (entry.kind) { + .directory => "DIR", + .sym_link => "LINK", + .file => "FILE", else => continue, - } + }; + const default = file_type_config.default; + const file_type, const icon, const color = switch (entry.kind) { + .directory => .{ "directory", file_type_config.folder_icon, default.color }, + .sym_link, .file => Project.guess_path_file_type(self.path, entry.name), + else => .{ default.name, default.icon, default.color }, + }; + try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, event_type, entry.name, file_type, icon, color }); count += 1; if (count >= self.max) break; } diff --git a/src/renderer/vaxis/renderer.zig b/src/renderer/vaxis/renderer.zig index 44b121d..f9343de 100644 --- a/src/renderer/vaxis/renderer.zig +++ b/src/renderer/vaxis/renderer.zig @@ -323,6 +323,9 @@ pub fn process_renderer_event(self: *Self, msg: []const u8) Error!void { })), }; }, + .mouse_leave => { + if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{"mouse_leave"})); + }, .focus_in => { if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{"focus_in"})); }, diff --git a/src/syntax/.gitignore b/src/syntax/.gitignore deleted file mode 100644 index 5211f11..0000000 --- a/src/syntax/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/.zig-cache/ diff --git a/src/syntax/LICENSE b/src/syntax/LICENSE deleted file mode 100644 index 0c64a22..0000000 --- a/src/syntax/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 CJ van den Berg - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/src/syntax/README.md b/src/syntax/README.md deleted file mode 100644 index f1cc61a..0000000 --- a/src/syntax/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# flow-syntax -Syntax highlighting module used by [flow](https://github.com/neurocyte/flow), [zat](https://github.com/neurocyte/zat) and [zine](https://github.com/kristoff-it/zine) diff --git a/src/syntax/build.zig b/src/syntax/build.zig deleted file mode 100644 index 4251ae1..0000000 --- a/src/syntax/build.zig +++ /dev/null @@ -1,154 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const use_tree_sitter = b.option(bool, "use_tree_sitter", "Enable tree-sitter (default: yes)") orelse true; - const options = b.addOptions(); - options.addOption(bool, "use_tree_sitter", use_tree_sitter); - const options_mod = options.createModule(); - - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - - const tree_sitter_dep = b.dependency("tree_sitter", .{ - .target = target, - .optimize = optimize, - }); - - const tree_sitter_host_dep = b.dependency("tree_sitter", .{}); - - const cbor_dep = b.dependency("cbor", .{ - .target = target, - .optimize = optimize, - }); - - const ts_bin_query_gen = b.addExecutable(.{ - .name = "ts_bin_query_gen", - .root_module = b.createModule(.{ - .root_source_file = b.path("src/ts_bin_query_gen.zig"), - .target = b.graph.host, - .optimize = .Debug, - }), - }); - 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", .{ - .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") }, - }, - }); - - 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); -} diff --git a/src/syntax/build.zig.zon b/src/syntax/build.zig.zon deleted file mode 100644 index 5d8194d..0000000 --- a/src/syntax/build.zig.zon +++ /dev/null @@ -1,22 +0,0 @@ -.{ - .name = .flow_syntax, - .version = "0.1.0", - .fingerprint = 0x3ba2584ea1cec85f, - .minimum_zig_version = "0.15.0-dev.1048+f43f89a70", - - .dependencies = .{ - .tree_sitter = .{ - .url = "https://github.com/neurocyte/tree-sitter/releases/download/master-f1f032d24f621e2ee4deab1c424d3bf9fb809f6e/source.tar.gz", - .hash = "tree_sitter-0.22.4-150-g7e3f5726-z0LhyN88UicDHlr22vQnOZ3DW9NWN1gOhDwLuCRXvrh2", - }, - .cbor = .{ - .url = "git+https://github.com/neurocyte/cbor#6eccce0b984296e7d05c20d83933cb31530e4fac", - .hash = "cbor-1.0.0-RcQE_N3yAADXjbyvhsmTQ6lf22l1nYgePq5FT8NaC4ic", - }, - }, - .paths = .{ - "src", - "build.zig", - "build.zig.zon", - }, -} diff --git a/src/syntax/src/QueryCache.zig b/src/syntax/src/QueryCache.zig deleted file mode 100644 index 7b011d5..0000000 --- a/src/syntax/src/QueryCache.zig +++ /dev/null @@ -1,195 +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_name: []const u8, - lang_fn: FileType.LangFn, - - 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); - errdefer allocator.destroy(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: 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, - .lang_fn = file_type.lang_fn, - .file_type_name = file_type.name, - .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.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: 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 deleted file mode 100644 index 9faa6fa..0000000 --- a/src/syntax/src/file_type.zig +++ /dev/null @@ -1,207 +0,0 @@ -const std = @import("std"); -const cbor = @import("cbor"); -const build_options = @import("build_options"); - -const treez = if (build_options.use_tree_sitter) - @import("treez") -else - @import("treez_dummy.zig"); - -pub const FileType = @This(); - -color: u24, -icon: []const u8, -name: []const u8, -description: []const u8, -lang_fn: LangFn, -extensions: []const []const u8, -first_line_matches: ?FirstLineMatch = null, -comment: []const u8, -formatter: ?[]const []const u8, -language_server: ?[]const []const u8, - -pub fn get_by_name_static(name: []const u8) ?FileType { - return FileType.static_file_types.get(name); -} - -pub fn get_all() []const FileType { - return FileType.static_file_types.values(); -} - -pub fn guess_static(file_path: ?[]const u8, content: []const u8) ?FileType { - if (guess_first_line_static(content)) |ft| return ft; - for (static_file_types.values()) |file_type| - if (file_path) |fp| if (match_file_type(file_type.extensions, fp)) - return file_type; - return null; -} - -fn guess_first_line_static(content: []const u8) ?FileType { - const first_line = if (std.mem.indexOf(u8, content, "\n")) |pos| content[0..pos] else content; - for (static_file_types.values()) |file_type| - if (file_type.first_line_matches) |match| - if (match_first_line(match.prefix, match.content, first_line)) - return file_type; - return null; -} - -pub fn match_first_line(match_prefix: ?[]const u8, match_content: ?[]const u8, first_line: []const u8) bool { - if (match_prefix == null and match_content == null) return false; - if (match_prefix) |prefix| - if (prefix.len > first_line.len or !std.mem.eql(u8, first_line[0..prefix.len], prefix)) - return false; - if (match_content) |content| - if (std.mem.indexOf(u8, first_line, content)) |_| {} else return false; - return true; -} - -pub fn match_file_type(extensions: []const []const u8, file_path: []const u8) bool { - const basename = std.fs.path.basename(file_path); - const extension = std.fs.path.extension(file_path); - return for (extensions) |ext| { - if (ext.len == basename.len and std.mem.eql(u8, ext, basename)) - return true; - if (extension.len > 0 and ext.len == extension.len - 1 and std.mem.eql(u8, ext, extension[1..])) - return true; - } else false; -} - -pub fn Parser(comptime lang: []const u8) LangFn { - return get_parser(lang); -} - -fn get_parser(comptime lang: []const u8) LangFn { - if (build_options.use_tree_sitter) { - const language_name = ft_func_name(lang); - return @extern(?LangFn, .{ .name = "tree_sitter_" ++ language_name }) orelse @compileError(std.fmt.comptimePrint("Cannot find extern tree_sitter_{s}", .{language_name})); - } else { - return treez.Language.LangFn; - } -} - -fn ft_func_name(comptime lang: []const u8) []const u8 { - var transform: [lang.len]u8 = undefined; - for (lang, 0..) |c, i| - transform[i] = if (c == '-') '_' else c; - const func_name = transform; - return &func_name; -} - -pub const LangFn = *const fn () callconv(.c) ?*const treez.Language; - -pub const FirstLineMatch = struct { - prefix: ?[]const u8 = null, - content: ?[]const u8 = null, -}; - -const static_file_type_list = load_file_types(@import("file_types.zig")); -const static_file_types = std.StaticStringMap(FileType).initComptime(static_file_type_list); - -fn vec(comptime args: anytype) []const []const u8 { - var cmd: []const []const u8 = &[_][]const u8{}; - inline for (args) |arg| { - cmd = cmd ++ [_][]const u8{arg}; - } - return cmd; -} - -const ListEntry = struct { []const u8, FileType }; - -fn load_file_types(comptime Namespace: type) []const ListEntry { - comptime switch (@typeInfo(Namespace)) { - .@"struct" => |info| { - var count = 0; - for (info.decls) |_| { - // @compileLog(decl.name, @TypeOf(@field(Namespace, decl.name))); - count += 1; - } - var construct_types: [count]ListEntry = undefined; - var i = 0; - for (info.decls) |decl| { - const lang = decl.name; - const args = @field(Namespace, lang); - construct_types[i] = .{ lang, .{ - .color = if (@hasField(@TypeOf(args), "color")) args.color else 0xffffff, - .icon = if (@hasField(@TypeOf(args), "icon")) args.icon else "󱀫", - .name = lang, - .description = args.description, - .lang_fn = if (@hasField(@TypeOf(args), "parser")) args.parser else get_parser(lang), - .extensions = vec(args.extensions), - .comment = args.comment, - .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, - } }; - i += 1; - } - const types = construct_types; - return &types; - }, - 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.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 deleted file mode 100644 index efb8ca2..0000000 --- a/src/syntax/src/file_types.zig +++ /dev/null @@ -1,609 +0,0 @@ -const file_type = @import("file_type.zig"); -const FirstLineMatch = file_type.FirstLineMatch; - -pub const agda = .{ - .description = "Agda", - .extensions = .{"agda"}, - .comment = "--", -}; - -pub const astro = .{ - .description = "Astro", - .icon = "", - .extensions = .{"astro"}, - .comment = "//", - .language_server = .{ "astro-ls", "--stdio" }, -}; - -pub const bash = .{ - .description = "Bash", - .color = 0x3e474a, - .icon = "󱆃", - .extensions = .{ "sh", "bash", ".profile" }, - .comment = "#", - .first_line_matches = FirstLineMatch{ .prefix = "#!", .content = "sh" }, - .formatter = .{ "shfmt", "--indent", "4" }, - .language_server = .{ "bash-language-server", "start" }, -}; - -pub const c = .{ - .description = "C", - .icon = "", - .extensions = .{"c"}, - .comment = "//", - .formatter = .{"clang-format"}, - .language_server = .{"clangd"}, -}; - -pub const @"c-sharp" = .{ - .description = "C#", - .color = 0x68217a, - .icon = "󰌛", - .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" }, - .highlights = fish.highlights, - .comment = "#", - .parser = fish.parser, -}; - -pub const cmake = .{ - .description = "CMake", - .color = 0x004078, - .icon = "", - .extensions = .{ "CMakeLists.txt", "cmake", "cmake.in" }, - .comment = "#", - .highlights = "queries/cmake/highlights.scm", - .injections = "queries/cmake/injections.scm", - .formatter = .{"cmake-format"}, - .language_server = .{"cmake-language-server"}, -}; - -pub const cpp = .{ - .description = "C++", - .color = 0x9c033a, - .icon = "", - .extensions = .{ "cc", "cpp", "cxx", "hpp", "hxx", "h", "ipp", "ixx" }, - .comment = "//", - .highlights_list = .{ - "tree-sitter-c/queries/highlights.scm", - "tree-sitter-cpp/queries/highlights.scm", - }, - .injections = "tree-sitter-cpp/queries/injections.scm", - .formatter = .{"clang-format"}, - .language_server = .{"clangd"}, -}; - -pub const css = .{ - .description = "CSS", - .color = 0x3d8fc6, - .icon = "󰌜", - .extensions = .{"css"}, - .comment = "//", - .language_server = .{ "vscode-css-language-server", "--stdio" }, -}; - -pub const diff = .{ - .description = "Diff", - .extensions = .{ "diff", "patch", "rej" }, - .comment = "#", -}; - -pub const dockerfile = .{ - .description = "Docker", - .color = 0x019bc6, - .icon = "", - .extensions = .{ "Dockerfile", "dockerfile", "docker", "Containerfile", "container" }, - .comment = "#", -}; - -pub const dtd = .{ - .description = "DTD", - .icon = "󰗀", - .extensions = .{"dtd"}, - .comment = "