Merge branch 'master' into zig-0.15.0

This commit is contained in:
CJ van den Berg 2025-08-22 13:12:37 +02:00
commit 55a862eac0
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
47 changed files with 1610 additions and 2504 deletions

View file

@ -5,7 +5,10 @@
.fingerprint = 0x52c0d670590aa80f, .fingerprint = 0x52c0d670590aa80f,
.dependencies = .{ .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 = .{ .flags = .{
.url = "https://github.com/n0s4/flags/archive/372501d1576b5723829bcba98e41361132c7b618.tar.gz", .url = "https://github.com/n0s4/flags/archive/372501d1576b5723829bcba98e41361132c7b618.tar.gz",
.hash = "flags-0.8.0-AAAAAJV0AACuGBBnpUnHqZzAhoGTp4ibFROBQQQZGRqx", .hash = "flags-0.8.0-AAAAAJV0AACuGBBnpUnHqZzAhoGTp4ibFROBQQQZGRqx",

View file

@ -3,7 +3,6 @@ const tp = @import("thespian");
const cbor = @import("cbor"); const cbor = @import("cbor");
const root = @import("root"); const root = @import("root");
const tracy = @import("tracy"); const tracy = @import("tracy");
const log = @import("log");
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
pid: tp.pid, pid: tp.pid,
@ -172,6 +171,7 @@ const Process = struct {
project: [:0]const u8, project: [:0]const u8,
sp_tag: [:0]const u8, sp_tag: [:0]const u8,
log_file: ?std.fs.File = null, log_file: ?std.fs.File = null,
log_file_path: ?[]const u8 = null,
next_id: i32 = 0, next_id: i32 = 0,
requests: std.StringHashMap(tp.pid), requests: std.StringHashMap(tp.pid),
state: enum { init, running } = .init, 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 if (try cbor.match(cmd.buf, .{ tp.extract(&tag), tp.more })) {
// //
} else { } else {
const logger = log.logger("LSP");
defer logger.deinit();
var buf: [1024]u8 = undefined; 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; return error.InvalidLspCommand;
} }
const self = try allocator.create(Process); const self = try allocator.create(Process);
@ -227,6 +225,7 @@ const Process = struct {
self.close() catch {}; self.close() catch {};
self.write_log("### terminated LSP process ###\n", .{}); self.write_log("### terminated LSP process ###\n", .{});
if (self.log_file) |file| file.close(); 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 { 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 { fn start(self: *Process) tp.result {
const frame = tracy.initZone(@src(), .{ .name = module_name ++ " start" }); const frame = tracy.initZone(@src(), .{ .name = module_name ++ " start" });
defer frame.deinit(); defer frame.deinit();
@ -255,8 +268,9 @@ const Process = struct {
var log_file_path = std.ArrayList(u8).init(self.allocator); var log_file_path = std.ArrayList(u8).init(self.allocator);
defer log_file_path.deinit(); defer log_file_path.deinit();
const state_dir = root.get_state_dir() catch |e| return tp.exit_error(e, @errorReturnTrace()); 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 = 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 { 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 { fn handle_not_found(self: *Process) error{ExitNormal}!void {
const logger = log.logger("LSP"); self.err_msg("'{s}' executable not found", .{self.tag});
defer logger.deinit();
logger.print_err("init", "'{s}' executable not found", .{self.tag});
self.write_log("### '{s}' executable not found ###\n", .{self.tag}); self.write_log("### '{s}' executable not found ###\n", .{self.tag});
self.parent.send(.{ sp_tag, self.tag, "not found" }) catch {}; self.parent.send(.{ sp_tag, self.tag, "not found" }) catch {};
return error.ExitNormal; return error.ExitNormal;
} }
fn handle_terminated(self: *Process, err: []const u8, code: u32) error{ExitNormal}!void { fn handle_terminated(self: *Process, err: []const u8, code: u32) error{ExitNormal}!void {
const logger = log.logger("LSP"); self.msg("terminated: {s} {d}", .{ err, code });
defer logger.deinit();
logger.print("terminated: {s} {d}", .{ err, code });
self.write_log("### subprocess terminated {s} {d} ###\n", .{ err, code }); self.write_log("### subprocess terminated {s} {d} ###\n", .{ err, code });
self.parent.send(.{ sp_tag, self.tag, "done" }) catch {}; self.parent.send(.{ sp_tag, self.tag, "done" }) catch {};
return error.ExitNormal; return error.ExitNormal;
@ -463,9 +473,9 @@ const Process = struct {
const id = self.next_id; const id = self.next_id;
self.next_id += 1; self.next_id += 1;
var msg = std.ArrayList(u8).init(self.allocator); var request = std.ArrayList(u8).init(self.allocator);
defer msg.deinit(); defer request.deinit();
const msg_writer = msg.writer(); const msg_writer = request.writer();
try cbor.writeMapHeader(msg_writer, 4); try cbor.writeMapHeader(msg_writer, 4);
try cbor.writeValue(msg_writer, "jsonrpc"); try cbor.writeValue(msg_writer, "jsonrpc");
try cbor.writeValue(msg_writer, "2.0"); try cbor.writeValue(msg_writer, "2.0");
@ -476,7 +486,7 @@ const Process = struct {
try cbor.writeValue(msg_writer, "params"); try cbor.writeValue(msg_writer, "params");
_ = try msg_writer.write(params_cb); _ = 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); defer self.allocator.free(json);
var output = std.ArrayList(u8).init(self.allocator); var output = std.ArrayList(u8).init(self.allocator);
defer output.deinit(); 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 { 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; const sp = if (self.sp) |*sp| sp else return error.Closed;
var msg = std.ArrayList(u8).init(self.allocator); var response = std.ArrayList(u8).init(self.allocator);
defer msg.deinit(); defer response.deinit();
const msg_writer = msg.writer(); const msg_writer = response.writer();
try cbor.writeMapHeader(msg_writer, 3); try cbor.writeMapHeader(msg_writer, 3);
try cbor.writeValue(msg_writer, "jsonrpc"); try cbor.writeValue(msg_writer, "jsonrpc");
try cbor.writeValue(msg_writer, "2.0"); try cbor.writeValue(msg_writer, "2.0");
@ -510,7 +520,7 @@ const Process = struct {
try cbor.writeValue(msg_writer, "result"); try cbor.writeValue(msg_writer, "result");
_ = try msg_writer.write(result_cb); _ = 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); defer self.allocator.free(json);
var output = std.ArrayList(u8).init(self.allocator); var output = std.ArrayList(u8).init(self.allocator);
defer output.deinit(); 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 { 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; const sp = if (self.sp) |*sp| sp else return error.Closed;
var msg = std.ArrayList(u8).init(self.allocator); var response = std.ArrayList(u8).init(self.allocator);
defer msg.deinit(); defer response.deinit();
const msg_writer = msg.writer(); const msg_writer = response.writer();
try cbor.writeMapHeader(msg_writer, 3); try cbor.writeMapHeader(msg_writer, 3);
try cbor.writeValue(msg_writer, "jsonrpc"); try cbor.writeValue(msg_writer, "jsonrpc");
try cbor.writeValue(msg_writer, "2.0"); 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");
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); defer self.allocator.free(json);
var output = std.ArrayList(u8).init(self.allocator); var output = std.ArrayList(u8).init(self.allocator);
defer output.deinit(); defer output.deinit();
@ -563,9 +573,9 @@ const Process = struct {
const have_params = !(cbor.match(params_cb, cbor.null_) catch false); const have_params = !(cbor.match(params_cb, cbor.null_) catch false);
var msg = std.ArrayList(u8).init(self.allocator); var notification = std.ArrayList(u8).init(self.allocator);
defer msg.deinit(); defer notification.deinit();
const msg_writer = msg.writer(); const msg_writer = notification.writer();
try cbor.writeMapHeader(msg_writer, 3); try cbor.writeMapHeader(msg_writer, 3);
try cbor.writeValue(msg_writer, "jsonrpc"); try cbor.writeValue(msg_writer, "jsonrpc");
try cbor.writeValue(msg_writer, "2.0"); try cbor.writeValue(msg_writer, "2.0");
@ -578,7 +588,7 @@ const Process = struct {
try cbor.writeMapHeader(msg_writer, 0); 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); defer self.allocator.free(json);
var output = std.ArrayList(u8).init(self.allocator); var output = std.ArrayList(u8).init(self.allocator);
defer output.deinit(); defer output.deinit();
@ -617,9 +627,9 @@ const Process = struct {
const json = if (params) |p| try cbor.toJsonPrettyAlloc(self.allocator, p) else null; const json = if (params) |p| try cbor.toJsonPrettyAlloc(self.allocator, p) else null;
defer if (json) |p| self.allocator.free(p); 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" }); 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); var request = std.ArrayList(u8).init(self.allocator);
defer msg.deinit(); defer request.deinit();
const writer = msg.writer(); const writer = request.writer();
try cbor.writeArrayHeader(writer, 7); try cbor.writeArrayHeader(writer, 7);
try cbor.writeValue(writer, sp_tag); try cbor.writeValue(writer, sp_tag);
try cbor.writeValue(writer, self.project); try cbor.writeValue(writer, self.project);
@ -628,7 +638,7 @@ const Process = struct {
try cbor.writeValue(writer, method); try cbor.writeValue(writer, method);
try writer.writeAll(cbor_id); try writer.writeAll(cbor_id);
if (params) |p| _ = try writer.write(p) else try cbor.writeValue(writer, null); 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 { 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); 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" }); 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; const from = self.requests.get(cbor_id) orelse return;
var msg = std.ArrayList(u8).init(self.allocator); var response = std.ArrayList(u8).init(self.allocator);
defer msg.deinit(); defer response.deinit();
const writer = msg.writer(); const writer = response.writer();
try cbor.writeArrayHeader(writer, 4); try cbor.writeArrayHeader(writer, 4);
try cbor.writeValue(writer, sp_tag); try cbor.writeValue(writer, sp_tag);
try cbor.writeValue(writer, self.tag); try cbor.writeValue(writer, self.tag);
@ -653,16 +663,16 @@ const Process = struct {
try cbor.writeValue(writer, "result"); try cbor.writeValue(writer, "result");
_ = try writer.write(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 { 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; const json = if (params) |p| try cbor.toJsonPrettyAlloc(self.allocator, p) else null;
defer if (json) |p| self.allocator.free(p); defer if (json) |p| self.allocator.free(p);
self.write_log("### RECV notify:\nmethod: {s}\n{s}\n###\n", .{ method, json orelse "no params" }); self.write_log("### RECV notify:\nmethod: {s}\n{s}\n###\n", .{ method, json orelse "no params" });
var msg = std.ArrayList(u8).init(self.allocator); var notification = std.ArrayList(u8).init(self.allocator);
defer msg.deinit(); defer notification.deinit();
const writer = msg.writer(); const writer = notification.writer();
try cbor.writeArrayHeader(writer, 6); try cbor.writeArrayHeader(writer, 6);
try cbor.writeValue(writer, sp_tag); try cbor.writeValue(writer, sp_tag);
try cbor.writeValue(writer, self.project); try cbor.writeValue(writer, self.project);
@ -670,7 +680,7 @@ const Process = struct {
try cbor.writeValue(writer, "notify"); try cbor.writeValue(writer, "notify");
try cbor.writeValue(writer, method); try cbor.writeValue(writer, method);
if (params) |p| _ = try writer.write(p) else try cbor.writeValue(writer, null); 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 { fn write_log(self: *Process, comptime format: []const u8, args: anytype) void {

View file

@ -8,6 +8,7 @@ const Buffer = @import("Buffer");
const fuzzig = @import("fuzzig"); const fuzzig = @import("fuzzig");
const tracy = @import("tracy"); const tracy = @import("tracy");
const git = @import("git"); const git = @import("git");
const file_type_config = @import("file_type_config");
const builtin = @import("builtin"); const builtin = @import("builtin");
const LSP = @import("LSP.zig"); const LSP = @import("LSP.zig");
@ -52,6 +53,9 @@ pub const LspOrClientError = (LspError || ClientError);
const File = struct { const File = struct {
path: []const u8, path: []const u8,
type: []const u8,
icon: []const u8,
color: u24,
mtime: i128, mtime: i128,
pos: FilePos = .{}, pos: FilePos = .{},
visited: bool = false, 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 { 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 {}; defer from.send(.{ "PRJ", "recent_done", self.longest_file_path, "" }) catch {};
for (self.files.items, 0..) |file, i| { 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; 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); defer self.allocator.free(matches);
var n: usize = 0; var n: usize = 0;
while (n < query.len) : (n += 1) matches[n] = idx + n; 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; i += 1;
if (i >= max) return i; 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 { const Match = struct {
path: []const u8, path: []const u8,
type: []const u8,
icon: []const u8,
color: u24,
score: i32, score: i32,
matches: []const usize, 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| { if (match.score) |score| {
(try matches.addOne()).* = .{ (try matches.addOne()).* = .{
.path = file.path, .path = file.path,
.type = file.type,
.icon = file.icon,
.color = file.color,
.score = score, .score = score,
.matches = try self.allocator.dupe(usize, match.matches), .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); std.mem.sort(Match, matches.items, {}, less_fn);
for (matches.items[0..@min(max, matches.items.len)]) |match| 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); 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); 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 { 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); 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 { fn merge_pending_files(self: *Self) OutOfMemoryError!void {
defer self.sort_files_by_mtime(); defer self.sort_files_by_mtime();
const existing = try self.files.toOwnedSlice(self.allocator); 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; return;
} }
const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(file_path);
if (row != 0) { if (row != 0) {
(try self.files.addOne(self.allocator)).* = .{ (try self.files.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, file_path), .path = try self.allocator.dupe(u8, file_path),
.type = file_type,
.icon = file_icon,
.color = file_color,
.mtime = mtime, .mtime = mtime,
.pos = .{ .row = row, .col = col }, .pos = .{ .row = row, .col = col },
.visited = true, .visited = true,
@ -479,6 +533,9 @@ fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usi
} else { } else {
(try self.files.addOne(self.allocator)).* = .{ (try self.files.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, file_path), .path = try self.allocator.dupe(u8, file_path),
.type = file_type,
.icon = file_icon,
.color = file_color,
.mtime = mtime, .mtime = mtime,
}; };
} }
@ -1473,7 +1530,16 @@ fn read_position(position: []const u8) !Position {
return .{ .line = line.?, .character = character.? }; 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 type_: i32 = 0;
var message: ?[]const u8 = null; var message: ?[]const u8 = null;
var iter = params_cb; 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) if (type_ <= 2)
self.logger_lsp.err_msg("lsp", msg) self.logger_lsp.err_msg("lsp", msg)
else 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 { 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) })) { } 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); self.longest_file_path = @max(self.longest_file_path, path.len);
const stat = std.fs.cwd().statFile(path) catch return; 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_ })) { } else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.null_ })) {
self.state.workspace_files = .done; self.state.workspace_files = .done;
try self.loaded(parent); try self.loaded(parent);

View file

@ -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); return self.buffers.get(file_path);
} }

View file

@ -33,6 +33,7 @@ const Vtable = struct {
pub const Metadata = struct { pub const Metadata = struct {
description: []const u8 = &[_]u8{}, description: []const u8 = &[_]u8{},
arguments: []const ArgumentType = &[_]ArgumentType{}, arguments: []const ArgumentType = &[_]ArgumentType{},
icon: ?[]const u8 = null,
}; };
pub const ArgumentType = enum { 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; 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(.{ const suppressed_errors = std.StaticStringMap(void).initComptime(.{
.{ "enable_fast_scroll", void }, .{ "enable_fast_scroll", void },
.{ "disable_fast_scroll", void }, .{ "disable_fast_scroll", void },

View file

@ -36,6 +36,13 @@ show_fileicons: bool = true,
start_debugger_on_crash: bool = false, 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 = "", include_files: []const u8 = "",
pub const DigitStyle = enum { pub const DigitStyle = enum {
@ -56,3 +63,25 @@ pub const IndentMode = enum {
spaces, spaces,
tabs, 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,
};

View file

@ -20,6 +20,8 @@ pub const default = struct {
pub const color = 0x000000; pub const color = 0x000000;
}; };
pub const folder_icon = "";
fn from_file_type(file_type: syntax.FileType) @This() { fn from_file_type(file_type: syntax.FileType) @This() {
return .{ return .{
.name = file_type.name, .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; 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; break :self file_type;
}; };
} }

View file

@ -1,6 +1,7 @@
{ {
"project": { "project": {
"press": [ "press": [
["ctrl+alt+shift+r", "restart"],
["ctrl+e", "find_file"], ["ctrl+e", "find_file"],
["ctrl+shift+n", "create_new_file"], ["ctrl+shift+n", "create_new_file"],
["ctrl+r", "open_recent_project"], ["ctrl+r", "open_recent_project"],
@ -28,7 +29,8 @@
["f10", "theme_next"], ["f10", "theme_next"],
["f11", "toggle_panel"], ["f11", "toggle_panel"],
["f12", "toggle_inputview"], ["f12", "toggle_inputview"],
["alt+!", "select_task"], ["alt+!", "run_task"],
["ctrl+1", "add_task"],
["ctrl+tab", "next_tab"], ["ctrl+tab", "next_tab"],
["ctrl+shift+tab", "previous_tab"], ["ctrl+shift+tab", "previous_tab"],
["ctrl+shift+e", "switch_buffers"], ["ctrl+shift+e", "switch_buffers"],
@ -38,6 +40,7 @@
["f5", ["create_scratch_buffer", "*test*"], ["shell_execute_insert", "zig", "build", "test"]], ["f5", ["create_scratch_buffer", "*test*"], ["shell_execute_insert", "zig", "build", "test"]],
["f7", ["create_scratch_buffer", "*build*"], ["shell_execute_insert", "zig", "build"]], ["f7", ["create_scratch_buffer", "*build*"], ["shell_execute_insert", "zig", "build"]],
["ctrl+f6", "open_version_info"], ["ctrl+f6", "open_version_info"],
["alt+shift+t", "set_session_tab_width"],
["alt+d", ["shell_execute_insert", "date", "--iso-8601"]], ["alt+d", ["shell_execute_insert", "date", "--iso-8601"]],
["ctrl+alt+shift+d", ["shell_execute_insert", "date", "--iso-8601=seconds"]] ["ctrl+alt+shift+d", ["shell_execute_insert", "date", "--iso-8601=seconds"]]
] ]
@ -162,6 +165,7 @@
["shift+f11", "toggle_highlight_columns"], ["shift+f11", "toggle_highlight_columns"],
["ctrl+f11", "toggle_inspector_view"], ["ctrl+f11", "toggle_inspector_view"],
["f12", "goto_definition"], ["f12", "goto_definition"],
["ctrl+.", "completion"],
["f34", "toggle_whitespace_mode"], ["f34", "toggle_whitespace_mode"],
["escape", "cancel"], ["escape", "cancel"],
["enter", "smart_insert_line"], ["enter", "smart_insert_line"],
@ -236,6 +240,8 @@
["page_down", "select_page_down"], ["page_down", "select_page_down"],
["ctrl+page_up", "select_scroll_page_up"], ["ctrl+page_up", "select_scroll_page_up"],
["ctrl+page_down", "select_scroll_page_down"], ["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+space", "enter_mode", "normal"],
["ctrl+x", ["cut"], ["enter_mode", "normal"], ["cancel"]], ["ctrl+x", ["cut"], ["enter_mode", "normal"], ["cancel"]],
["ctrl+c", ["copy"], ["enter_mode", "normal"], ["cancel"]], ["ctrl+c", ["copy"], ["enter_mode", "normal"], ["cancel"]],
@ -249,6 +255,7 @@
"inherit": "project", "inherit": "project",
"on_match_failure": "ignore", "on_match_failure": "ignore",
"press": [ "press": [
["alt+f9", "home_next_widget_style"],
["ctrl+e", "find_file"], ["ctrl+e", "find_file"],
["f", "find_file"], ["f", "find_file"],
["e", "find_file"], ["e", "find_file"],
@ -279,9 +286,12 @@
}, },
"overlay/palette": { "overlay/palette": {
"press": [ "press": [
["alt+f9", "overlay_next_widget_style"],
["alt+!", "add_task"],
["ctrl+j", "toggle_panel"], ["ctrl+j", "toggle_panel"],
["ctrl+q", "quit"], ["ctrl+q", "quit"],
["ctrl+w", "close_file"], ["ctrl+w", "close_file"],
["ctrl+shift+f", "find_in_files"],
["ctrl+p", "palette_menu_up"], ["ctrl+p", "palette_menu_up"],
["ctrl+n", "palette_menu_down"], ["ctrl+n", "palette_menu_down"],
["ctrl+e", "palette_menu_down"], ["ctrl+e", "palette_menu_down"],
@ -328,7 +338,7 @@
["right_control", "palette_menu_activate_quick"] ["right_control", "palette_menu_activate_quick"]
] ]
}, },
"mini/goto": { "mini/numeric": {
"press": [ "press": [
["ctrl+q", "quit"], ["ctrl+q", "quit"],
["ctrl+v", "system_paste"], ["ctrl+v", "system_paste"],
@ -338,7 +348,7 @@
["ctrl+l", "scroll_view_center_cycle"], ["ctrl+l", "scroll_view_center_cycle"],
["ctrl+space", "mini_mode_cancel"], ["ctrl+space", "mini_mode_cancel"],
["escape", "mini_mode_cancel"], ["escape", "mini_mode_cancel"],
["enter", "exit_mini_mode"], ["enter", "mini_mode_select"],
["backspace", "mini_mode_delete_backwards"] ["backspace", "mini_mode_delete_backwards"]
] ]
}, },
@ -347,6 +357,8 @@
["ctrl+g", "mini_mode_cancel"], ["ctrl+g", "mini_mode_cancel"],
["ctrl+c", "mini_mode_cancel"], ["ctrl+c", "mini_mode_cancel"],
["ctrl+l", "scroll_view_center_cycle"], ["ctrl+l", "scroll_view_center_cycle"],
["tab", "mini_mode_insert_bytes", "\t"],
["enter", "mini_mode_insert_bytes", "\n"],
["escape", "mini_mode_cancel"], ["escape", "mini_mode_cancel"],
["backspace", "mini_mode_cancel"] ["backspace", "mini_mode_cancel"]
] ]

View file

@ -11,6 +11,8 @@ subscriber: ?tp.pid,
heap: [32 + 1024]u8, heap: [32 + 1024]u8,
fba: std.heap.FixedBufferAllocator, fba: std.heap.FixedBufferAllocator,
msg_store: MsgStore, msg_store: MsgStore,
no_stdout: bool = false,
no_stderr: bool = false,
const MsgStore = std.DoublyLinkedList; const MsgStore = std.DoublyLinkedList;
const MsgStoreEntry = struct { 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 { fn receive(self: *Self, from: tp.pid_ref, m: tp.message) tp.result {
errdefer self.deinit(); 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| { if (self.subscriber) |subscriber| {
subscriber.send_raw(m) catch {}; subscriber.send_raw(m) catch {};
} else { } else {
self.store(m); 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"})) { } else if (try m.match(.{"subscribe"})) {
// log("subscribed"); // log("subscribed");
if (self.subscriber) |*s| s.deinit(); 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(); if (self.subscriber) |*s| s.deinit();
self.subscriber = null; self.subscriber = null;
self.store_reset(); 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"})) { } else if (try m.match(.{"shutdown"})) {
return tp.exit_normal(); return tp.exit_normal();
} }
@ -208,6 +229,14 @@ pub fn unsubscribe() tp.result {
return tp.env.get().proc("log").send(.{"unsubscribe"}); 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; var std_log_pid: ?tp.pid_ref = null;
pub fn set_std_log_pid(pid: ?tp.pid_ref) void { pub fn set_std_log_pid(pid: ?tp.pid_ref) void {

View file

@ -6,6 +6,7 @@ const color = @import("color");
const flags = @import("flags"); const flags = @import("flags");
const builtin = @import("builtin"); const builtin = @import("builtin");
const bin_path = @import("bin_path"); const bin_path = @import("bin_path");
const sep = std.fs.path.sep;
const list_languages = @import("list_languages.zig"); const list_languages = @import("list_languages.zig");
pub const file_link = @import("file_link.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.writeValue(writer, cmd_);
try cbor.writeArrayHeader(writer, count - 1); 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 }); 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; const a = std.heap.c_allocator;
var path = std.ArrayList(u8).init(a); var path = std.ArrayList(u8).init(a);
defer path.deinit(); 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; const file = std.fs.createFileAbsolute(path.items, .{ .truncate = true }) catch return;
State.state = .{ State.state = .{
.file = file, .file = file,
@ -502,12 +508,12 @@ pub fn parse_text_config_file(T: type, allocator: std.mem.Allocator, conf: *T, b
lineno += 1; lineno += 1;
if (line.len == 0 or line[0] == '#') if (line.len == 0 or line[0] == '#')
continue; 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 }); std.log.err("{s}:{}: {s} missing value", .{ file_name, lineno, line });
continue; continue;
}; };
const name = line[0..sep]; const name = line[0..spc];
const value_str = line[sep + 1 ..]; const value_str = line[spc + 1 ..];
const cb = cbor.fromJsonAlloc(allocator, value_str) catch { const cb = cbor.fromJsonAlloc(allocator, value_str) catch {
std.log.err("{s}:{}: {s} has bad value: {s}", .{ file_name, lineno, name, value_str }); std.log.err("{s}:{}: {s} has bad value: {s}", .{ file_name, lineno, name, value_str });
continue; continue;
@ -781,6 +787,11 @@ pub const ConfigDirError = error{
AppConfigDirUnavailable, 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 { fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 {
const a = std.heap.c_allocator; const a = std.heap.c_allocator;
const local = struct { const local = struct {
@ -791,22 +802,22 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 {
dir dir
else if (std.process.getEnvVarOwned(a, "XDG_CONFIG_HOME") catch null) |xdg| ret: { else if (std.process.getEnvVarOwned(a, "XDG_CONFIG_HOME") catch null) |xdg| ret: {
defer a.free(xdg); 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: { } else if (std.process.getEnvVarOwned(a, "HOME") catch null) |home| ret: {
defer a.free(home); 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) { std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {}, 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: { } else if (builtin.os.tag == .windows) ret: {
if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| { if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| {
defer a.free(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) { std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {}, error.PathAlreadyExists => {},
else => return error.MakeAppConfigDirFailed, else => return make_dir_error(dir, error.MakeAppConfigDirFailed),
}; };
break :ret dir; break :ret dir;
} else return error.AppConfigDirUnavailable; } else return error.AppConfigDirUnavailable;
@ -815,14 +826,14 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 {
local.config_dir = config_dir; local.config_dir = config_dir;
std.fs.makeDirAbsolute(config_dir) catch |e| switch (e) { std.fs.makeDirAbsolute(config_dir) catch |e| switch (e) {
error.PathAlreadyExists => {}, 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; 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; 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; return config_dir;
} }
@ -841,22 +852,22 @@ fn get_app_cache_dir(appname: []const u8) ![]const u8 {
dir dir
else if (std.process.getEnvVarOwned(a, "XDG_CACHE_HOME") catch null) |xdg| ret: { else if (std.process.getEnvVarOwned(a, "XDG_CACHE_HOME") catch null) |xdg| ret: {
defer a.free(xdg); 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: { } else if (std.process.getEnvVarOwned(a, "HOME") catch null) |home| ret: {
defer a.free(home); 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) { std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {}, 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: { } else if (builtin.os.tag == .windows) ret: {
if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| { if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| {
defer a.free(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) { std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {}, error.PathAlreadyExists => {},
else => return e, else => return make_dir_error(dir, e),
}; };
break :ret dir; break :ret dir;
} else return error.AppCacheDirUnavailable; } else return error.AppCacheDirUnavailable;
@ -865,7 +876,7 @@ fn get_app_cache_dir(appname: []const u8) ![]const u8 {
local.cache_dir = cache_dir; local.cache_dir = cache_dir;
std.fs.makeDirAbsolute(cache_dir) catch |e| switch (e) { std.fs.makeDirAbsolute(cache_dir) catch |e| switch (e) {
error.PathAlreadyExists => {}, error.PathAlreadyExists => {},
else => return e, else => return make_dir_error(cache_dir, e),
}; };
return cache_dir; return cache_dir;
} }
@ -884,27 +895,27 @@ fn get_app_state_dir(appname: []const u8) ![]const u8 {
dir dir
else if (std.process.getEnvVarOwned(a, "XDG_STATE_HOME") catch null) |xdg| ret: { else if (std.process.getEnvVarOwned(a, "XDG_STATE_HOME") catch null) |xdg| ret: {
defer a.free(xdg); 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: { } else if (std.process.getEnvVarOwned(a, "HOME") catch null) |home| ret: {
defer a.free(home); 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) { std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {}, 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) { std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {}, 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: { } else if (builtin.os.tag == .windows) ret: {
if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| { if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| {
defer a.free(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) { std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {}, error.PathAlreadyExists => {},
else => return e, else => return make_dir_error(dir, e),
}; };
break :ret dir; break :ret dir;
} else return error.AppCacheDirUnavailable; } else return error.AppCacheDirUnavailable;
@ -913,7 +924,7 @@ fn get_app_state_dir(appname: []const u8) ![]const u8 {
local.state_dir = state_dir; local.state_dir = state_dir;
std.fs.makeDirAbsolute(state_dir) catch |e| switch (e) { std.fs.makeDirAbsolute(state_dir) catch |e| switch (e) {
error.PathAlreadyExists => {}, error.PathAlreadyExists => {},
else => return e, else => return make_dir_error(state_dir, e),
}; };
return state_dir; return state_dir;
} }
@ -926,7 +937,7 @@ fn get_app_config_dir_file_name(appname: []const u8, comptime config_file_name:
const local = struct { const local = struct {
var config_file_buffer: [std.posix.PATH_MAX]u8 = undefined; 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 { 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| const restore_file = if (local.restore_file) |file|
file file
else 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; local.restore_file = restore_file;
return restore_file; return restore_file;
} }
@ -958,7 +969,7 @@ fn get_keybind_namespaces_directory() ![]const u8 {
defer a.free(dir); defer a.free(dir);
return try std.fmt.bufPrint(&local.dir_buffer, "{s}", .{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 { 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 { const local = struct {
var file_buffer: [std.posix.PATH_MAX]u8 = undefined; 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"; const theme_dir = "themes";
@ -980,7 +991,7 @@ fn get_theme_directory() ![]const u8 {
defer a.free(dir); defer a.free(dir);
return try std.fmt.bufPrint(&local.dir_buffer, "{s}", .{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 { 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 { const local = struct {
var file_buffer: [std.posix.PATH_MAX]u8 = undefined; 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 { fn restart() noreturn {

View file

@ -27,7 +27,7 @@ const OutOfMemoryError = error{OutOfMemory};
const FileSystemError = error{FileSystem}; const FileSystemError = error{FileSystem};
const SetCwdError = if (builtin.os.tag == .windows) error{UnrecognizedVolume} else error{}; const SetCwdError = if (builtin.os.tag == .windows) error{UnrecognizedVolume} else error{};
const CallError = tp.CallError; const CallError = tp.CallError;
const ProjectManagerError = (SpawnError || error{ProjectManagerFailed}); const ProjectManagerError = (SpawnError || error{ ProjectManagerFailed, InvalidProjectDirectory });
pub fn get() SpawnError!Self { pub fn get() SpawnError!Self {
const pid = tp.env.get().proc(module_name); 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 project_directory = std.fs.cwd().realpath(rel_project_directory, &path_buf) catch "(none)";
const current_project = tp.env.get().str("project"); const current_project = tp.env.get().str("project");
if (std.mem.eql(u8, current_project, project_directory)) return; 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, .{}); var dir = try std.fs.openDirAbsolute(project_directory, .{});
try dir.setAsCwd(); try dir.setAsCwd();
dir.close(); dir.close();
@ -97,9 +98,9 @@ pub fn request_recent_files(max: usize) (ProjectManagerError || ProjectError)!vo
return send(.{ "request_recent_files", project, max }); 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"); 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 { 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 n: usize = 0;
var task: []const u8 = undefined; var task: []const u8 = undefined;
var context: usize = undefined; var context: usize = undefined;
var tag: []const u8 = undefined;
var message: []const u8 = undefined;
var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf); 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; 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) })) { } 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; 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"})) { } else if (try cbor.match(m.buf, .{"shutdown"})) {
self.persist_projects(); self.persist_projects();
from.send(.{ "project_manager", "shutdown" }) catch return error.ClientFailed; from.send(.{ "project_manager", "shutdown" }) catch return error.ClientFailed;
@ -461,6 +469,9 @@ const Process = struct {
self.sort_projects_by_last_used(&recent_projects); self.sort_projects_by_last_used(&recent_projects);
var message = std.ArrayList(u8).init(self.allocator); var message = std.ArrayList(u8).init(self.allocator);
const writer = message.writer(); 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); try cbor.writeArrayHeader(writer, recent_projects.items.len);
for (recent_projects.items) |project| { for (recent_projects.items) |project| {
try cbor.writeArrayHeader(writer, 2); 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); try cbor.writeValue(writer, if (self.projects.get(project.name)) |_| true else false);
} }
from.send_raw(.{ .buf = message.items }) catch return error.ClientFailed; 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 { 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")) return if (std.mem.eql(u8, method, "textDocument/publishDiagnostics"))
project.publish_diagnostics(self.parent.ref(), params_cb) project.publish_diagnostics(self.parent.ref(), params_cb)
else if (std.mem.eql(u8, method, "window/showMessage")) 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")) else if (std.mem.eql(u8, method, "window/logMessage"))
project.show_message(self.parent.ref(), params_cb) project.log_message(params_cb)
else { else
const params = try cbor.toJsonAlloc(self.allocator, params_cb); project.show_notification(method, params_cb);
defer self.allocator.free(params);
self.logger.print("LSP notification: {s} -> {s}", .{ method, params });
};
} }
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 { 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(); var iter = self.dir.iterateAssumeFirstIteration();
errdefer |e| self.parent.send(.{ "PRJ", "path_error", self.project_name, self.path, e }) catch {}; errdefer |e| self.parent.send(.{ "PRJ", "path_error", self.project_name, self.path, e }) catch {};
while (try iter.next()) |entry| { while (try iter.next()) |entry| {
switch (entry.kind) { const event_type = switch (entry.kind) {
.directory => try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, "DIR", entry.name }), .directory => "DIR",
.sym_link => try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, "LINK", entry.name }), .sym_link => "LINK",
.file => try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, "FILE", entry.name }), .file => "FILE",
else => continue, 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; count += 1;
if (count >= self.max) break; if (count >= self.max) break;
} }

View file

@ -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 => { .focus_in => {
if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{"focus_in"})); if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{"focus_in"}));
}, },

View file

@ -1 +0,0 @@
/.zig-cache/

View file

@ -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.

View file

@ -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)

View file

@ -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);
}

View file

@ -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",
},
}

View file

@ -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 };
}

View file

@ -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;
}

View file

@ -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 = "<!--",
.highlights = "tree-sitter-xml/queries/dtd/highlights.scm",
};
pub const elixir = .{
.description = "Elixir",
.color = 0x4e2a8e,
.icon = "",
.extensions = .{ "ex", "exs" },
.comment = "#",
.injections = "tree-sitter-elixir/queries/injections.scm",
.formatter = .{ "mix", "format", "-" },
.language_server = .{"elixir-ls"},
};
pub const fish = .{
.description = "Fish",
.extensions = .{"fish"},
.comment = "#",
.parser = @import("file_type.zig").Parser("fish"),
.highlights = "tree-sitter-fish/queries/highlights.scm",
};
pub const @"git-rebase" = .{
.description = "Git (rebase)",
.color = 0xf34f29,
.icon = "",
.extensions = .{"git-rebase-todo"},
.comment = "#",
};
pub const gitcommit = .{
.description = "Git (commit)",
.color = 0xf34f29,
.icon = "",
.extensions = .{"COMMIT_EDITMSG"},
.comment = "#",
.injections = "tree-sitter-gitcommit/queries/injections.scm",
};
pub const gleam = .{
.description = "Gleam",
.color = 0xffaff3,
.icon = "󰦥",
.extensions = .{"gleam"},
.comment = "//",
.language_server = .{ "gleam", "lsp" },
.formatter = .{ "gleam", "format", "--stdin" },
};
pub const go = .{
.description = "Go",
.color = 0x00acd7,
.icon = "󰟓",
.extensions = .{"go"},
.comment = "//",
.language_server = .{"gopls"},
.formatter = .{"gofmt"},
};
pub const hare = .{
.description = "Hare",
.extensions = .{"ha"},
.comment = "//",
};
pub const haskell = .{
.description = "Haskell",
.color = 0x5E5185,
.icon = "󰲒",
.extensions = .{"hs"},
.comment = "--",
.language_server = .{ "haskell-language-server-wrapper", "lsp" },
};
pub const html = .{
.description = "HTML",
.color = 0xe54d26,
.icon = "󰌝",
.extensions = .{"html"},
.comment = "<!--",
.injections = "tree-sitter-html/queries/injections.scm",
.language_server = .{ "superhtml", "lsp" }, // https://github.com/kristoff-it/super-html.git
.formatter = .{ "superhtml", "fmt", "--stdin" },
};
pub const superhtml = .{
.description = "SuperHTML",
.color = 0xe54d26,
.icon = "󰌝",
.extensions = .{"shtml"},
.comment = "<!--",
.highlights = "tree-sitter-superhtml/tree-sitter-superhtml/queries/highlights.scm",
.injections = "tree-sitter-superhtml/tree-sitter-superhtml/queries/injections.scm",
.language_server = .{ "superhtml", "lsp" },
.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,
.icon = "",
.extensions = .{"java"},
.comment = "//",
};
pub const javascript = .{
.description = "JavaScript",
.color = 0xf0db4f,
.icon = "󰌞",
.extensions = .{"js"},
.comment = "//",
.injections = "tree-sitter-javascript/queries/injections.scm",
.language_server = .{ "typescript-language-server", "--stdio" },
.formatter = .{ "prettier", "--parser", "typescript" },
};
pub const json = .{
.description = "JSON",
.extensions = .{"json"},
.comment = "//",
.language_server = .{ "vscode-json-language-server", "--stdio" },
.formatter = .{ "prettier", "--parser", "json" },
};
pub const julia = .{
.description = "Julia",
.color = 0x4D64AE,
.icon = "",
.extensions = .{"jl"},
.comment = "#",
.language_server = .{ "julia", "-e", "using LanguageServer; runserver()" },
.formatter = .{ "julia", "-e", "using JuliaFormatter; print(format_text(read(stdin, String)))" },
};
pub const kdl = .{
.description = "KDL",
.color = 0x000000,
.icon = "",
.extensions = .{"kdl"},
.comment = "//",
};
pub const lua = .{
.description = "Lua",
.color = 0x02027d,
.icon = "󰢱",
.extensions = .{"lua"},
.comment = "--",
.injections = "tree-sitter-lua/queries/injections.scm",
.first_line_matches = FirstLineMatch{ .prefix = "--", .content = "lua" },
.language_server = .{"lua-lsp"},
};
pub const mail = .{
.description = "E-Mail",
.icon = "󰇮",
.extensions = .{ "eml", "mbox" },
.comment = ">",
.highlights = "tree-sitter-mail/queries/mail/highlights.scm",
.first_line_matches = FirstLineMatch{ .prefix = "From" },
};
pub const make = .{
.description = "Make",
.extensions = .{ "makefile", "Makefile", "MAKEFILE", "GNUmakefile", "mk", "mak", "dsp" },
.comment = "#",
};
pub const markdown = .{
.description = "Markdown",
.color = 0x000000,
.icon = "󰍔",
.extensions = .{"md"},
.comment = "<!--",
.highlights = "tree-sitter-markdown/tree-sitter-markdown/queries/highlights.scm",
.injections = "tree-sitter-markdown/tree-sitter-markdown/queries/injections.scm",
.language_server = .{ "marksman", "server" },
.formatter = .{ "prettier", "--parser", "markdown" },
};
pub const @"markdown-inline" = .{
.description = "Markdown (inline)",
.color = 0x000000,
.icon = "󰍔",
.extensions = .{},
.comment = "<!--",
.highlights = "tree-sitter-markdown/tree-sitter-markdown-inline/queries/highlights.scm",
.injections = "tree-sitter-markdown/tree-sitter-markdown-inline/queries/injections.scm",
};
pub const nasm = .{
.description = "Assembly Language (nasm)",
.extensions = .{ "asm", "nasm" },
.comment = "#",
.injections = "tree-sitter-nasm/queries/injections.scm",
};
pub const nim = .{
.description = "Nim",
.color = 0xffe953,
.icon = "",
.extensions = .{"nim"},
.comment = "#",
.language_server = .{"nimlangserver"},
};
pub const nimble = .{
.description = "Nimble (nim)",
.color = 0xffe953,
.icon = "",
.extensions = .{"nimble"},
.highlights = toml.highlights,
.comment = "#",
.parser = toml.parser,
};
pub const ninja = .{
.description = "Ninja",
.extensions = .{"ninja"},
.comment = "#",
};
pub const nix = .{
.description = "Nix",
.color = 0x5277C3,
.icon = "󱄅",
.extensions = .{"nix"},
.comment = "#",
.injections = "tree-sitter-nix/queries/injections.scm",
.language_server = .{"nixd"},
.formatter = .{"alejandra"},
};
pub const nu = .{
.description = "Nushell",
.color = 0x3AA675,
.icon = ">",
.extensions = .{ "nu", "nushell" },
.comment = "#",
.language_server = .{ "nu", "--lsp" },
.highlights = "tree-sitter-nu/queries/nu/highlights.scm",
.injections = "tree-sitter-nu/queries/nu/injections.scm",
};
pub const ocaml = .{
.description = "OCaml",
.color = 0xF18803,
.icon = "",
.extensions = .{ "ml", "mli" },
.comment = "(*",
.formatter = .{ "ocamlformat", "--profile=ocamlformat", "-" },
.language_server = .{ "ocamllsp", "--fallback-read-dot-merlin" },
};
pub const odin = .{
.description = "Odin",
.extensions = .{"odin"},
.comment = "//",
.parser = @import("file_type.zig").Parser("odin"),
.injections = "tree-sitter-odin/queries/injections.scm",
.language_server = .{"ols"},
.formatter = .{ "odinfmt", "-stdin" },
};
pub const openscad = .{
.description = "OpenSCAD",
.color = 0x000000,
.icon = "󰻫",
.extensions = .{"scad"},
.comment = "//",
.injections = "tree-sitter-openscad/queries/injections.scm",
.language_server = .{"openscad-lsp"},
};
pub const org = .{
.description = "Org Mode",
.icon = "",
.extensions = .{"org"},
.comment = "#",
};
pub const php = .{
.description = "PHP",
.color = 0x6181b6,
.icon = "󰌟",
.extensions = .{"php"},
.comment = "//",
.injections = "tree-sitter-php/queries/injections.scm",
.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,
.icon = "",
.extensions = .{"purs"},
.comment = "--",
.injections = "tree-sitter-purescript/queries/injections.scm",
};
pub const python = .{
.description = "Python",
.color = 0xffd845,
.icon = "󰌠",
.extensions = .{ "py", "pyi" },
.comment = "#",
.first_line_matches = FirstLineMatch{ .prefix = "#!", .content = "python" },
.language_server = .{"pylsp"},
};
pub const regex = .{
.description = "Regular expression",
.extensions = .{},
.comment = "#",
};
pub const rpmspec = .{
.description = "RPM spec",
.color = 0xff0000,
.icon = "󱄛",
.extensions = .{"spec"},
.comment = "#",
};
pub const ruby = .{
.description = "Ruby",
.color = 0xd91404,
.icon = "󰴭",
.extensions = .{"rb"},
.comment = "#",
.language_server = .{"ruby-lsp"},
};
pub const rust = .{
.description = "Rust",
.color = 0x000000,
.icon = "󱘗",
.extensions = .{"rs"},
.comment = "//",
.injections = "tree-sitter-rust/queries/injections.scm",
.language_server = .{"rust-analyzer"},
.formatter = .{"rustfmt"},
};
pub const scheme = .{
.description = "Scheme",
.extensions = .{ "scm", "ss", "el" },
.comment = ";",
};
pub const sql = .{
.description = "SQL",
.icon = "󰆼",
.extensions = .{"sql"},
.comment = "--",
};
pub const @"ssh-config" = .{
.description = "SSH config",
.extensions = .{".ssh/config"},
.comment = "#",
};
pub const swift = .{
.description = "Swift",
.color = 0xf05138,
.icon = "󰛥",
.extensions = .{ "swift", "swiftinterface" },
.comment = "//",
.language_server = .{"sourcekit-lsp"},
.formatter = .{"swift-format"},
};
pub const verilog = .{
.description = "SystemVerilog",
.extensions = .{ "sv", "svh" },
.comment = "//",
.highlights = "nvim-treesitter/queries/verilog/highlights.scm",
.injections = "nvim-treesitter/queries/verilog/injections.scm",
.language_server = .{"verible-verilog-ls"},
.formatter = .{ "verible-verilog-format", "-" },
};
pub const toml = .{
.description = "TOML",
.extensions = .{ "toml", "ini" },
.comment = "#",
.highlights = "tree-sitter-toml/queries/highlights.scm",
.parser = @import("file_type.zig").Parser("toml"),
};
pub const typescript = .{
.description = "TypeScript",
.color = 0x007acc,
.icon = "󰛦",
.extensions = .{ "ts", "tsx" },
.comment = "//",
.language_server = .{ "typescript-language-server", "--stdio" },
.formatter = .{ "prettier", "--parser", "typescript" },
};
pub const typst = .{
.description = "Typst",
.color = 0x23b6bc,
.icon = "t",
.extensions = .{ "typst", "typ" },
.comment = "//",
.language_server = .{"tinymist"},
.highlights = "tree-sitter-typst/queries/typst/highlights.scm",
.injections = "tree-sitter-typst/queries/typst/injections.scm",
};
pub const uxntal = .{
.description = "Uxntal",
.extensions = .{"tal"},
.comment = "(",
};
pub const vim = .{
.description = "Vimscript",
.color = 0x007f00,
.icon = "",
.extensions = .{"vim"},
.comment = "\"",
.highlights = "tree-sitter-vim/queries/vim/highlights.scm",
.injections = "tree-sitter-vim/queries/vim/injections.scm",
};
pub const xml = .{
.description = "XML",
.icon = "󰗀",
.extensions = .{"xml"},
.comment = "<!--",
.highlights = "tree-sitter-xml/queries/xml/highlights.scm",
.first_line_matches = FirstLineMatch{ .prefix = "<?xml " },
.formatter = .{ "xmllint", "--format", "-" },
};
pub const yaml = .{
.description = "YAML",
.color = 0x000000,
.icon = "",
.extensions = .{ "yaml", "yml" },
.comment = "#",
};
pub const zig = .{
.description = "Zig",
.color = 0xf7a41d,
.icon = "",
.extensions = .{ "zig", "zon" },
.comment = "//",
.formatter = .{ "zig", "fmt", "--stdin" },
.language_server = .{"zls"},
.injections = "tree-sitter-zig/queries/injections.scm",
};
pub const ziggy = .{
.description = "Ziggy",
.color = 0xf7a41d,
.icon = "",
.extensions = .{ "ziggy", "zgy" },
.comment = "//",
.highlights = "tree-sitter-ziggy/tree-sitter-ziggy/queries/highlights.scm",
};
pub const @"ziggy-schema" = .{
.description = "Ziggy (schema)",
.color = 0xf7a41d,
.icon = "",
.extensions = .{ "ziggy-schema", "zyg-schema" },
.comment = "//",
.highlights = "tree-sitter-ziggy/tree-sitter-ziggy-schema/queries/highlights.scm",
};

View file

@ -1,213 +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 Edit = treez.InputEdit;
pub const FileType = @import("file_type.zig");
pub const QueryCache = @import("QueryCache.zig");
pub const Range = treez.Range;
pub const Point = treez.Point;
const Input = treez.Input;
const Language = treez.Language;
const Parser = treez.Parser;
const Query = treez.Query;
pub const Node = treez.Node;
allocator: std.mem.Allocator,
lang: *const Language,
parser: *Parser,
query: *Query,
errors_query: *Query,
injections: ?*Query,
tree: ?*treez.Tree = null,
pub fn create(file_type: FileType, allocator: std.mem.Allocator, query_cache: *QueryCache) !*Self {
const query = try query_cache.get(file_type, .highlights);
errdefer query_cache.release(query, .highlights);
const errors_query = try query_cache.get(file_type, .errors);
errdefer query_cache.release(errors_query, .highlights);
const injections = try query_cache.get(file_type, .injections);
errdefer if (injections) |injections_| query_cache.release(injections_, .injections);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.lang = file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{file_type.name}),
.parser = try Parser.create(),
.query = query,
.errors_query = errors_query,
.injections = injections,
};
try self.parser.setLanguage(self.lang);
return self;
}
pub fn create_file_type_static(allocator: std.mem.Allocator, lang_name: []const u8, query_cache: *QueryCache) !*Self {
const file_type = FileType.get_by_name_static(lang_name) orelse return error.NotFound;
return create(file_type, allocator, query_cache);
}
pub fn create_guess_file_type_static(allocator: std.mem.Allocator, content: []const u8, file_path: ?[]const u8, query_cache: *QueryCache) !*Self {
const file_type = FileType.guess_static(file_path, content) orelse return error.NotFound;
return create(file_type, allocator, query_cache);
}
pub fn destroy(self: *Self, query_cache: *QueryCache) void {
if (self.tree) |tree| tree.destroy();
query_cache.release(self.query, .highlights);
query_cache.release(self.errors_query, .highlights);
if (self.injections) |injections| query_cache.release(injections, .injections);
self.parser.destroy();
self.allocator.destroy(self);
}
pub fn reset(self: *Self) void {
if (self.tree) |tree| {
tree.destroy();
self.tree = null;
}
}
pub fn refresh_full(self: *Self, content: []const u8) !void {
self.reset();
self.tree = try self.parser.parseString(null, content);
}
pub fn edit(self: *Self, ed: Edit) void {
if (self.tree) |tree| tree.edit(&ed);
}
pub fn refresh_from_buffer(self: *Self, buffer: anytype, metrics: anytype) !void {
const old_tree = self.tree;
defer if (old_tree) |tree| tree.destroy();
const State = struct {
buffer: @TypeOf(buffer),
metrics: @TypeOf(metrics),
syntax: *Self,
result_buf: [1024]u8 = undefined,
};
var state: State = .{
.buffer = buffer,
.metrics = metrics,
.syntax = self,
};
const input: Input = .{
.payload = &state,
.read = struct {
fn read(payload: ?*anyopaque, _: u32, position: treez.Point, bytes_read: *u32) callconv(.c) [*:0]const u8 {
const ctx: *State = @ptrCast(@alignCast(payload orelse return ""));
const result = ctx.buffer.get_from_pos(.{ .row = position.row, .col = position.column }, &ctx.result_buf, ctx.metrics);
bytes_read.* = @intCast(result.len);
return @ptrCast(result.ptr);
}
}.read,
.encoding = .utf_8,
};
self.tree = try self.parser.parse(old_tree, input);
}
pub fn refresh_from_string(self: *Self, content: [:0]const u8) !void {
const old_tree = self.tree;
defer if (old_tree) |tree| tree.destroy();
const State = struct {
content: @TypeOf(content),
};
var state: State = .{
.content = content,
};
const input: Input = .{
.payload = &state,
.read = struct {
fn read(payload: ?*anyopaque, _: u32, position: treez.Point, bytes_read: *u32) callconv(.c) [*:0]const u8 {
bytes_read.* = 0;
const ctx: *State = @ptrCast(@alignCast(payload orelse return ""));
const pos = (find_line_begin(ctx.content, position.row) orelse return "") + position.column;
if (pos >= ctx.content.len) return "";
bytes_read.* = @intCast(ctx.content.len - pos);
return ctx.content[pos..].ptr;
}
}.read,
.encoding = .utf_8,
};
self.tree = try self.parser.parse(old_tree, input);
}
fn find_line_begin(s: []const u8, line: usize) ?usize {
var idx: usize = 0;
var at_line: usize = 0;
while (idx < s.len) {
if (at_line == line)
return idx;
if (s[idx] == '\n')
at_line += 1;
idx += 1;
}
return null;
}
fn CallBack(comptime T: type) type {
return fn (ctx: T, sel: Range, scope: []const u8, id: u32, capture_idx: usize, node: *const Node) error{Stop}!void;
}
pub fn render(self: *const Self, ctx: anytype, comptime cb: CallBack(@TypeOf(ctx)), range: ?Range) !void {
const cursor = try Query.Cursor.create();
defer cursor.destroy();
const tree = self.tree orelse return;
cursor.execute(self.query, tree.getRootNode());
if (range) |r| cursor.setPointRange(r.start_point, r.end_point);
while (cursor.nextMatch()) |match| {
var idx: usize = 0;
for (match.captures()) |capture| {
try cb(ctx, capture.node.getRange(), self.query.getCaptureNameForId(capture.id), capture.id, idx, &capture.node);
idx += 1;
}
}
}
pub fn highlights_at_point(self: *const Self, ctx: anytype, comptime cb: CallBack(@TypeOf(ctx)), point: Point) void {
const cursor = Query.Cursor.create() catch return;
defer cursor.destroy();
const tree = self.tree orelse return;
cursor.execute(self.query, tree.getRootNode());
cursor.setPointRange(.{ .row = point.row, .column = 0 }, .{ .row = point.row + 1, .column = 0 });
while (cursor.nextMatch()) |match| {
for (match.captures()) |capture| {
const range = capture.node.getRange();
const start = range.start_point;
const end = range.end_point;
const scope = self.query.getCaptureNameForId(capture.id);
if (start.row == point.row and start.column <= point.column and point.column < end.column)
cb(ctx, range, scope, capture.id, 0, &capture.node) catch return;
break;
}
}
return;
}
pub fn node_at_point_range(self: *const Self, range: Range) error{Stop}!treez.Node {
const tree = self.tree orelse return error.Stop;
const root_node = tree.getRootNode();
return treez.Node.externs.ts_node_descendant_for_point_range(root_node, range.start_point, range.end_point);
}
pub fn count_error_nodes(self: *const Self) usize {
const cursor = Query.Cursor.create() catch return std.math.maxInt(usize);
defer cursor.destroy();
const tree = self.tree orelse return 0;
cursor.execute(self.errors_query, tree.getRootNode());
var error_count: usize = 0;
while (cursor.nextMatch()) |match| for (match.captures()) |_| {
error_count += 1;
};
return error_count;
}

View file

@ -1,133 +0,0 @@
pub const InputEdit = extern struct {
start_byte: u32,
old_end_byte: u32,
new_end_byte: u32,
start_point: Point,
old_end_point: Point,
new_end_point: Point,
};
pub const Range = extern struct {
start_point: Point = .{},
end_point: Point = .{},
start_byte: u32 = 0,
end_byte: u32 = 0,
};
pub const Point = extern struct {
row: u32 = 0,
column: u32 = 0,
};
pub const InputEncoding = enum(c_uint) {
utf_8,
utf_16,
};
pub const Input = extern struct {
payload: ?*anyopaque,
read: ?*const fn (payload: ?*anyopaque, byte_index: u32, position: Point, bytes_read: *u32) callconv(.c) [*:0]const u8,
encoding: InputEncoding,
};
pub const Language = struct {
var dummy: @This() = .{};
pub fn LangFn() callconv(.c) ?*const Language {
return &dummy;
}
};
pub const Parser = struct {
var dummy: @This() = .{};
pub fn create() !*@This() {
return &dummy;
}
pub fn parse(_: *Parser, _: ?*Tree, _: Input) !*Tree {
return &Tree.dummy;
}
pub fn parseString(_: *@This(), _: ?[]const u8, _: []const u8) !?*Tree {
return null;
}
pub fn destroy(_: *@This()) void {}
pub fn setLanguage(_: *Parser, _: *const Language) !void {}
};
pub const Query = struct {
var dummy: @This() = .{};
pub fn create(_: *const Language, _: []const u8) !*Query {
return &dummy;
}
pub const Cursor = struct {
var dummy_: @This() = .{};
pub fn create() !*@This() {
return &dummy_;
}
pub fn execute(_: *@This(), _: *Query, _: *Node) void {}
pub fn setPointRange(_: *@This(), _: Point, _: Point) void {}
pub fn nextMatch(_: *@This()) ?*Match {
return null;
}
pub fn destroy(_: *@This()) void {}
pub const Match = struct {
pub fn captures(_: *@This()) []Capture {
return &[_]Capture{};
}
};
pub const Capture = struct {
id: u32,
node: Node,
};
};
pub fn getCaptureNameForId(_: *@This(), _: u32) []const u8 {
return "";
}
pub fn destroy(_: *@This()) void {}
};
pub const Tree = struct {
var dummy: @This() = .{};
pub fn getRootNode(_: *@This()) *Node {
return &Node.dummy;
}
pub fn destroy(_: *@This()) void {}
pub fn edit(_: *Tree, _: *const InputEdit) void {}
};
pub const Node = struct {
var dummy: @This() = .{};
pub fn getRange(_: *const @This()) Range {
return .{};
}
pub fn asSExpressionString(_: *const @This()) []const u8 {
return "";
}
pub fn freeSExpressionString(_: []const u8) void {}
pub fn getParent(_: *const @This()) Node {
return dummy;
}
pub fn getChild(_: *const @This(), _: usize) Node {
return dummy;
}
pub fn getChildCount(_: *const @This()) usize {
return 0;
}
pub fn getNamedChild(_: *const @This(), _: usize) Node {
return dummy;
}
pub fn getNamedChildCount(_: *const @This()) usize {
return 0;
}
pub fn isNull(_: *const @This()) bool {
return true;
}
pub const externs = struct {
pub fn ts_node_next_sibling(_: Node) Node {
return Node.dummy;
}
pub fn ts_node_prev_sibling(_: Node) Node {
return Node.dummy;
}
pub fn ts_node_next_named_sibling(_: Node) Node {
return Node.dummy;
}
pub fn ts_node_prev_named_sibling(_: Node) Node {
return Node.dummy;
}
pub fn ts_node_descendant_for_point_range(_: *const Node, _: Point, _: Point) Node {
return Node.dummy;
}
};
};

View file

@ -1,140 +0,0 @@
const std = @import("std");
const cbor = @import("cbor");
const treez = @import("treez");
pub const tss = @import("ts_serializer.zig");
const verbose = false;
pub fn main() anyerror!void {
const allocator = std.heap.c_allocator;
const args = try std.process.argsAlloc(allocator);
var opt_output_file_path: ?[]const u8 = null;
var i: usize = 1;
while (i < args.len) : (i += 1) {
const arg = args[i];
if (opt_output_file_path != null) fatal("duplicated {s} argument", .{arg});
opt_output_file_path = args[i];
}
const output_file_path = opt_output_file_path orelse fatal("missing output file", .{});
var output_file = std.fs.cwd().createFile(output_file_path, .{}) catch |err| {
fatal("unable to open '{s}': {s}", .{ output_file_path, @errorName(err) });
};
defer output_file.close();
var output = std.ArrayList(u8).init(allocator);
defer output.deinit();
const writer = output.writer();
try cbor.writeMapHeader(writer, file_types.len);
for (file_types) |file_type| {
const lang = file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{file_type.name});
try cbor.writeValue(writer, file_type.name);
try cbor.writeMapHeader(writer, if (file_type.injections) |_| 3 else 2);
const highlights_in = try treez.Query.create(lang, file_type.highlights);
const ts_highlights_in: *tss.TSQuery = @alignCast(@ptrCast(highlights_in));
const highlights_cb = try tss.toCbor(ts_highlights_in, allocator);
defer allocator.free(highlights_cb);
try cbor.writeValue(writer, "highlights");
try cbor.writeValue(writer, highlights_cb);
if (verbose)
std.log.info("file_type {s} highlights {d} bytes", .{ file_type.name, highlights_cb.len });
const errors_in = try treez.Query.create(lang, "(ERROR) @error");
const ts_errors_in: *tss.TSQuery = @alignCast(@ptrCast(errors_in));
const errors_cb = try tss.toCbor(ts_errors_in, allocator);
defer allocator.free(errors_cb);
try cbor.writeValue(writer, "errors");
try cbor.writeValue(writer, errors_cb);
if (verbose)
std.log.info("file_type {s} errors {d} bytes", .{ file_type.name, errors_cb.len });
if (file_type.injections) |injections| {
const injections_in = try treez.Query.create(lang, injections);
const ts_injections_in: *tss.TSQuery = @alignCast(@ptrCast(injections_in));
const injections_cb = try tss.toCbor(ts_injections_in, allocator);
defer allocator.free(injections_cb);
try cbor.writeValue(writer, "injections");
try cbor.writeValue(writer, injections_cb);
if (verbose)
std.log.info("file_type {s} injections {d} bytes", .{ file_type.name, injections_cb.len });
}
}
try output_file.writeAll(output.items);
if (verbose)
std.log.info("file_types total {d} bytes", .{output.items.len});
}
fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.debug.print(format, args);
std.process.exit(1);
}
pub const file_types = load_file_types(@import("file_types.zig"));
const FileType = struct {
name: []const u8,
lang_fn: LangFn,
highlights: [:0]const u8,
injections: ?[:0]const u8,
};
const LangFn = *const fn () callconv(.c) ?*const treez.Language;
fn load_file_types(comptime Namespace: type) []const FileType {
comptime switch (@typeInfo(Namespace)) {
.@"struct" => |info| {
var count = 0;
for (info.decls) |_| count += 1;
var construct_types: [count]FileType = undefined;
var i = 0;
for (info.decls) |decl| {
const lang = decl.name;
const args = @field(Namespace, lang);
construct_types[i] = .{
.name = lang,
.lang_fn = if (@hasField(@TypeOf(args), "parser")) args.parser else get_parser(lang),
.highlights = 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"),
.injections = if (@hasField(@TypeOf(args), "injections"))
@embedFile(args.injections)
else
null,
};
i += 1;
}
const types = construct_types;
return &types;
},
else => @compileError("expected tuple or struct type"),
};
}
fn get_parser(comptime lang: []const u8) LangFn {
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}));
}
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;
}

View file

@ -1,297 +0,0 @@
/// This file *MUST* be kept in sync with tree-sitter/lib/src/query.c
/// It exactly represents the C structures in memory and must produce
/// the exact same results as the C tree-sitter library version used.
///
/// Yes,... it is not a public API! Here be dragons!
///
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 Slice = extern struct {
offset: u32,
length: u32,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8) cbor.Error!bool {
return cbor.matchValue(iter, .{
cbor.extract(&self.offset),
cbor.extract(&self.length),
});
}
};
pub fn Array(T: type) type {
return extern struct {
contents: ?*T,
size: u32,
capacity: u32,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
if (self.contents) |contents| {
const arr: []T = @as([*]T, @ptrCast(contents))[0..self.size];
try cbor.writeValue(writer, arr);
return;
}
try cbor.writeValue(writer, null);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8, allocator: std.mem.Allocator) cbor.Error!bool {
var iter_ = iter.*;
if (cbor.matchValue(&iter_, cbor.null_) catch false) {
iter.* = iter_;
self.contents = null;
self.size = 0;
self.capacity = 0;
return true;
}
if (T == u8) {
var arr: []const u8 = undefined;
if (try cbor.matchValue(iter, cbor.extract(&arr))) {
self.contents = @constCast(@ptrCast(arr.ptr));
self.size = @intCast(arr.len);
self.capacity = @intCast(arr.len);
return true;
}
return false;
}
var i: usize = 0;
var n = try cbor.decodeArrayHeader(iter);
var arr: []T = try allocator.alloc(T, n);
while (n > 0) : (n -= 1) {
if (comptime cbor.isExtractableAlloc(T)) {
if (!(cbor.matchValue(iter, cbor.extractAlloc(&arr[i], allocator)) catch return false))
return false;
} else {
if (!(cbor.matchValue(iter, cbor.extract(&arr[i])) catch return false))
return false;
}
i += 1;
}
self.contents = @constCast(@ptrCast(arr.ptr));
self.size = @intCast(arr.len);
self.capacity = @intCast(arr.len);
return true;
}
};
}
pub const SymbolTable = extern struct {
characters: Array(u8),
slices: Array(Slice),
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8, allocator: std.mem.Allocator) cbor.Error!bool {
return cbor.matchValue(iter, .{
cbor.extractAlloc(&self.characters, allocator),
cbor.extractAlloc(&self.slices, allocator),
});
}
};
pub const CaptureQuantifiers = Array(u8);
pub const PatternEntry = extern struct {
step_index: u16,
pattern_index: u16,
is_rooted: bool,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8) cbor.Error!bool {
return cbor.matchValue(iter, .{
cbor.extract(&self.step_index),
cbor.extract(&self.pattern_index),
cbor.extract(&self.is_rooted),
});
}
};
pub const QueryPattern = extern struct {
steps: Slice,
predicate_steps: Slice,
start_byte: u32,
end_byte: u32,
is_non_local: bool,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8, allocator: std.mem.Allocator) cbor.Error!bool {
return cbor.matchValue(iter, .{
cbor.extractAlloc(&self.steps, allocator),
cbor.extractAlloc(&self.predicate_steps, allocator),
cbor.extract(&self.start_byte),
cbor.extract(&self.end_byte),
cbor.extract(&self.is_non_local),
});
}
};
pub const StepOffset = extern struct {
byte_offset: u32,
step_index: u16,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8) cbor.Error!bool {
return cbor.matchValue(iter, .{
cbor.extract(&self.byte_offset),
cbor.extract(&self.step_index),
});
}
};
pub const MAX_STEP_CAPTURE_COUNT = 3;
pub const TSSymbol = u16;
pub const TSFieldId = u16;
pub const QueryStep = extern struct {
symbol: TSSymbol,
supertype_symbol: TSSymbol,
field: TSFieldId,
capture_ids: [MAX_STEP_CAPTURE_COUNT]u16,
depth: u16,
alternative_index: u16,
negated_field_list_id: u16,
// is_named: u1,
// is_immediate: u1,
// is_last_child: u1,
// is_pass_through: u1,
// is_dead_end: u1,
// alternative_is_immediate: u1,
// contains_captures: u1,
// root_pattern_guaranteed: u1,
flags8: u8,
// parent_pattern_guaranteed: u1,
flags16: u8,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8) cbor.Error!bool {
return cbor.matchValue(iter, .{
cbor.extract(&self.symbol),
cbor.extract(&self.supertype_symbol),
cbor.extract(&self.field),
cbor.extract(&self.capture_ids),
cbor.extract(&self.depth),
cbor.extract(&self.alternative_index),
cbor.extract(&self.negated_field_list_id),
cbor.extract(&self.flags8),
cbor.extract(&self.flags16),
});
}
};
pub const PredicateStep = extern struct {
pub const Type = enum(c_uint) {
done,
capture,
string,
};
type: Type,
value_id: u32,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8) cbor.Error!bool {
return cbor.matchValue(iter, .{
cbor.extract(&self.type),
cbor.extract(&self.value_id),
});
}
};
pub const TSQuery = extern struct {
captures: SymbolTable,
predicate_values: SymbolTable,
capture_quantifiers: Array(CaptureQuantifiers),
steps: Array(QueryStep),
pattern_map: Array(PatternEntry),
predicate_steps: Array(PredicateStep),
patterns: Array(QueryPattern),
step_offsets: Array(StepOffset),
negated_fields: Array(TSFieldId),
string_buffer: Array(u8),
repeat_symbols_with_rootless_patterns: Array(TSSymbol),
language: usize,
// language: ?*const treez.Language,
wildcard_root_pattern_count: u16,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8, allocator: std.mem.Allocator) cbor.Error!bool {
const result = cbor.matchValue(iter, .{
cbor.extractAlloc(&self.captures, allocator),
cbor.extractAlloc(&self.predicate_values, allocator),
cbor.extractAlloc(&self.capture_quantifiers, allocator),
cbor.extractAlloc(&self.steps, allocator),
cbor.extractAlloc(&self.pattern_map, allocator),
cbor.extractAlloc(&self.predicate_steps, allocator),
cbor.extractAlloc(&self.patterns, allocator),
cbor.extractAlloc(&self.step_offsets, allocator),
cbor.extractAlloc(&self.negated_fields, allocator),
cbor.extractAlloc(&self.string_buffer, allocator),
cbor.extractAlloc(&self.repeat_symbols_with_rootless_patterns, allocator),
cbor.extract(&self.language),
cbor.extract(&self.wildcard_root_pattern_count),
});
self.language = 0;
return result;
}
};
pub const SerializeError = error{OutOfMemory};
pub fn toCbor(query: *TSQuery, allocator: std.mem.Allocator) SerializeError![]const u8 {
var cb: std.ArrayListUnmanaged(u8) = .empty;
defer cb.deinit(allocator);
try cbor.writeValue(cb.writer(allocator), query.*);
return cb.toOwnedSlice(allocator);
}
pub const DeserializeError = error{
OutOfMemory,
IntegerTooLarge,
IntegerTooSmall,
InvalidType,
TooShort,
InvalidFloatType,
InvalidArrayType,
InvalidPIntType,
JsonIncompatibleType,
InvalidQueryCbor,
NotAnObject,
BadArrayAllocExtract,
};
pub fn fromCbor(cb: []const u8, allocator: std.mem.Allocator) DeserializeError!struct { *TSQuery, *std.heap.ArenaAllocator } {
var arena = try allocator.create(std.heap.ArenaAllocator);
errdefer allocator.destroy(arena);
arena.* = std.heap.ArenaAllocator.init(allocator);
errdefer arena.deinit();
const query = try arena.allocator().create(TSQuery);
query.* = undefined;
var iter: []const u8 = cb;
if (!try cbor.matchValue(&iter, cbor.extractAlloc(query, arena.allocator())))
return error.InvalidQueryCbor;
return .{ query, arena };
}

View file

@ -12,6 +12,8 @@ pub fn Options(context: type) type {
label: []const u8 = "Enter text", label: []const u8 = "Enter text",
pos: Widget.Box = .{ .y = 0, .x = 0, .w = 12, .h = 1 }, pos: Widget.Box = .{ .y = 0, .x = 0, .w = 12, .h = 1 },
ctx: Context, ctx: Context,
padding: u8 = 1,
icon: ?[]const u8 = null,
on_click: *const fn (ctx: context, button: *State(Context)) void = do_nothing, on_click: *const fn (ctx: context, button: *State(Context)) void = do_nothing,
on_render: *const fn (ctx: context, button: *State(Context), theme: *const Widget.Theme) bool = on_render_default, on_render: *const fn (ctx: context, button: *State(Context), theme: *const Widget.Theme) bool = on_render_default,
@ -29,18 +31,22 @@ pub fn Options(context: type) type {
self.plane.set_style(style_label); self.plane.set_style(style_label);
self.plane.fill(" "); self.plane.fill(" ");
self.plane.home(); self.plane.home();
for (0..self.opts.padding) |_| _ = self.plane.putchar(" ");
if (self.icon_width > 0) if (self.opts.icon) |icon| {
_ = self.plane.print("{s}", .{icon}) catch {};
};
if (self.text.items.len > 0) { if (self.text.items.len > 0) {
_ = self.plane.print(" {s} ", .{self.text.items}) catch {}; _ = self.plane.print("{s} ", .{self.text.items}) catch {};
} else { } else {
_ = self.plane.print(" {s} ", .{self.label.items}) catch {}; _ = self.plane.print("{s} ", .{self.label.items}) catch {};
} }
if (self.cursor) |cursor| { if (self.cursor) |cursor| {
const pos: c_int = @intCast(cursor); const pos: c_int = @intCast(cursor);
if (tui.config().enable_terminal_cursor) { if (tui.config().enable_terminal_cursor) {
const y, const x = self.plane.rel_yx_to_abs(0, pos + 1); const y, const x = self.plane.rel_yx_to_abs(0, pos + self.opts.padding + self.icon_width);
tui.rdr().cursor_enable(y, x, tui.get_cursor_shape()) catch {}; tui.rdr().cursor_enable(y, x, tui.get_cursor_shape()) catch {};
} else { } else {
self.plane.cursor_move_yx(0, pos + 1) catch return false; self.plane.cursor_move_yx(0, pos + self.opts.padding + self.icon_width) catch return false;
var cell = self.plane.cell_init(); var cell = self.plane.cell_init();
_ = self.plane.at_cursor_cell(&cell) catch return false; _ = self.plane.at_cursor_cell(&cell) catch return false;
cell.set_style(theme.editor_cursor); cell.set_style(theme.editor_cursor);
@ -68,6 +74,7 @@ pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts:
.opts = opts, .opts = opts,
.label = std.ArrayList(u8).init(allocator), .label = std.ArrayList(u8).init(allocator),
.text = std.ArrayList(u8).init(allocator), .text = std.ArrayList(u8).init(allocator),
.icon_width = @intCast(if (tui.config().show_fileicons) if (opts.icon) |icon| n.egc_chunk_width(icon, 0, 1) else 0 else 0),
}; };
try self.label.appendSlice(self.opts.label); try self.label.appendSlice(self.opts.label);
self.opts.label = self.label.items; self.opts.label = self.label.items;
@ -83,6 +90,7 @@ pub fn State(ctx_type: type) type {
label: std.ArrayList(u8), label: std.ArrayList(u8),
opts: Options(ctx_type), opts: Options(ctx_type),
text: std.ArrayList(u8), text: std.ArrayList(u8),
icon_width: c_int,
cursor: ?usize = 0, cursor: ?usize = 0,
const Self = @This(); const Self = @This();

View file

@ -14,13 +14,15 @@ pub const scroll_lines = 3;
pub fn Options(context: type) type { pub fn Options(context: type) type {
return struct { return struct {
ctx: Context, ctx: Context,
style: Widget.Type,
on_click: *const fn (ctx: context, button: *Button.State(*State(Context))) void = do_nothing, on_click: *const fn (ctx: context, button: *Button.State(*State(Context))) void = do_nothing,
on_click4: *const fn (menu: **State(Context), button: *Button.State(*State(Context))) void = do_nothing_click, on_click4: *const fn (menu: **State(Context), button: *Button.State(*State(Context))) void = do_nothing_click,
on_click5: *const fn (menu: **State(Context), button: *Button.State(*State(Context))) void = do_nothing_click, on_click5: *const fn (menu: **State(Context), button: *Button.State(*State(Context))) void = do_nothing_click,
on_render: *const fn (ctx: context, button: *Button.State(*State(Context)), theme: *const Widget.Theme, selected: bool) bool = on_render_default, on_render: *const fn (ctx: context, button: *Button.State(*State(Context)), theme: *const Widget.Theme, selected: bool) bool = on_render_default,
on_layout: *const fn (ctx: context, button: *Button.State(*State(Context))) Widget.Layout = on_layout_default, on_layout: *const fn (ctx: context, button: *Button.State(*State(Context))) Widget.Layout = on_layout_default,
on_resize: *const fn (ctx: context, menu: *State(Context), box: Widget.Box) void = on_resize_default, prepare_resize: *const fn (ctx: context, menu: *State(Context), box: Widget.Box) Widget.Box = prepare_resize_default,
after_resize: *const fn (ctx: context, menu: *State(Context), box: Widget.Box) void = after_resize_default,
on_scroll: ?EventHandler = null, on_scroll: ?EventHandler = null,
pub const Context = context; pub const Context = context;
@ -46,23 +48,26 @@ pub fn Options(context: type) type {
return .{ .static = 1 }; return .{ .static = 1 };
} }
pub fn on_resize_default(_: context, state: *State(Context), box_: Widget.Box) void { pub fn prepare_resize_default(_: context, state: *State(Context), box_: Widget.Box) Widget.Box {
var box = box_; var box = box_;
box.h = if (box_.h == 0) state.menu.widgets.items.len else box_.h; box.h = if (box_.h == 0) state.menu.widgets.items.len else box_.h;
state.container.resize(box); return box;
} }
pub fn after_resize_default(_: context, _: *State(Context), _: Widget.Box) void {}
}; };
} }
pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts: Options(ctx_type)) !*State(ctx_type) { pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts: Options(ctx_type)) !*State(ctx_type) {
const self = try allocator.create(State(ctx_type)); const self = try allocator.create(State(ctx_type));
errdefer allocator.destroy(self); errdefer allocator.destroy(self);
const container = try WidgetList.createH(allocator, parent, @typeName(@This()), .dynamic); const container = try WidgetList.createHStyled(allocator, parent, @typeName(@This()), .dynamic, opts.style);
self.* = .{ self.* = .{
.allocator = allocator, .allocator = allocator,
.menu = try WidgetList.createV(allocator, container.plane, @typeName(@This()), .dynamic), .menu = try WidgetList.createV(allocator, container.plane, @typeName(@This()), .dynamic),
.container = container, .container = container,
.container_widget = container.widget(), .container_widget = container.widget(),
.frame_widget = null,
.scrollbar = if (tui.config().show_scrollbars) .scrollbar = if (tui.config().show_scrollbars)
if (opts.on_scroll) |on_scroll| (try scrollbar_v.create(allocator, parent, null, on_scroll)).dynamic_cast(scrollbar_v).? else null if (opts.on_scroll) |on_scroll| (try scrollbar_v.create(allocator, parent, null, on_scroll)).dynamic_cast(scrollbar_v).? else null
else else
@ -72,7 +77,8 @@ pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts:
self.menu.ctx = self; self.menu.ctx = self;
self.menu.on_render = State(ctx_type).on_render_menu; self.menu.on_render = State(ctx_type).on_render_menu;
container.ctx = self; container.ctx = self;
container.on_resize = State(ctx_type).on_resize_container; container.prepare_resize = State(ctx_type).prepare_resize;
container.after_resize = State(ctx_type).after_resize;
try container.add(self.menu.widget()); try container.add(self.menu.widget());
if (self.scrollbar) |sb| try container.add(sb.widget()); if (self.scrollbar) |sb| try container.add(sb.widget());
return self; return self;
@ -84,6 +90,7 @@ pub fn State(ctx_type: type) type {
menu: *WidgetList, menu: *WidgetList,
container: *WidgetList, container: *WidgetList,
container_widget: Widget, container_widget: Widget,
frame_widget: ?Widget,
scrollbar: ?*scrollbar_v, scrollbar: ?*scrollbar_v,
opts: options_type, opts: options_type,
selected: ?usize = null, selected: ?usize = null,
@ -146,9 +153,14 @@ pub fn State(ctx_type: type) type {
self.render_idx = 0; self.render_idx = 0;
} }
fn on_resize_container(ctx: ?*anyopaque, _: *WidgetList, box: Widget.Box) void { fn prepare_resize(ctx: ?*anyopaque, _: *WidgetList, box: Widget.Box) Widget.Box {
const self: *Self = @ptrCast(@alignCast(ctx)); const self: *Self = @ptrCast(@alignCast(ctx));
self.opts.on_resize(self.*.opts.ctx, self, box); return self.opts.prepare_resize(self.*.opts.ctx, self, box);
}
fn after_resize(ctx: ?*anyopaque, _: *WidgetList, box: Widget.Box) void {
const self: *Self = @ptrCast(@alignCast(ctx));
self.opts.after_resize(self.*.opts.ctx, self, box);
} }
pub fn on_layout(self: **Self, button: *Button.State(*Self)) Widget.Layout { pub fn on_layout(self: **Self, button: *Button.State(*Self)) Widget.Layout {
@ -170,7 +182,7 @@ pub fn State(ctx_type: type) type {
} }
pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool { pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool {
return self.menu.walk(walk_ctx, f, &self.container_widget); return self.menu.walk(walk_ctx, f, if (self.frame_widget) |*frame| frame else &self.container_widget);
} }
pub fn count(self: *Self) usize { pub fn count(self: *Self) usize {

View file

@ -14,6 +14,7 @@ pub const Error = (cbor.Error || cbor.JsonEncodeError || error{
ThespianSpawnFailed, ThespianSpawnFailed,
NoProject, NoProject,
ProjectManagerFailed, ProjectManagerFailed,
InvalidProjectDirectory,
SendFailed, SendFailed,
}); });

View file

@ -10,6 +10,9 @@ pub const Box = @import("Box.zig");
pub const Theme = @import("theme"); pub const Theme = @import("theme");
pub const themes = @import("themes").themes; pub const themes = @import("themes").themes;
pub const scopes = @import("themes").scopes; pub const scopes = @import("themes").scopes;
pub const Type = @import("config").WidgetType;
pub const StyleTag = @import("config").WidgetStyle;
pub const Style = @import("WidgetStyle.zig");
ptr: *anyopaque, ptr: *anyopaque,
plane: *Plane, plane: *Plane,

View file

@ -6,6 +6,7 @@ const tp = @import("thespian");
const Plane = @import("renderer").Plane; const Plane = @import("renderer").Plane;
const tui = @import("tui.zig");
const Widget = @import("Widget.zig"); const Widget = @import("Widget.zig");
const Box = @import("Box.zig"); const Box = @import("Box.zig");
@ -26,25 +27,35 @@ widgets: ArrayList(WidgetState),
layout_: Layout, layout_: Layout,
layout_empty: bool = true, layout_empty: bool = true,
direction: Direction, direction: Direction,
box: ?Widget.Box = null, deco_box: Widget.Box,
ctx: ?*anyopaque = null, ctx: ?*anyopaque = null,
on_render: *const fn (ctx: ?*anyopaque, theme: *const Widget.Theme) void = on_render_default, on_render: *const fn (ctx: ?*anyopaque, theme: *const Widget.Theme) void = on_render_default,
after_render: *const fn (ctx: ?*anyopaque, theme: *const Widget.Theme) void = on_render_default, after_render: *const fn (ctx: ?*anyopaque, theme: *const Widget.Theme) void = on_render_default,
on_resize: *const fn (ctx: ?*anyopaque, self: *Self, pos_: Widget.Box) void = on_resize_default, prepare_resize: *const fn (ctx: ?*anyopaque, self: *Self, box: Widget.Box) Widget.Box = prepare_resize_default,
after_resize: *const fn (ctx: ?*anyopaque, self: *Self, box: Widget.Box) void = after_resize_default,
on_layout: *const fn (ctx: ?*anyopaque, self: *Self) Widget.Layout = on_layout_default, on_layout: *const fn (ctx: ?*anyopaque, self: *Self) Widget.Layout = on_layout_default,
widget_type: Widget.Type,
pub fn createH(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout) error{OutOfMemory}!*Self { pub fn createH(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout) error{OutOfMemory}!*Self {
return createHStyled(allocator, parent, name, layout_, .none);
}
pub fn createHStyled(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout, widget_type: Widget.Type) error{OutOfMemory}!*Self {
const self = try allocator.create(Self); const self = try allocator.create(Self);
errdefer allocator.destroy(self); errdefer allocator.destroy(self);
self.* = try init(allocator, parent, name, .horizontal, layout_, Box{}); self.* = try init(allocator, parent, name, .horizontal, layout_, Box{}, widget_type);
self.plane.hide(); self.plane.hide();
return self; return self;
} }
pub fn createV(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout) !*Self { pub fn createV(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout) !*Self {
return createVStyled(allocator, parent, name, layout_, .none);
}
pub fn createVStyled(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout, widget_type: Widget.Type) !*Self {
const self = try allocator.create(Self); const self = try allocator.create(Self);
errdefer allocator.destroy(self); errdefer allocator.destroy(self);
self.* = try init(allocator, parent, name, .vertical, layout_, Box{}); self.* = try init(allocator, parent, name, .vertical, layout_, Box{}, widget_type);
self.plane.hide(); self.plane.hide();
return self; return self;
} }
@ -57,15 +68,21 @@ pub fn createBox(allocator: Allocator, parent: Plane, name: [:0]const u8, dir: D
return self; return self;
} }
fn init(allocator: Allocator, parent: Plane, name: [:0]const u8, dir: Direction, layout_: Layout, box: Box) !Self { fn init(allocator: Allocator, parent: Plane, name: [:0]const u8, dir: Direction, layout_: Layout, box_: Box, widget_type: Widget.Type) !Self {
return .{ var self: Self = .{
.plane = try Plane.init(&box.opts(name), parent), .plane = undefined,
.parent = parent, .parent = parent,
.allocator = allocator, .allocator = allocator,
.widgets = ArrayList(WidgetState).init(allocator), .widgets = ArrayList(WidgetState).init(allocator),
.layout_ = layout_, .layout_ = layout_,
.direction = dir, .direction = dir,
.widget_type = widget_type,
.deco_box = undefined,
}; };
const padding = tui.get_widget_style(self.widget_type).padding;
self.deco_box = self.from_client_box(box_, padding);
self.plane = try Plane.init(&self.deco_box.opts(name), parent);
return self;
} }
pub fn widget(self: *Self) Widget { pub fn widget(self: *Self) Widget {
@ -147,18 +164,27 @@ pub fn update(self: *Self) void {
} }
pub fn render(self: *Self, theme: *const Widget.Theme) bool { pub fn render(self: *Self, theme: *const Widget.Theme) bool {
const widget_style = tui.get_widget_style(self.widget_type);
const padding = widget_style.padding;
for (self.widgets.items) |*w| if (!w.layout.eql(w.widget.layout())) { for (self.widgets.items) |*w| if (!w.layout.eql(w.widget.layout())) {
self.refresh_layout(); self.refresh_layout(padding);
break; break;
}; };
self.on_render(self.ctx, theme); self.on_render(self.ctx, theme);
self.render_decoration(theme, widget_style);
const client_box = self.to_client_box(self.deco_box, padding);
var more = false; var more = false;
for (self.widgets.items) |*w| for (self.widgets.items) |*w| {
const widget_box = w.widget.box();
if (client_box.y + client_box.h <= widget_box.y) break;
if (client_box.x + client_box.w <= widget_box.x) break;
if (w.widget.render(theme)) { if (w.widget.render(theme)) {
more = true; more = true;
}; }
}
self.after_render(self.ctx, theme); self.after_render(self.ctx, theme);
return more; return more;
@ -166,6 +192,41 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
fn on_render_default(_: ?*anyopaque, _: *const Widget.Theme) void {} fn on_render_default(_: ?*anyopaque, _: *const Widget.Theme) void {}
fn render_decoration(self: *Self, theme: *const Widget.Theme, widget_style: *const Widget.Style) void {
const style = Widget.Style.theme_style_from_type(self.widget_type, theme);
const padding = widget_style.padding;
const border = widget_style.border;
const plane = &self.plane;
const box = self.deco_box;
plane.set_style(style);
plane.fill(" ");
if (padding.top > 0 and padding.left > 0) put_at_pos(plane, 0, 0, border.nw);
if (padding.top > 0 and padding.right > 0) put_at_pos(plane, 0, box.w - 1, border.ne);
if (padding.bottom > 0 and padding.left > 0 and box.h > 0) put_at_pos(plane, box.h - 1, 0, border.sw);
if (padding.bottom > 0 and padding.right > 0 and box.h > 0) put_at_pos(plane, box.h - 1, box.w - 1, border.se);
{
const start: usize = if (padding.left > 0) 1 else 0;
const end: usize = if (padding.right > 0 and box.w > 0) box.w - 1 else box.w;
if (padding.top > 0) for (start..end) |x| put_at_pos(plane, 0, x, border.n);
if (padding.bottom > 0) for (start..end) |x| put_at_pos(plane, box.h - 1, x, border.s);
}
{
const start: usize = if (padding.top > 0) 1 else 0;
const end: usize = if (padding.bottom > 0 and box.h > 0) box.h - 1 else box.h;
if (padding.left > 0) for (start..end) |y| put_at_pos(plane, y, 0, border.w);
if (padding.right > 0) for (start..end) |y| put_at_pos(plane, y, box.w - 1, border.e);
}
}
inline fn put_at_pos(plane: *Plane, y: usize, x: usize, egc: []const u8) void {
plane.cursor_move_yx(@intCast(y), @intCast(x)) catch return;
plane.putchar(egc);
}
pub fn receive(self: *Self, from_: tp.pid_ref, m: tp.message) error{Exit}!bool { pub fn receive(self: *Self, from_: tp.pid_ref, m: tp.message) error{Exit}!bool {
if (try m.match(.{ "H", tp.more })) if (try m.match(.{ "H", tp.more }))
return false; return false;
@ -176,6 +237,13 @@ pub fn receive(self: *Self, from_: tp.pid_ref, m: tp.message) error{Exit}!bool {
return false; return false;
} }
fn get_size_a_const(self: *Self, pos: *const Widget.Box) usize {
return switch (self.direction) {
.vertical => pos.h,
.horizontal => pos.w,
};
}
fn get_size_a(self: *Self, pos: *Widget.Box) *usize { fn get_size_a(self: *Self, pos: *Widget.Box) *usize {
return switch (self.direction) { return switch (self.direction) {
.vertical => &pos.h, .vertical => &pos.h,
@ -183,6 +251,13 @@ fn get_size_a(self: *Self, pos: *Widget.Box) *usize {
}; };
} }
fn get_size_b_const(self: *Self, pos: *const Widget.Box) usize {
return switch (self.direction) {
.vertical => pos.w,
.horizontal => pos.h,
};
}
fn get_size_b(self: *Self, pos: *Widget.Box) *usize { fn get_size_b(self: *Self, pos: *Widget.Box) *usize {
return switch (self.direction) { return switch (self.direction) {
.vertical => &pos.w, .vertical => &pos.w,
@ -190,6 +265,13 @@ fn get_size_b(self: *Self, pos: *Widget.Box) *usize {
}; };
} }
fn get_loc_a_const(self: *Self, pos: *const Widget.Box) usize {
return switch (self.direction) {
.vertical => pos.y,
.horizontal => pos.x,
};
}
fn get_loc_a(self: *Self, pos: *Widget.Box) *usize { fn get_loc_a(self: *Self, pos: *Widget.Box) *usize {
return switch (self.direction) { return switch (self.direction) {
.vertical => &pos.y, .vertical => &pos.y,
@ -197,6 +279,13 @@ fn get_loc_a(self: *Self, pos: *Widget.Box) *usize {
}; };
} }
fn get_loc_b_const(self: *Self, pos: *const Widget.Box) usize {
return switch (self.direction) {
.vertical => pos.x,
.horizontal => pos.y,
};
}
fn get_loc_b(self: *Self, pos: *Widget.Box) *usize { fn get_loc_b(self: *Self, pos: *Widget.Box) *usize {
return switch (self.direction) { return switch (self.direction) {
.vertical => &pos.x, .vertical => &pos.x,
@ -204,28 +293,62 @@ fn get_loc_b(self: *Self, pos: *Widget.Box) *usize {
}; };
} }
fn refresh_layout(self: *Self) void { fn refresh_layout(self: *Self, padding: Widget.Style.Margin) void {
return if (self.box) |box| self.handle_resize(box); return self.handle_resize(self.to_client_box(self.deco_box, padding));
} }
pub fn handle_resize(self: *Self, pos: Widget.Box) void { pub fn handle_resize(self: *Self, box: Widget.Box) void {
self.on_resize(self.ctx, self, pos); const padding = tui.get_widget_style(self.widget_type).padding;
const client_box_ = self.prepare_resize(self.ctx, self, self.to_client_box(box, padding));
self.deco_box = self.from_client_box(client_box_, padding);
self.do_resize(padding);
self.after_resize(self.ctx, self, self.to_client_box(self.deco_box, padding));
} }
fn on_resize_default(_: ?*anyopaque, self: *Self, pos: Widget.Box) void { pub inline fn to_client_box(_: *const Self, box_: Widget.Box, padding: Widget.Style.Margin) Widget.Box {
self.resize(pos); const total_y_padding = padding.top + padding.bottom;
const total_x_padding = padding.left + padding.right;
var box = box_;
box.y += padding.top;
box.h -= if (box.h > total_y_padding) total_y_padding else box.h;
box.x += padding.left;
box.w -= if (box.w > total_x_padding) total_x_padding else box.w;
return box;
} }
inline fn from_client_box(_: *const Self, box_: Widget.Box, padding: Widget.Style.Margin) Widget.Box {
const total_y_padding = padding.top + padding.bottom;
const total_x_padding = padding.left + padding.right;
const y = if (box_.y < padding.top) padding.top else box_.y;
const x = if (box_.x < padding.left) padding.left else box_.x;
var box = box_;
box.y = y - padding.top;
box.h += total_y_padding;
box.x = x - padding.left;
box.w += total_x_padding;
return box;
}
fn prepare_resize_default(_: ?*anyopaque, _: *Self, box: Widget.Box) Widget.Box {
return box;
}
fn after_resize_default(_: ?*anyopaque, _: *Self, _: Widget.Box) void {}
fn on_layout_default(_: ?*anyopaque, self: *Self) Widget.Layout { fn on_layout_default(_: ?*anyopaque, self: *Self) Widget.Layout {
return self.layout_; return self.layout_;
} }
pub fn resize(self: *Self, pos_: Widget.Box) void { pub fn resize(self: *Self, box: Widget.Box) void {
self.box = pos_; return self.handle_resize(box);
var pos = pos_; }
self.plane.move_yx(@intCast(pos.y), @intCast(pos.x)) catch return;
self.plane.resize_simple(@intCast(pos.h), @intCast(pos.w)) catch return; fn do_resize(self: *Self, padding: Widget.Style.Margin) void {
const total = self.get_size_a(&pos).*; const client_box = self.to_client_box(self.deco_box, padding);
const deco_box = self.deco_box;
self.plane.move_yx(@intCast(deco_box.y), @intCast(deco_box.x)) catch return;
self.plane.resize_simple(@intCast(deco_box.h), @intCast(deco_box.w)) catch return;
const total = self.get_size_a_const(&client_box);
var avail = total; var avail = total;
var statics: usize = 0; var statics: usize = 0;
var dynamics: usize = 0; var dynamics: usize = 0;
@ -245,7 +368,7 @@ pub fn resize(self: *Self, pos_: Widget.Box) void {
const dyn_size = avail / if (dynamics > 0) dynamics else 1; const dyn_size = avail / if (dynamics > 0) dynamics else 1;
const rounded: usize = if (dyn_size * dynamics < avail) avail - dyn_size * dynamics else 0; const rounded: usize = if (dyn_size * dynamics < avail) avail - dyn_size * dynamics else 0;
var cur_loc: usize = self.get_loc_a(&pos).*; var cur_loc: usize = self.get_loc_a_const(&client_box);
var first = true; var first = true;
for (self.widgets.items) |*w| { for (self.widgets.items) |*w| {
@ -261,8 +384,8 @@ pub fn resize(self: *Self, pos_: Widget.Box) void {
self.get_loc_a(&w_pos).* = cur_loc; self.get_loc_a(&w_pos).* = cur_loc;
cur_loc += size; cur_loc += size;
self.get_size_b(&w_pos).* = self.get_size_b(&pos).*; self.get_size_b(&w_pos).* = self.get_size_b_const(&client_box);
self.get_loc_b(&w_pos).* = self.get_loc_b(&pos).*; self.get_loc_b(&w_pos).* = self.get_loc_b_const(&client_box);
w.widget.resize(w_pos); w.widget.resize(w_pos);
} }
} }

141
src/tui/WidgetStyle.zig Normal file
View file

@ -0,0 +1,141 @@
padding: Margin = Margin.@"0",
border: Border = Border.blank,
pub const WidgetType = @import("config").WidgetType;
pub const WidgetStyle = @import("config").WidgetStyle;
pub const Padding = struct {
pub const Unit = u16;
};
pub const Margin = struct {
const Unit = Padding.Unit;
top: Unit,
bottom: Unit,
left: Unit,
right: Unit,
const @"0": Margin = .{ .top = 0, .bottom = 0, .left = 0, .right = 0 };
const @"1": Margin = .{ .top = 1, .bottom = 1, .left = 1, .right = 1 };
const @"2": Margin = .{ .top = 2, .bottom = 2, .left = 2, .right = 2 };
const @"3": Margin = .{ .top = 3, .bottom = 3, .left = 3, .right = 3 };
const @"1/2": Margin = .{ .top = 1, .bottom = 1, .left = 2, .right = 2 };
const @"2/1": Margin = .{ .top = 2, .bottom = 2, .left = 1, .right = 1 };
const @"2/3": Margin = .{ .top = 2, .bottom = 2, .left = 3, .right = 3 };
const @"2/4": Margin = .{ .top = 2, .bottom = 2, .left = 4, .right = 4 };
const @"top/bottom/1": Margin = .{ .top = 1, .bottom = 1, .left = 0, .right = 0 };
const @"top/bottom/2": Margin = .{ .top = 2, .bottom = 2, .left = 0, .right = 0 };
const @"left/right/1": Margin = .{ .top = 0, .bottom = 0, .left = 1, .right = 1 };
const @"left/right/2": Margin = .{ .top = 0, .bottom = 0, .left = 2, .right = 2 };
};
pub const Border = struct {
nw: []const u8,
n: []const u8,
ne: []const u8,
e: []const u8,
se: []const u8,
s: []const u8,
sw: []const u8,
w: []const u8,
const blank: Border = .{ .nw = " ", .n = " ", .ne = " ", .e = " ", .se = " ", .s = " ", .sw = " ", .w = " " };
const box: Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"rounded box": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"double box": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"single/double box (top/bottom)": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"single/double box (left/right)": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"dotted box (braille)": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"thick box (half)": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"thick box (sextant)": Border = .{ .nw = "🬕", .n = "🬂", .ne = "🬨", .e = "", .se = "🬷", .s = "🬭", .sw = "🬲", .w = "" };
const @"thick box (octant)": Border = .{ .nw = "𜵊", .n = "🮂", .ne = "𜶘", .e = "", .se = "𜷕", .s = "", .sw = "𜷀", .w = "" };
const @"extra thick box": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"round thick box": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
};
const compact: @This() = .{};
const spacious: @This() = .{
.padding = Margin.@"1",
.border = Border.blank,
};
const boxed: @This() = .{
.padding = Margin.@"1",
.border = Border.box,
};
const rounded_boxed: @This() = .{
.padding = Margin.@"1",
.border = Border.@"rounded box",
};
const double_boxed: @This() = .{
.padding = Margin.@"1",
.border = Border.@"double box",
};
const single_double_top_bottom_boxed: @This() = .{
.padding = Margin.@"1",
.border = Border.@"single/double box (top/bottom)",
};
const single_double_left_right_boxed: @This() = .{
.padding = Margin.@"1",
.border = Border.@"single/double box (left/right)",
};
const dotted_boxed: @This() = .{
.padding = Margin.@"1",
.border = Border.@"dotted box (braille)",
};
const thick_boxed: @This() = .{
.padding = Margin.@"1/2",
.border = Border.@"thick box (octant)",
};
const extra_thick_boxed: @This() = .{
.padding = Margin.@"1/2",
.border = Border.@"extra thick box",
};
const bars_top_bottom: @This() = .{
.padding = Margin.@"top/bottom/1",
.border = Border.@"thick box (octant)",
};
const bars_left_right: @This() = .{
.padding = Margin.@"left/right/1",
.border = Border.@"thick box (octant)",
};
pub fn from_tag(tag: WidgetStyle) *const @This() {
return switch (tag) {
.compact => &compact,
.spacious => &spacious,
.boxed => &boxed,
.double_boxed => &double_boxed,
.rounded_boxed => &rounded_boxed,
.single_double_top_bottom_boxed => &single_double_top_bottom_boxed,
.single_double_left_right_boxed => &single_double_left_right_boxed,
.dotted_boxed => &dotted_boxed,
.thick_boxed => &thick_boxed,
.extra_thick_boxed => &extra_thick_boxed,
.bars_top_bottom => &bars_top_bottom,
.bars_left_right => &bars_left_right,
};
}
const Theme = @import("Widget.zig").Theme;
pub fn theme_style_from_type(style_type: WidgetType, theme: *const Theme) Theme.Style {
return switch (style_type) {
.none => theme.editor,
.palette => .{ .fg = theme.editor_widget_border.fg, .bg = theme.editor_widget.bg },
.panel => .{ .fg = theme.editor_widget_border.fg, .bg = theme.editor.bg },
.home => .{ .fg = theme.editor_widget_border.fg, .bg = theme.editor.bg },
};
}

View file

@ -55,6 +55,8 @@ pub const whitespace = struct {
}; };
}; };
pub const PosType = enum { column, byte };
pub const Match = struct { pub const Match = struct {
begin: Cursor = Cursor{}, begin: Cursor = Cursor{},
end: Cursor = Cursor{}, end: Cursor = Cursor{},
@ -351,6 +353,9 @@ pub const Editor = struct {
diag_hints: usize = 0, diag_hints: usize = 0,
completions: std.ArrayListUnmanaged(u8) = .empty, completions: std.ArrayListUnmanaged(u8) = .empty,
completion_row: usize = 0,
completion_col: usize = 0,
completion_is_complete: bool = true,
enable_auto_save: bool, enable_auto_save: bool,
enable_format_on_save: bool, enable_format_on_save: bool,
@ -432,6 +437,7 @@ pub const Editor = struct {
tp.extract_cbor(&cursels_cbor), tp.extract_cbor(&cursels_cbor),
})) }))
return error.RestoreStateMatch; return error.RestoreStateMatch;
self.refresh_tab_width();
if (op == .open_file) if (op == .open_file)
try self.open(file_path); try self.open(file_path);
self.clipboard = if (clipboard.len > 0) try self.allocator.dupe(u8, clipboard) else null; self.clipboard = if (clipboard.len > 0) try self.allocator.dupe(u8, clipboard) else null;
@ -468,7 +474,7 @@ pub const Editor = struct {
fn init(self: *Self, allocator: Allocator, n: Plane, buffer_manager: *Buffer.Manager) void { fn init(self: *Self, allocator: Allocator, n: Plane, buffer_manager: *Buffer.Manager) void {
const logger = log.logger("editor"); const logger = log.logger("editor");
const frame_rate = tp.env.get().num("frame-rate"); const frame_rate = tp.env.get().num("frame-rate");
const tab_width = tui.config().tab_width; const tab_width = tui.get_tab_width();
const indent_mode = tui.config().indent_mode; const indent_mode = tui.config().indent_mode;
const indent_size = if (indent_mode == .tabs) tab_width else tui.config().indent_size; const indent_size = if (indent_mode == .tabs) tab_width else tui.config().indent_size;
self.* = Self{ self.* = Self{
@ -702,6 +708,23 @@ pub const Editor = struct {
return; return;
} }
fn refresh_tab_width(self: *Self) void {
self.metrics = self.plane.metrics(self.tab_width);
switch (self.indent_mode) {
.spaces, .auto => {},
.tabs => self.indent_size = self.tab_width,
}
}
pub fn set_editor_tab_width(self: *Self, ctx: Context) Result {
var tab_width: usize = 0;
if (!try ctx.args.match(.{tp.extract(&tab_width)}))
return error.InvalidSetTabWidthArgument;
self.tab_width = tab_width;
self.refresh_tab_width();
}
pub const set_editor_tab_width_meta: Meta = .{ .arguments = &.{.integer} };
fn close(self: *Self) !void { fn close(self: *Self) !void {
var meta = std.ArrayListUnmanaged(u8).empty; var meta = std.ArrayListUnmanaged(u8).empty;
defer meta.deinit(self.allocator); defer meta.deinit(self.allocator);
@ -4457,7 +4480,9 @@ pub const Editor = struct {
}; };
} }
fn cursel_smart_insert_line(self: *Self, root: Buffer.Root, cursel: *CurSel, b_allocator: std.mem.Allocator) !Buffer.Root { const WSCollapseMode = enum { leave_ws, collapse_ws };
fn cursel_smart_insert_line(self: *Self, root: Buffer.Root, cursel: *CurSel, b_allocator: std.mem.Allocator, mode: WSCollapseMode) !Buffer.Root {
const row = cursel.cursor.row; const row = cursel.cursor.row;
const leading_ws = @min(find_first_non_ws(root, row, self.metrics), cursel.cursor.col); const leading_ws = @min(find_first_non_ws(root, row, self.metrics), cursel.cursor.col);
var sfa = std.heap.stackFallback(512, self.allocator); var sfa = std.heap.stackFallback(512, self.allocator);
@ -4468,7 +4493,8 @@ pub const Editor = struct {
_ = try writer.write("\n"); _ = try writer.write("\n");
try self.generate_leading_ws(&writer, leading_ws); try self.generate_leading_ws(&writer, leading_ws);
var root_ = try self.insert(root, cursel, stream.items, b_allocator); var root_ = try self.insert(root, cursel, stream.items, b_allocator);
root_ = self.collapse_trailing_ws_line(root_, row, b_allocator); if (mode == .collapse_ws)
root_ = self.collapse_trailing_ws_line(root_, row, b_allocator);
const leading_ws_ = find_first_non_ws(root_, cursel.cursor.row, self.metrics); const leading_ws_ = find_first_non_ws(root_, cursel.cursor.row, self.metrics);
if (leading_ws_ > leading_ws and leading_ws_ > cursel.cursor.col) { if (leading_ws_ > leading_ws and leading_ws_ > cursel.cursor.col) {
const sel = try cursel.enable_selection(root_, self.metrics); const sel = try cursel.enable_selection(root_, self.metrics);
@ -4499,11 +4525,11 @@ pub const Editor = struct {
(std.mem.eql(u8, egc_right, "(") and std.mem.eql(u8, egc_left, ")")); (std.mem.eql(u8, egc_right, "(") and std.mem.eql(u8, egc_left, ")"));
}; };
root = try self.cursel_smart_insert_line(root, cursel, b.allocator); root = try self.cursel_smart_insert_line(root, cursel, b.allocator, .collapse_ws);
if (smart_brace_indent) { if (smart_brace_indent) {
const cursor = cursel.cursor; const cursor = cursel.cursor;
root = try self.cursel_smart_insert_line(root, cursel, b.allocator); root = try self.cursel_smart_insert_line(root, cursel, b.allocator, .leave_ws);
cursel.cursor = cursor; cursel.cursor = cursor;
if (indent_extra) if (indent_extra)
root = try self.indent_cursel(root, cursel, b.allocator); root = try self.indent_cursel(root, cursel, b.allocator);
@ -5389,11 +5415,18 @@ pub const Editor = struct {
var column: usize = 0; var column: usize = 0;
var have_sel: bool = false; var have_sel: bool = false;
var sel: Selection = .{}; var sel: Selection = .{};
var pos_type: PosType = .column;
if (try ctx.args.match(.{ if (try ctx.args.match(.{
tp.extract(&line), tp.extract(&line),
tp.extract(&column), tp.extract(&column),
})) { })) {
// self.logger.print("goto: l:{d} c:{d}", .{ line, column }); // self.logger.print("goto: l:{d} c:{d}", .{ line, column });
} else if (try ctx.args.match(.{
tp.extract(&line),
tp.extract(&column),
tp.extract(&pos_type),
})) {
// self.logger.print("goto: l:{d} c:{d}", .{ line, column });
} else if (try ctx.args.match(.{ } else if (try ctx.args.match(.{
tp.extract(&line), tp.extract(&line),
tp.extract(&column), tp.extract(&column),
@ -5404,9 +5437,29 @@ pub const Editor = struct {
})) { })) {
// self.logger.print("goto: l:{d} c:{d} {any}", .{ line, column, sel }); // self.logger.print("goto: l:{d} c:{d} {any}", .{ line, column, sel });
have_sel = true; have_sel = true;
} else if (try ctx.args.match(.{
tp.extract(&line),
tp.extract(&column),
tp.extract(&sel.begin.row),
tp.extract(&sel.begin.col),
tp.extract(&sel.end.row),
tp.extract(&sel.end.col),
tp.extract(&pos_type),
})) {
// self.logger.print("goto: l:{d} c:{d} {any} {}", .{ line, column, sel, pos_type });
have_sel = true;
} else return error.InvalidGotoLineAndColumnArgument; } else return error.InvalidGotoLineAndColumnArgument;
self.cancel_all_selections(); self.cancel_all_selections();
const root = self.buf_root() catch return; const root = self.buf_root() catch return;
if (pos_type == .byte) {
column = root.pos_to_width(line - 1, column - 1, self.metrics) catch return;
column += 1;
if (have_sel) {
sel.begin.col = root.pos_to_width(sel.begin.row, sel.begin.col, self.metrics) catch return;
sel.end.col = root.pos_to_width(sel.end.row, sel.end.col, self.metrics) catch return;
}
// self.logger.print("goto_byte_pos: l:{d} c:{d} {any} {}", .{ line, column, sel, pos_type });
}
const primary = self.get_primary(); const primary = self.get_primary();
try primary.cursor.move_to( try primary.cursor.move_to(
root, root,
@ -5660,10 +5713,13 @@ pub const Editor = struct {
} }
pub fn add_completion(self: *Self, row: usize, col: usize, is_incomplete: bool, msg: tp.message) Result { pub fn add_completion(self: *Self, row: usize, col: usize, is_incomplete: bool, msg: tp.message) Result {
if (!(row == self.completion_row and col == self.completion_col)) {
self.completions.clearRetainingCapacity();
self.completion_row = row;
self.completion_col = col;
}
try self.completions.appendSlice(self.allocator, msg.buf); try self.completions.appendSlice(self.allocator, msg.buf);
_ = row; self.completion_is_complete = is_incomplete;
_ = col;
_ = is_incomplete;
} }
pub fn select(self: *Self, ctx: Context) Result { pub fn select(self: *Self, ctx: Context) Result {
@ -6029,6 +6085,8 @@ pub const EditorWidget = struct {
last_btn: input.Mouse = .none, last_btn: input.Mouse = .none,
last_btn_time_ms: i64 = 0, last_btn_time_ms: i64 = 0,
last_btn_count: usize = 0, last_btn_count: usize = 0,
last_btn_x: c_int = 0,
last_btn_y: c_int = 0,
hover: bool = false, hover: bool = false,
hover_timer: ?tp.Cancellable = null, hover_timer: ?tp.Cancellable = null,
@ -6193,9 +6251,16 @@ pub const EditorWidget = struct {
fn mouse_click_button1(self: *Self, y: c_int, x: c_int, _: c_int, xoffset: c_int) Result { fn mouse_click_button1(self: *Self, y: c_int, x: c_int, _: c_int, xoffset: c_int) Result {
const y_, const x_ = self.mouse_pos_abs(y, x, xoffset); const y_, const x_ = self.mouse_pos_abs(y, x, xoffset);
defer {
self.last_btn_y = y_;
self.last_btn_x = x_;
}
if (self.last_btn == input.mouse.BUTTON1) { if (self.last_btn == input.mouse.BUTTON1) {
const click_time_ms = time.milliTimestamp() - self.last_btn_time_ms; const click_time_ms = time.milliTimestamp() - self.last_btn_time_ms;
if (click_time_ms <= double_click_time_ms) { if (click_time_ms <= double_click_time_ms and
self.last_btn_y == y_ and
self.last_btn_x == x_)
{
if (self.last_btn_count == 2) { if (self.last_btn_count == 2) {
self.last_btn_count = 3; self.last_btn_count = 3;
try self.editor.primary_triple_click(y_, x_); try self.editor.primary_triple_click(y_, x_);

View file

@ -33,8 +33,10 @@ view_rows: usize = 0,
view_cols: usize = 0, view_cols: usize = 0,
entries: std.ArrayList(Entry) = undefined, entries: std.ArrayList(Entry) = undefined,
selected: ?usize = null, selected: ?usize = null,
box: Widget.Box = .{},
const path_column_ratio = 4; const path_column_ratio = 4;
const widget_type: Widget.Type = .panel;
const Entry = struct { const Entry = struct {
path: []const u8, path: []const u8,
@ -44,6 +46,7 @@ const Entry = struct {
end_pos: usize, end_pos: usize,
lines: []const u8, lines: []const u8,
severity: editor.Diagnostic.Severity = .Information, severity: editor.Diagnostic.Severity = .Information,
pos_type: editor.PosType,
}; };
pub fn create(allocator: Allocator, parent: Plane) !Widget { pub fn create(allocator: Allocator, parent: Plane) !Widget {
@ -56,6 +59,7 @@ pub fn create(allocator: Allocator, parent: Plane) !Widget {
.entries = std.ArrayList(Entry).init(allocator), .entries = std.ArrayList(Entry).init(allocator),
.menu = try Menu.create(*Self, allocator, tui.plane(), .{ .menu = try Menu.create(*Self, allocator, tui.plane(), .{
.ctx = self, .ctx = self,
.style = widget_type,
.on_render = handle_render_menu, .on_render = handle_render_menu,
.on_scroll = EventHandler.bind(self, Self.handle_scroll), .on_scroll = EventHandler.bind(self, Self.handle_scroll),
.on_click4 = mouse_click_button4, .on_click4 = mouse_click_button4,
@ -84,11 +88,14 @@ fn scrollbar_style(sb: *scrollbar_v, theme: *const Widget.Theme) Widget.Theme.St
} }
pub fn handle_resize(self: *Self, pos: Widget.Box) void { pub fn handle_resize(self: *Self, pos: Widget.Box) void {
const padding = tui.get_widget_style(widget_type).padding;
self.plane.move_yx(@intCast(pos.y), @intCast(pos.x)) catch return; self.plane.move_yx(@intCast(pos.y), @intCast(pos.x)) catch return;
self.plane.resize_simple(@intCast(pos.h), @intCast(pos.w)) catch return; self.plane.resize_simple(@intCast(pos.h), @intCast(pos.w)) catch return;
self.menu.container_widget.resize(pos); self.box = pos;
self.view_rows = pos.h; self.menu.container.resize(self.box);
self.view_cols = pos.w; const client_box = self.menu.container.to_client_box(pos, padding);
self.view_rows = client_box.h;
self.view_cols = client_box.w;
self.update_scrollbar(); self.update_scrollbar();
} }
@ -107,7 +114,7 @@ pub fn add_item(self: *Self, entry_: Entry) !void {
const writer = label.writer(); const writer = label.writer();
cbor.writeValue(writer, idx) catch return; cbor.writeValue(writer, idx) catch return;
self.menu.add_item_with_handler(label.items, handle_menu_action) catch return; self.menu.add_item_with_handler(label.items, handle_menu_action) catch return;
self.menu.container_widget.resize(Widget.Box.from(self.plane)); self.menu.resize(self.box);
self.update_scrollbar(); self.update_scrollbar();
} }
@ -160,8 +167,8 @@ fn handle_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), th
button.plane.home(); button.plane.home();
} }
const entry = &self.entries.items[idx]; const entry = &self.entries.items[idx];
const pointer = if (selected) "" else " "; button.plane.set_style(style_label);
_ = button.plane.print("{s} ", .{pointer}) catch {}; tui.render_pointer(&button.plane, selected);
var buf: [std.fs.max_path_bytes]u8 = undefined; var buf: [std.fs.max_path_bytes]u8 = undefined;
var removed_prefix: usize = 0; var removed_prefix: usize = 0;
const max_len = self.view_cols / path_column_ratio; const max_len = self.view_cols / path_column_ratio;
@ -244,6 +251,7 @@ fn handle_menu_action(menu: **Menu.State(*Self), button: *Button.State(*Menu.Sta
if (entry.begin_pos == 0) 0 else entry.begin_pos + 1, if (entry.begin_pos == 0) 0 else entry.begin_pos + 1,
entry.end_line, entry.end_line,
entry.end_pos + 1, entry.end_pos + 1,
entry.pos_type,
}, },
} }) catch |e| self.logger.err("navigate", e); } }) catch |e| self.logger.err("navigate", e);
} }

View file

@ -30,7 +30,7 @@ const style = struct {
\\open_recent_project \\open_recent_project
\\find_in_files \\find_in_files
\\open_command_palette \\open_command_palette
\\select_task \\run_task
\\add_task \\add_task
\\open_config \\open_config
\\open_gui_config \\open_gui_config
@ -48,7 +48,7 @@ const style = struct {
\\open_recent_project \\open_recent_project
\\find_in_files \\find_in_files
\\open_command_palette \\open_command_palette
\\select_task \\run_task
\\add_task \\add_task
\\open_config \\open_config
\\open_keybind_config \\open_keybind_config
@ -70,6 +70,8 @@ fire: ?Fire = null,
commands: Commands = undefined, commands: Commands = undefined,
menu: *Menu.State(*Self), menu: *Menu.State(*Self),
menu_w: usize = 0, menu_w: usize = 0,
menu_label_max: usize = 0,
menu_count: usize = 0,
menu_len: usize = 0, menu_len: usize = 0,
max_desc_len: usize = 0, max_desc_len: usize = 0,
input_namespace: []const u8, input_namespace: []const u8,
@ -79,6 +81,8 @@ home_style_bufs: [][]const u8,
const Self = @This(); const Self = @This();
const widget_type: Widget.Type = .home;
pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget { pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
const logger = log.logger("home"); const logger = log.logger("home");
const self = try allocator.create(Self); const self = try allocator.create(Self);
@ -95,7 +99,11 @@ pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
.allocator = allocator, .allocator = allocator,
.parent = parent.plane.*, .parent = parent.plane.*,
.plane = n, .plane = n,
.menu = try Menu.create(*Self, allocator, w.plane.*, .{ .ctx = self, .on_render = menu_on_render }), .menu = try Menu.create(*Self, allocator, w.plane.*, .{
.ctx = self,
.style = widget_type,
.on_render = menu_on_render,
}),
.input_namespace = keybind.get_namespace(), .input_namespace = keybind.get_namespace(),
.home_style = home_style, .home_style = home_style,
.home_style_bufs = home_style_bufs, .home_style_bufs = home_style_bufs,
@ -103,7 +111,6 @@ pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
try self.commands.init(self); try self.commands.init(self);
var it = std.mem.splitAny(u8, self.home_style.menu_commands, "\n "); var it = std.mem.splitAny(u8, self.home_style.menu_commands, "\n ");
while (it.next()) |command_name| { while (it.next()) |command_name| {
self.menu_len += 1;
const id = command.get_id(command_name) orelse { const id = command.get_id(command_name) orelse {
logger.print("{s} is not defined", .{command_name}); logger.print("{s} is not defined", .{command_name});
continue; continue;
@ -112,11 +119,14 @@ pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
logger.print("{s} has no description", .{command_name}); logger.print("{s} has no description", .{command_name});
continue; continue;
}; };
self.menu_count += 1;
var hints = std.mem.splitScalar(u8, keybind_mode.keybind_hints.get(command_name) orelse "", ','); var hints = std.mem.splitScalar(u8, keybind_mode.keybind_hints.get(command_name) orelse "", ',');
const hint = hints.first(); const hint = hints.first();
self.max_desc_len = @max(self.max_desc_len, description.len + hint.len + 5); self.max_desc_len = @max(self.max_desc_len, description.len + hint.len + 5);
try self.add_menu_command(command_name, description, hint, self.menu); try self.add_menu_command(command_name, description, hint, self.menu);
} }
const padding = tui.get_widget_style(widget_type).padding;
self.menu_len = self.menu_count + padding.top + padding.bottom;
self.position_menu(15, 9); self.position_menu(15, 9);
return w; return w;
} }
@ -145,7 +155,9 @@ fn add_menu_command(self: *Self, command_name: []const u8, description: []const
_ = try writer.write(leader); _ = try writer.write(leader);
try writer.print(" :{s}", .{hint}); try writer.print(" :{s}", .{hint});
const label = fis.getWritten(); const label = fis.getWritten();
self.menu_w = @max(self.menu_w, label.len + 1); const padding = tui.get_widget_style(widget_type).padding;
self.menu_label_max = @max(self.menu_label_max, label.len);
self.menu_w = self.menu_label_max + 2 + padding.left + padding.right;
} }
var value = std.ArrayList(u8).init(self.allocator); var value = std.ArrayList(u8).init(self.allocator);
@ -228,8 +240,8 @@ fn menu_on_render(self: *Self, button: *Button.State(*Menu.State(*Self)), theme:
} else { } else {
button.plane.set_style_bg_transparent(style_text); button.plane.set_style_bg_transparent(style_text);
} }
const pointer = if (selected) "" else " "; tui.render_pointer(&button.plane, selected);
_ = button.plane.print("{s}{s}", .{ pointer, description }) catch {}; _ = button.plane.print("{s}", .{description}) catch {};
if (button.active or button.hover or selected) { if (button.active or button.hover or selected) {
button.plane.set_style(style_leader); button.plane.set_style(style_leader);
} else { } else {
@ -323,13 +335,13 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
_ = self.plane.print("{s}", .{debug_warning_text}) catch return false; _ = self.plane.print("{s}", .{debug_warning_text}) catch return false;
} }
const more = self.menu.render(theme); const more = self.menu.container.render(theme);
return more or self.fire != null; return more or self.fire != null;
} }
fn position_menu(self: *Self, y: usize, x: usize) void { fn position_menu(self: *Self, y: usize, x: usize) void {
const box = Widget.Box.from(self.plane); const box = Widget.Box.from(self.plane);
self.menu.resize(.{ .y = box.y + y, .x = box.x + x, .w = self.menu_w }); self.menu.resize(.{ .y = box.y + y, .x = box.x + x, .w = self.menu_w, .h = self.menu_len });
} }
fn center(self: *Self, non_centered: usize, w: usize) usize { fn center(self: *Self, non_centered: usize, w: usize) usize {
@ -388,6 +400,16 @@ const cmds = struct {
} }
pub const home_menu_activate_meta: Meta = .{}; pub const home_menu_activate_meta: Meta = .{};
pub fn home_next_widget_style(self: *Self, _: Ctx) Result {
tui.set_next_style(widget_type);
const padding = tui.get_widget_style(widget_type).padding;
self.menu_len = self.menu_count + padding.top + padding.bottom;
self.menu_w = self.menu_label_max + 2 + padding.left + padding.right;
tui.need_render();
try tui.save_config();
}
pub const home_next_widget_style_meta: Meta = .{};
pub fn home_sheeran(self: *Self, _: Ctx) Result { pub fn home_sheeran(self: *Self, _: Ctx) Result {
self.fire = if (self.fire) |*fire| ret: { self.fire = if (self.fire) |*fire| ret: {
fire.deinit(); fire.deinit();

View file

@ -322,6 +322,7 @@ const cmds = struct {
if (!try ctx.args.match(.{tp.extract(&project_dir)})) if (!try ctx.args.match(.{tp.extract(&project_dir)}))
return; return;
try self.check_all_not_dirty(); try self.check_all_not_dirty();
try project_manager.open(project_dir);
for (self.editors.items) |editor| { for (self.editors.items) |editor| {
editor.clear_diagnostics(); editor.clear_diagnostics();
try editor.close_file(.{}); try editor.close_file(.{});
@ -332,7 +333,6 @@ const cmds = struct {
try self.toggle_panel_view(filelist_view, false); try self.toggle_panel_view(filelist_view, false);
self.buffer_manager.deinit(); self.buffer_manager.deinit();
self.buffer_manager = Buffer.Manager.init(self.allocator); self.buffer_manager = Buffer.Manager.init(self.allocator);
try project_manager.open(project_dir);
const project = tp.env.get().str("project"); const project = tp.env.get().str("project");
tui.rdr().set_terminal_working_directory(project); tui.rdr().set_terminal_working_directory(project);
if (self.top_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" }); if (self.top_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" });
@ -827,8 +827,11 @@ const cmds = struct {
tp.more, tp.more,
})) return error.InvalidAddDiagnosticArgument; })) return error.InvalidAddDiagnosticArgument;
file_path = project_manager.normalize_file_path(file_path); file_path = project_manager.normalize_file_path(file_path);
if (self.get_active_editor()) |editor| if (std.mem.eql(u8, file_path, editor.file_path orelse "")) if (self.get_active_editor()) |editor| {
try editor.add_completion(row, col, is_incomplete, ctx.args); if (std.mem.eql(u8, file_path, editor.file_path orelse ""))
try editor.add_completion(row, col, is_incomplete, ctx.args);
try tui.open_overlay(@import("mode/overlay/completion_palette.zig").Type);
}
} }
pub const add_completion_meta: Meta = .{ pub const add_completion_meta: Meta = .{
.arguments = &.{ .arguments = &.{
@ -938,6 +941,12 @@ const cmds = struct {
} }
pub const open_previous_file_meta: Meta = .{ .description = "Open the previous file" }; pub const open_previous_file_meta: Meta = .{ .description = "Open the previous file" };
pub fn open_most_recent_file(self: *Self, _: Ctx) Result {
if (try project_manager.request_most_recent_file(self.allocator)) |file_path|
self.show_file_async(file_path);
}
pub const open_most_recent_file_meta: Meta = .{ .description = "Open the last changed file" };
pub fn system_paste(self: *Self, _: Ctx) Result { pub fn system_paste(self: *Self, _: Ctx) Result {
if (builtin.os.tag == .windows) { if (builtin.os.tag == .windows) {
const text = try @import("renderer").request_windows_clipboard(self.allocator); const text = try @import("renderer").request_windows_clipboard(self.allocator);
@ -1315,7 +1324,7 @@ pub fn write_restore_info(self: *Self) void {
fn read_restore_info(self: *Self) !void { fn read_restore_info(self: *Self) !void {
const file_name = try root.get_restore_file_name(); const file_name = try root.get_restore_file_name();
const file = try std.fs.cwd().openFile(file_name, .{ .mode = .read_only }); const file = try std.fs.openFileAbsolute(file_name, .{ .mode = .read_only });
defer file.close(); defer file.close();
const stat = try file.stat(); const stat = try file.stat();
var buf = try self.allocator.alloc(u8, @intCast(stat.size)); var buf = try self.allocator.alloc(u8, @intCast(stat.size));
@ -1414,6 +1423,7 @@ fn add_find_in_files_result(
.end_pos = @max(1, end_pos) - 1, .end_pos = @max(1, end_pos) - 1,
.lines = lines, .lines = lines,
.severity = severity, .severity = severity,
.pos_type = .byte,
}) catch |e| return tp.exit_error(e, @errorReturnTrace()); }) catch |e| return tp.exit_error(e, @errorReturnTrace());
} }

View file

@ -2,6 +2,7 @@ const std = @import("std");
const tp = @import("thespian"); const tp = @import("thespian");
const cbor = @import("cbor"); const cbor = @import("cbor");
const log = @import("log"); const log = @import("log");
const file_type_config = @import("file_type_config");
const root = @import("root"); const root = @import("root");
const input = @import("input"); const input = @import("input");
@ -20,6 +21,7 @@ pub fn Create(options: type) type {
return struct { return struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
file_path: std.ArrayList(u8), file_path: std.ArrayList(u8),
rendered_mini_buffer: std.ArrayListUnmanaged(u8) = .empty,
query: std.ArrayList(u8), query: std.ArrayList(u8),
match: std.ArrayList(u8), match: std.ArrayList(u8),
entries: std.ArrayList(Entry), entries: std.ArrayList(Entry),
@ -33,8 +35,12 @@ pub fn Create(options: type) type {
const Entry = struct { const Entry = struct {
name: []const u8, name: []const u8,
type: enum { dir, file, link }, type: EntryType,
file_type: []const u8,
icon: []const u8,
color: u24,
}; };
const EntryType = enum { dir, file, link };
pub fn create(allocator: std.mem.Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } { pub fn create(allocator: std.mem.Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } {
const self = try allocator.create(Self); const self = try allocator.create(Self);
@ -66,6 +72,7 @@ pub fn Create(options: type) type {
self.match.deinit(); self.match.deinit();
self.query.deinit(); self.query.deinit();
self.file_path.deinit(); self.file_path.deinit();
self.rendered_mini_buffer.deinit(self.allocator);
self.allocator.destroy(self); self.allocator.destroy(self);
} }
@ -80,7 +87,11 @@ pub fn Create(options: type) type {
} }
fn clear_entries(self: *Self) void { fn clear_entries(self: *Self) void {
for (self.entries.items) |entry| self.allocator.free(entry.name); for (self.entries.items) |entry| {
self.allocator.free(entry.name);
self.allocator.free(entry.file_type);
self.allocator.free(entry.icon);
}
self.entries.clearRetainingCapacity(); self.entries.clearRetainingCapacity();
} }
@ -112,14 +123,11 @@ pub fn Create(options: type) type {
self.complete_trigger_count = 0; self.complete_trigger_count = 0;
self.file_path.clearRetainingCapacity(); self.file_path.clearRetainingCapacity();
if (self.match.items.len > 0) { if (self.match.items.len > 0) {
try self.construct_path(self.query.items, .{ .name = self.match.items, .type = .file }, 0); try self.construct_path(self.query.items, self.match.items, .file, 0);
} else { } else {
try self.file_path.appendSlice(self.query.items); try self.file_path.appendSlice(self.query.items);
} }
if (tui.mini_mode()) |mini_mode| { self.update_mini_mode_text();
mini_mode.text = self.file_path.items;
mini_mode.cursor = tui.egc_chunk_width(self.file_path.items, 0, 1);
}
return; return;
} }
self.complete_trigger_count -= 1; self.complete_trigger_count -= 1;
@ -139,12 +147,7 @@ pub fn Create(options: type) type {
} }
fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void { fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void {
defer { defer self.update_mini_mode_text();
if (tui.mini_mode()) |mini_mode| {
mini_mode.text = self.file_path.items;
mini_mode.cursor = tui.egc_chunk_width(self.file_path.items, 0, 1);
}
}
var count: usize = undefined; var count: usize = undefined;
if (try cbor.match(m.buf, .{ "PRJ", "path_entry", tp.more })) { if (try cbor.match(m.buf, .{ "PRJ", "path_entry", tp.more })) {
return self.process_path_entry(m); return self.process_path_entry(m);
@ -158,18 +161,31 @@ pub fn Create(options: type) type {
fn process_path_entry(self: *Self, m: tp.message) MessageFilter.Error!void { fn process_path_entry(self: *Self, m: tp.message) MessageFilter.Error!void {
var path: []const u8 = undefined; var path: []const u8 = undefined;
var file_name: []const u8 = undefined; var file_name: []const u8 = undefined;
if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "DIR", tp.extract(&file_name) })) { var file_type: []const u8 = undefined;
(try self.entries.addOne()).* = .{ .name = try self.allocator.dupe(u8, file_name), .type = .dir }; var icon: []const u8 = undefined;
} else if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "LINK", tp.extract(&file_name) })) { var color: u24 = undefined;
(try self.entries.addOne()).* = .{ .name = try self.allocator.dupe(u8, file_name), .type = .link }; if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "DIR", tp.extract(&file_name), tp.extract(&file_type), tp.extract(&icon), tp.extract(&color) })) {
} else if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "FILE", tp.extract(&file_name) })) { try self.add_entry(file_name, .dir, file_type, icon, color);
(try self.entries.addOne()).* = .{ .name = try self.allocator.dupe(u8, file_name), .type = .file }; } else if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "LINK", tp.extract(&file_name), tp.extract(&file_type), tp.extract(&icon), tp.extract(&color) })) {
try self.add_entry(file_name, .link, file_type, icon, color);
} else if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "FILE", tp.extract(&file_name), tp.extract(&file_type), tp.extract(&icon), tp.extract(&color) })) {
try self.add_entry(file_name, .file, file_type, icon, color);
} else { } else {
log.logger("file_browser").err("receive", tp.unexpected(m)); log.logger("file_browser").err("receive", tp.unexpected(m));
} }
tui.need_render(); tui.need_render();
} }
fn add_entry(self: *Self, file_name: []const u8, entry_type: EntryType, file_type: []const u8, icon: []const u8, color: u24) !void {
(try self.entries.addOne()).* = .{
.name = try self.allocator.dupe(u8, file_name),
.type = entry_type,
.file_type = try self.allocator.dupe(u8, file_type),
.icon = try self.allocator.dupe(u8, icon),
.color = color,
};
}
fn do_complete(self: *Self) !void { fn do_complete(self: *Self) !void {
self.complete_trigger_count = @min(self.complete_trigger_count, self.entries.items.len); self.complete_trigger_count = @min(self.complete_trigger_count, self.entries.items.len);
self.file_path.clearRetainingCapacity(); self.file_path.clearRetainingCapacity();
@ -179,9 +195,10 @@ pub fn Create(options: type) type {
if (self.total_matches == 1) if (self.total_matches == 1)
self.complete_trigger_count = 0; self.complete_trigger_count = 0;
} else if (self.entries.items.len > 0) { } else if (self.entries.items.len > 0) {
try self.construct_path(self.query.items, self.entries.items[self.complete_trigger_count - 1], self.complete_trigger_count - 1); const entry = self.entries.items[self.complete_trigger_count - 1];
try self.construct_path(self.query.items, entry.name, entry.type, self.complete_trigger_count - 1);
} else { } else {
try self.construct_path(self.query.items, .{ .name = "", .type = .file }, 0); try self.construct_path(self.query.items, "", .file, 0);
} }
if (self.match.items.len > 0) if (self.match.items.len > 0)
if (self.total_matches > 1) if (self.total_matches > 1)
@ -192,14 +209,14 @@ pub fn Create(options: type) type {
message("{d}/{d}", .{ self.matched_entry + 1, self.entries.items.len }); message("{d}/{d}", .{ self.matched_entry + 1, self.entries.items.len });
} }
fn construct_path(self: *Self, path_: []const u8, entry: Entry, entry_no: usize) error{OutOfMemory}!void { fn construct_path(self: *Self, path_: []const u8, entry_name: []const u8, entry_type: EntryType, entry_no: usize) error{OutOfMemory}!void {
self.matched_entry = entry_no; self.matched_entry = entry_no;
const path = project_manager.normalize_file_path(path_); const path = project_manager.normalize_file_path(path_);
try self.file_path.appendSlice(path); try self.file_path.appendSlice(path);
if (path.len > 0 and path[path.len - 1] != std.fs.path.sep) if (path.len > 0 and path[path.len - 1] != std.fs.path.sep)
try self.file_path.append(std.fs.path.sep); try self.file_path.append(std.fs.path.sep);
try self.file_path.appendSlice(entry.name); try self.file_path.appendSlice(entry_name);
if (entry.type == .dir) if (entry_type == .dir)
try self.file_path.append(std.fs.path.sep); try self.file_path.append(std.fs.path.sep);
} }
@ -212,7 +229,7 @@ pub fn Create(options: type) type {
if (try prefix_compare_icase(self.allocator, self.match.items, entry.name)) { if (try prefix_compare_icase(self.allocator, self.match.items, entry.name)) {
matched += 1; matched += 1;
if (matched == self.complete_trigger_count) { if (matched == self.complete_trigger_count) {
try self.construct_path(self.query.items, entry, i); try self.construct_path(self.query.items, entry.name, entry.type, i);
found_match = i; found_match = i;
} }
last = entry; last = entry;
@ -222,11 +239,11 @@ pub fn Create(options: type) type {
self.total_matches = matched; self.total_matches = matched;
if (found_match) |_| return; if (found_match) |_| return;
if (last) |entry| { if (last) |entry| {
try self.construct_path(self.query.items, entry, last_no); try self.construct_path(self.query.items, entry.name, entry.type, last_no);
self.complete_trigger_count = matched; self.complete_trigger_count = matched;
} else { } else {
message("no match for '{s}'", .{self.match.items}); message("no match for '{s}'", .{self.match.items});
try self.construct_path(self.query.items, .{ .name = self.match.items, .type = .file }, 0); try self.construct_path(self.query.items, self.match.items, .file, 0);
} }
} }
@ -264,8 +281,15 @@ pub fn Create(options: type) type {
fn update_mini_mode_text(self: *Self) void { fn update_mini_mode_text(self: *Self) void {
if (tui.mini_mode()) |mini_mode| { if (tui.mini_mode()) |mini_mode| {
mini_mode.text = self.file_path.items; const icon = if (self.entries.items.len > 0 and self.complete_trigger_count > 0)
mini_mode.cursor = tui.egc_chunk_width(self.file_path.items, 0, 1); self.entries.items[self.complete_trigger_count - 1].icon
else
" ";
self.rendered_mini_buffer.clearRetainingCapacity();
const writer = self.rendered_mini_buffer.writer(self.allocator);
writer.print("{s} {s}", .{ icon, self.file_path.items }) catch {};
mini_mode.text = self.rendered_mini_buffer.items;
mini_mode.cursor = tui.egc_chunk_width(self.file_path.items, 0, 1) + 3;
} }
} }

View file

@ -1,140 +1,23 @@
const tp = @import("thespian");
const key = @import("renderer").input.key;
const mod = @import("renderer").input.modifier;
const event_type = @import("renderer").input.event_type;
const keybind = @import("keybind");
const command = @import("command"); const command = @import("command");
const EventHandler = @import("EventHandler");
const tui = @import("../../tui.zig"); const tui = @import("../../tui.zig");
const Allocator = @import("std").mem.Allocator; pub const Type = @import("numeric_input.zig").Create(@This());
const fmt = @import("std").fmt; pub const create = Type.create;
const Self = @This(); pub fn name(_: *Type) []const u8 {
const name = "goto"; return "goto";
const Commands = command.Collection(cmds);
allocator: Allocator,
buf: [30]u8 = undefined,
input: ?usize = null,
start: usize,
commands: Commands = undefined,
pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } {
const editor = tui.get_active_editor() orelse return error.NotFound;
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.start = editor.get_primary().cursor.row + 1,
};
try self.commands.init(self);
var mode = try keybind.mode("mini/goto", allocator, .{
.insert_command = "mini_mode_insert_bytes",
});
mode.event_handler = EventHandler.to_owned(self);
return .{ mode, .{ .name = name } };
} }
pub fn deinit(self: *Self) void { pub fn start(_: *Type) usize {
self.commands.deinit(); const editor = tui.get_active_editor() orelse return 1;
self.allocator.destroy(self); return editor.get_primary().cursor.row + 1;
} }
pub fn receive(self: *Self, _: tp.pid_ref, _: tp.message) error{Exit}!bool { pub const preview = goto;
self.update_mini_mode_text(); pub const apply = goto;
return false; pub const cancel = goto;
}
fn update_mini_mode_text(self: *Self) void { fn goto(self: *Type, _: command.Context) void {
if (tui.mini_mode()) |mini_mode| {
mini_mode.text = if (self.input) |linenum|
(fmt.bufPrint(&self.buf, "{d}", .{linenum}) catch "")
else
"";
mini_mode.cursor = tui.egc_chunk_width(mini_mode.text, 0, 1);
}
}
fn goto(self: *Self) void {
command.executeName("goto_line", command.fmt(.{self.input orelse self.start})) catch {}; command.executeName("goto_line", command.fmt(.{self.input orelse self.start})) catch {};
} }
fn insert_char(self: *Self, char: u8) void {
switch (char) {
'0' => {
if (self.input) |linenum| self.input = linenum * 10;
},
'1'...'9' => {
const digit: usize = @intCast(char - '0');
self.input = if (self.input) |x| x * 10 + digit else digit;
},
else => {},
}
}
fn insert_bytes(self: *Self, bytes: []const u8) void {
for (bytes) |c| self.insert_char(c);
}
const cmds = struct {
pub const Target = Self;
const Ctx = command.Context;
const Meta = command.Metadata;
const Result = command.Result;
pub fn mini_mode_reset(self: *Self, _: Ctx) Result {
self.input = null;
self.update_mini_mode_text();
}
pub const mini_mode_reset_meta: Meta = .{ .description = "Clear input" };
pub fn mini_mode_cancel(self: *Self, _: Ctx) Result {
self.input = null;
self.update_mini_mode_text();
self.goto();
command.executeName("exit_mini_mode", .{}) catch {};
}
pub const mini_mode_cancel_meta: Meta = .{ .description = "Cancel input" };
pub fn mini_mode_delete_backwards(self: *Self, _: Ctx) Result {
if (self.input) |linenum| {
const newval = if (linenum < 10) 0 else linenum / 10;
self.input = if (newval == 0) null else newval;
self.update_mini_mode_text();
self.goto();
}
}
pub const mini_mode_delete_backwards_meta: Meta = .{ .description = "Delete backwards" };
pub fn mini_mode_insert_code_point(self: *Self, ctx: Ctx) Result {
var keypress: usize = 0;
if (!try ctx.args.match(.{tp.extract(&keypress)}))
return error.InvalidGotoInsertCodePointArgument;
switch (keypress) {
'0'...'9' => self.insert_char(@intCast(keypress)),
else => {},
}
self.update_mini_mode_text();
self.goto();
}
pub const mini_mode_insert_code_point_meta: Meta = .{ .arguments = &.{.integer} };
pub fn mini_mode_insert_bytes(self: *Self, ctx: Ctx) Result {
var bytes: []const u8 = undefined;
if (!try ctx.args.match(.{tp.extract(&bytes)}))
return error.InvalidGotoInsertBytesArgument;
self.insert_bytes(bytes);
self.update_mini_mode_text();
self.goto();
}
pub const mini_mode_insert_bytes_meta: Meta = .{ .arguments = &.{.string} };
pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result {
return mini_mode_insert_bytes(self, ctx);
}
pub const mini_mode_paste_meta: Meta = .{ .arguments = &.{.string} };
};

View file

@ -0,0 +1,148 @@
const tp = @import("thespian");
const key = @import("renderer").input.key;
const mod = @import("renderer").input.modifier;
const event_type = @import("renderer").input.event_type;
const keybind = @import("keybind");
const command = @import("command");
const EventHandler = @import("EventHandler");
const tui = @import("../../tui.zig");
const Allocator = @import("std").mem.Allocator;
const fmt = @import("std").fmt;
pub fn Create(options: type) type {
return struct {
const Self = @This();
const Commands = command.Collection(cmds);
allocator: Allocator,
buf: [30]u8 = undefined,
input: ?usize = null,
start: usize,
ctx: command.Context,
commands: Commands = undefined,
pub fn create(allocator: Allocator, ctx: command.Context) !struct { tui.Mode, tui.MiniMode } {
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.ctx = .{ .args = try ctx.args.clone(allocator) },
.start = 0,
};
self.start = options.start(self);
try self.commands.init(self);
var mode = try keybind.mode("mini/numeric", allocator, .{
.insert_command = "mini_mode_insert_bytes",
});
mode.event_handler = EventHandler.to_owned(self);
return .{ mode, .{ .name = options.name(self) } };
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.ctx.args.buf);
self.commands.deinit();
self.allocator.destroy(self);
}
pub fn receive(self: *Self, _: tp.pid_ref, _: tp.message) error{Exit}!bool {
self.update_mini_mode_text();
return false;
}
fn update_mini_mode_text(self: *Self) void {
if (tui.mini_mode()) |mini_mode| {
mini_mode.text = if (self.input) |linenum|
(fmt.bufPrint(&self.buf, "{d}", .{linenum}) catch "")
else
"";
mini_mode.cursor = tui.egc_chunk_width(mini_mode.text, 0, 1);
}
}
fn insert_char(self: *Self, char: u8) void {
switch (char) {
'0' => {
if (self.input) |linenum| self.input = linenum * 10;
},
'1'...'9' => {
const digit: usize = @intCast(char - '0');
self.input = if (self.input) |x| x * 10 + digit else digit;
},
else => {},
}
}
fn insert_bytes(self: *Self, bytes: []const u8) void {
for (bytes) |c| self.insert_char(c);
}
const cmds = struct {
pub const Target = Self;
const Ctx = command.Context;
const Meta = command.Metadata;
const Result = command.Result;
pub fn mini_mode_reset(self: *Self, _: Ctx) Result {
self.input = null;
self.update_mini_mode_text();
}
pub const mini_mode_reset_meta: Meta = .{ .description = "Clear input" };
pub fn mini_mode_cancel(self: *Self, _: Ctx) Result {
self.input = null;
self.update_mini_mode_text();
options.cancel(self, self.ctx);
command.executeName("exit_mini_mode", .{}) catch {};
}
pub const mini_mode_cancel_meta: Meta = .{ .description = "Cancel input" };
pub fn mini_mode_delete_backwards(self: *Self, _: Ctx) Result {
if (self.input) |linenum| {
const newval = if (linenum < 10) 0 else linenum / 10;
self.input = if (newval == 0) null else newval;
self.update_mini_mode_text();
options.preview(self, self.ctx);
}
}
pub const mini_mode_delete_backwards_meta: Meta = .{ .description = "Delete backwards" };
pub fn mini_mode_insert_code_point(self: *Self, ctx: Ctx) Result {
var keypress: usize = 0;
if (!try ctx.args.match(.{tp.extract(&keypress)}))
return error.InvalidGotoInsertCodePointArgument;
switch (keypress) {
'0'...'9' => self.insert_char(@intCast(keypress)),
else => {},
}
self.update_mini_mode_text();
options.preview(self, self.ctx);
}
pub const mini_mode_insert_code_point_meta: Meta = .{ .arguments = &.{.integer} };
pub fn mini_mode_insert_bytes(self: *Self, ctx: Ctx) Result {
var bytes: []const u8 = undefined;
if (!try ctx.args.match(.{tp.extract(&bytes)}))
return error.InvalidGotoInsertBytesArgument;
self.insert_bytes(bytes);
self.update_mini_mode_text();
options.preview(self, self.ctx);
}
pub const mini_mode_insert_bytes_meta: Meta = .{ .arguments = &.{.string} };
pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result {
return mini_mode_insert_bytes(self, ctx);
}
pub const mini_mode_paste_meta: Meta = .{ .arguments = &.{.string} };
pub fn mini_mode_select(self: *Self, _: Ctx) Result {
options.apply(self, self.ctx);
command.executeName("exit_mini_mode", .{}) catch {};
}
pub const mini_mode_select_meta: Meta = .{ .description = "Select" };
};
};
}

View file

@ -0,0 +1,32 @@
const cbor = @import("cbor");
const command = @import("command");
const tui = @import("../../tui.zig");
pub const Type = @import("numeric_input.zig").Create(@This());
pub const create = Type.create;
pub fn name(_: *Type) []const u8 {
return " tab size";
}
pub fn start(self: *Type) usize {
const tab_width = if (tui.get_active_editor()) |editor| editor.tab_width else tui.get_tab_width();
self.input = tab_width;
return tab_width;
}
const default_cmd = "set_editor_tab_width";
pub const cancel = preview;
pub fn preview(self: *Type, _: command.Context) void {
command.executeName(default_cmd, command.fmt(.{self.input orelse self.start})) catch {};
}
pub fn apply(self: *Type, ctx: command.Context) void {
var cmd: []const u8 = undefined;
if (!(ctx.args.match(.{cbor.extract(&cmd)}) catch false))
cmd = default_cmd;
command.executeName(cmd, command.fmt(.{self.input orelse self.start})) catch {};
}

View file

@ -12,8 +12,7 @@ const Widget = @import("../../Widget.zig");
pub const label = "Switch buffers"; pub const label = "Switch buffers";
pub const name = " buffer"; pub const name = " buffer";
pub const description = "buffer"; pub const description = "buffer";
const dirty_indicator = ""; pub const icon = "󰈞 ";
const hidden_indicator = "-";
pub const Entry = struct { pub const Entry = struct {
label: []const u8, label: []const u8,
@ -27,12 +26,7 @@ pub fn load_entries(palette: *Type) !usize {
const buffers = try buffer_manager.list_most_recently_used(palette.allocator); const buffers = try buffer_manager.list_most_recently_used(palette.allocator);
defer palette.allocator.free(buffers); defer palette.allocator.free(buffers);
for (buffers) |buffer| { for (buffers) |buffer| {
const indicator = if (buffer.is_dirty()) const indicator = tui.get_buffer_state_indicator(buffer);
dirty_indicator
else if (buffer.is_hidden())
hidden_indicator
else
"";
(try palette.entries.addOne()).* = .{ (try palette.entries.addOne()).* = .{
.label = buffer.get_file_path(), .label = buffer.get_file_path(),
.icon = buffer.file_type_icon orelse "", .icon = buffer.file_type_icon orelse "",
@ -61,50 +55,7 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v
} }
pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool { pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
const style_base = theme.editor_widget; return tui.render_file_item_cbor(&button.plane, button.opts.label, button.active, selected, button.hover, theme);
const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget;
const style_hint = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_label;
button.plane.set_base_style(style_base);
button.plane.erase();
button.plane.home();
button.plane.set_style(style_label);
if (button.active or button.hover or selected) {
button.plane.fill(" ");
button.plane.home();
}
button.plane.set_style(style_hint);
const pointer = if (selected) "" else " ";
_ = button.plane.print("{s}", .{pointer}) catch {};
var iter = button.opts.label;
var file_path_: []const u8 = undefined;
var icon: []const u8 = undefined;
var color: u24 = undefined;
if (!(cbor.matchString(&iter, &file_path_) catch false)) @panic("invalid buffer file path");
if (!(cbor.matchString(&iter, &icon) catch false)) @panic("invalid buffer file type icon");
if (!(cbor.matchInt(u24, &iter, &color) catch false)) @panic("invalid buffer file type color");
if (tui.config().show_fileicons) {
tui.render_file_icon(&button.plane, icon, color);
_ = button.plane.print(" ", .{}) catch {};
}
button.plane.set_style(style_label);
_ = button.plane.print(" {s} ", .{file_path_}) catch {};
var indicator: []const u8 = undefined;
if (!(cbor.matchString(&iter, &indicator) catch false))
indicator = "";
button.plane.set_style(style_hint);
_ = button.plane.print_aligned_right(0, "{s} ", .{indicator}) catch {};
var index: usize = 0;
var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) {
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
tui.render_match_cell(&button.plane, 0, index + 4, theme) catch break;
} else break;
}
return false;
} }
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void { fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {

View file

@ -0,0 +1,175 @@
const std = @import("std");
const cbor = @import("cbor");
const tp = @import("thespian");
const root = @import("root");
const command = @import("command");
const tui = @import("../../tui.zig");
pub const Type = @import("palette.zig").Create(@This());
const module_name = @typeName(@This());
const Widget = @import("../../Widget.zig");
pub const label = "Select completion";
pub const name = "completion";
pub const description = "completions";
pub const icon = "󱎸 ";
pub const Entry = struct {
label: []const u8,
sort_text: []const u8,
cbor: []const u8,
};
pub fn load_entries(palette: *Type) !usize {
const editor = tui.get_active_editor() orelse return error.NotFound;
var iter: []const u8 = editor.completions.items;
while (iter.len > 0) {
var cbor_item: []const u8 = undefined;
if (!try cbor.matchValue(&iter, cbor.extract_cbor(&cbor_item))) return error.BadCompletion;
(try palette.entries.addOne()).* = .{ .cbor = cbor_item, .label = undefined, .sort_text = undefined };
}
var max_label_len: usize = 0;
for (palette.entries.items) |*item| {
const label_, const sort_text, _ = get_values(item.cbor);
item.label = label_;
item.sort_text = sort_text;
max_label_len = @max(max_label_len, item.label.len);
}
const less_fn = struct {
fn less_fn(_: void, lhs: Entry, rhs: Entry) bool {
const lhs_str = if (lhs.sort_text.len > 0) lhs.sort_text else lhs.label;
const rhs_str = if (rhs.sort_text.len > 0) rhs.sort_text else rhs.label;
return std.mem.order(u8, lhs_str, rhs_str) == .lt;
}
}.less_fn;
std.mem.sort(Entry, palette.entries.items, {}, less_fn);
return if (max_label_len > label.len + 3) 0 else label.len + 3 - max_label_len;
}
pub fn clear_entries(palette: *Type) void {
palette.entries.clearRetainingCapacity();
}
pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !void {
var value = std.ArrayList(u8).init(palette.allocator);
defer value.deinit();
const writer = value.writer();
try writer.writeAll(entry.cbor);
try cbor.writeValue(writer, matches orelse &[_]usize{});
try palette.menu.add_item_with_handler(value.items, select);
palette.items += 1;
}
pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
var item_cbor: []const u8 = undefined;
var matches_cbor: []const u8 = undefined;
var iter = button.opts.label;
if (!(cbor.matchValue(&iter, cbor.extract_cbor(&item_cbor)) catch false)) return false;
if (!(cbor.matchValue(&iter, cbor.extract_cbor(&matches_cbor)) catch false)) return false;
const label_, _, const kind = get_values(item_cbor);
const icon_: []const u8 = kind_icon(@enumFromInt(kind));
const color: u24 = 0x0;
const indicator: []const u8 = &.{};
return tui.render_file_item(&button.plane, label_, icon_, color, indicator, matches_cbor, button.active, selected, button.hover, theme);
}
fn get_values(item_cbor: []const u8) struct { []const u8, []const u8, u8 } {
var label_: []const u8 = "";
var sort_text: []const u8 = "";
var kind: u8 = 0;
_ = cbor.match(item_cbor, .{
cbor.any, // file_path
cbor.any, // row
cbor.any, // col
cbor.any, // is_incomplete
cbor.extract(&label_), // label
cbor.any, // label_detail
cbor.any, // label_description
cbor.extract(&kind), // kind
cbor.any, // detail
cbor.any, // documentation
cbor.any, // documentation_kind
cbor.extract(&sort_text), // sortText
cbor.any, // insertTextFormat
cbor.any, // textEdit_newText
cbor.any, // insert.begin.row
cbor.any, // insert.begin.col
cbor.any, // insert.end.row
cbor.any, // insert.end.col
cbor.any, // replace.begin.row
cbor.any, // replace.begin.col
cbor.any, // replace.end.row
cbor.any, // replace.end.col
}) catch false;
return .{ label_, sort_text, kind };
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
const label_, _, _ = get_values(button.opts.label);
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
tp.self_pid().send(.{ "cmd", "insert_chars", .{label_} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
}
const CompletionItemKind = enum(u8) {
Text = 1,
Method = 2,
Function = 3,
Constructor = 4,
Field = 5,
Variable = 6,
Class = 7,
Interface = 8,
Module = 9,
Property = 10,
Unit = 11,
Value = 12,
Enum = 13,
Keyword = 14,
Snippet = 15,
Color = 16,
File = 17,
Reference = 18,
Folder = 19,
EnumMember = 20,
Constant = 21,
Struct = 22,
Event = 23,
Operator = 24,
TypeParameter = 25,
};
fn kind_icon(kind: CompletionItemKind) []const u8 {
return switch (kind) {
.Text => "󰊄",
.Method => "",
.Function => "󰊕",
.Constructor => "",
.Field => "",
.Variable => "",
.Class => "",
.Interface => "",
.Module => "",
.Property => "",
.Unit => "󱔁",
.Value => "󱔁",
.Enum => "",
.Keyword => "",
.Snippet => "",
.Color => "",
.File => "",
.Reference => "",
.Folder => "🗀",
.EnumMember => "",
.Constant => "",
.Struct => "",
.Event => "",
.Operator => "",
.TypeParameter => "",
};
}

View file

@ -14,6 +14,7 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_
pub const label = label_; pub const label = label_;
pub const name = " file type"; pub const name = " file type";
pub const description = "file type"; pub const description = "file type";
pub const icon = "";
pub const Entry = struct { pub const Entry = struct {
label: []const u8, label: []const u8,
@ -84,20 +85,18 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_
} }
button.plane.set_style(style_hint); button.plane.set_style(style_hint);
const pointer = if (selected) "" else " "; tui.render_pointer(&button.plane, selected);
_ = button.plane.print("{s}", .{pointer}) catch {};
var iter = button.opts.label; var iter = button.opts.label;
var description_: []const u8 = undefined; var description_: []const u8 = undefined;
var icon: []const u8 = undefined; var icon_: []const u8 = undefined;
var color: u24 = undefined; var color: u24 = undefined;
if (!(cbor.matchString(&iter, &description_) catch false)) @panic("invalid file_type description"); if (!(cbor.matchString(&iter, &description_) catch false)) @panic("invalid file_type description");
if (!(cbor.matchString(&iter, &icon) catch false)) @panic("invalid file_type icon"); if (!(cbor.matchString(&iter, &icon_) catch false)) @panic("invalid file_type icon");
if (!(cbor.matchInt(u24, &iter, &color) catch false)) @panic("invalid file_type color"); if (!(cbor.matchInt(u24, &iter, &color) catch false)) @panic("invalid file_type color");
if (tui.config().show_fileicons) {
tui.render_file_icon(&button.plane, icon, color); const icon_width = tui.render_file_icon(&button.plane, icon_, color);
_ = button.plane.print(" ", .{}) catch {};
}
button.plane.set_style(style_label); button.plane.set_style(style_label);
_ = button.plane.print("{s} ", .{description_}) catch {}; _ = button.plane.print("{s} ", .{description_}) catch {};
@ -111,7 +110,7 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_
var len = cbor.decodeArrayHeader(&iter) catch return false; var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) { while (len > 0) : (len -= 1) {
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) { if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
tui.render_match_cell(&button.plane, 0, index + 4, theme) catch break; tui.render_match_cell(&button.plane, 0, index + 2 + icon_width, theme) catch break;
} else break; } else break;
} }
return false; return false;
@ -119,12 +118,12 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void { fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
var description_: []const u8 = undefined; var description_: []const u8 = undefined;
var icon: []const u8 = undefined; var icon_: []const u8 = undefined;
var color: u24 = undefined; var color: u24 = undefined;
var name_: []const u8 = undefined; var name_: []const u8 = undefined;
var iter = button.opts.label; var iter = button.opts.label;
if (!(cbor.matchString(&iter, &description_) catch false)) return; if (!(cbor.matchString(&iter, &description_) catch false)) return;
if (!(cbor.matchString(&iter, &icon) catch false)) return; if (!(cbor.matchString(&iter, &icon_) catch false)) return;
if (!(cbor.matchInt(u24, &iter, &color) catch false)) return; if (!(cbor.matchInt(u24, &iter, &color) catch false)) return;
if (!(cbor.matchString(&iter, &name_) catch false)) return; if (!(cbor.matchString(&iter, &name_) catch false)) return;
if (!allow_previous) if (previous_file_type) |prev| if (std.mem.eql(u8, prev, name_)) if (!allow_previous) if (previous_file_type) |prev| if (std.mem.eql(u8, prev, name_))

View file

@ -2,6 +2,7 @@ const std = @import("std");
const tp = @import("thespian"); const tp = @import("thespian");
const log = @import("log"); const log = @import("log");
const cbor = @import("cbor"); const cbor = @import("cbor");
const file_type_config = @import("file_type_config");
const root = @import("root"); const root = @import("root");
const Plane = @import("renderer").Plane; const Plane = @import("renderer").Plane;
@ -22,6 +23,7 @@ const ModalBackground = @import("../../ModalBackground.zig");
const Self = @This(); const Self = @This();
const max_recent_files: usize = 25; const max_recent_files: usize = 25;
const widget_type: Widget.Type = .palette;
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
f: usize = 0, f: usize = 0,
@ -32,7 +34,7 @@ logger: log.Logger,
query_pending: bool = false, query_pending: bool = false,
need_reset: bool = false, need_reset: bool = false,
need_select_first: bool = true, need_select_first: bool = true,
longest: usize = 0, longest: usize,
commands: Commands = undefined, commands: Commands = undefined,
buffer_manager: ?*BufferManager, buffer_manager: ?*BufferManager,
@ -47,21 +49,25 @@ pub fn create(allocator: std.mem.Allocator) !tui.Mode {
.modal = try ModalBackground.create(*Self, allocator, tui.mainview_widget(), .{ .ctx = self }), .modal = try ModalBackground.create(*Self, allocator, tui.mainview_widget(), .{ .ctx = self }),
.menu = try Menu.create(*Self, allocator, tui.plane(), .{ .menu = try Menu.create(*Self, allocator, tui.plane(), .{
.ctx = self, .ctx = self,
.style = widget_type,
.on_render = on_render_menu, .on_render = on_render_menu,
.on_resize = on_resize_menu, .prepare_resize = prepare_resize_menu,
}), }),
.logger = log.logger(@typeName(Self)), .logger = log.logger(@typeName(Self)),
.inputbox = (try self.menu.add_header(try InputBox.create(*Self, self.allocator, self.menu.menu.parent, .{ .inputbox = (try self.menu.add_header(try InputBox.create(*Self, self.allocator, self.menu.menu.parent, .{
.ctx = self, .ctx = self,
.label = inputbox_label, .label = inputbox_label,
.padding = 2,
.icon = "󰈞 ",
}))).dynamic_cast(InputBox.State(*Self)) orelse unreachable, }))).dynamic_cast(InputBox.State(*Self)) orelse unreachable,
.buffer_manager = tui.get_buffer_manager(), .buffer_manager = tui.get_buffer_manager(),
.longest = inputbox_label.len,
}; };
try self.commands.init(self); try self.commands.init(self);
try tui.message_filters().add(MessageFilter.bind(self, receive_project_manager)); try tui.message_filters().add(MessageFilter.bind(self, receive_project_manager));
self.query_pending = true; self.query_pending = true;
try project_manager.request_recent_files(max_recent_files); try project_manager.request_recent_files(max_recent_files);
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = max_menu_width() + 2 }); self.do_resize();
try mv.floating_views.add(self.modal.widget()); try mv.floating_views.add(self.modal.widget());
try mv.floating_views.add(self.menu.container_widget); try mv.floating_views.add(self.menu.container_widget);
var mode = try keybind.mode("overlay/palette", allocator, .{ var mode = try keybind.mode("overlay/palette", allocator, .{
@ -84,7 +90,7 @@ pub fn deinit(self: *Self) void {
} }
inline fn menu_width(self: *Self) usize { inline fn menu_width(self: *Self) usize {
return @max(@min(self.longest, max_menu_width()) + 2, inputbox_label.len + 2); return @max(@min(self.longest + 3, max_menu_width()) + 5, inputbox_label.len + 2);
} }
inline fn menu_pos_x(self: *Self) usize { inline fn menu_pos_x(self: *Self) usize {
@ -98,54 +104,23 @@ inline fn max_menu_width() usize {
return @max(15, width - (width / 5)); return @max(15, width - (width / 5));
} }
fn on_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool { fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool {
const style_base = theme.editor_widget; return tui.render_file_item_cbor(&button.plane, button.opts.label, button.active, selected, button.hover, theme);
const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget;
const style_keybind = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_base;
button.plane.set_base_style(style_base);
button.plane.erase();
button.plane.home();
button.plane.set_style(style_label);
if (button.active or button.hover or selected) {
button.plane.fill(" ");
button.plane.home();
}
var file_path: []const u8 = undefined;
var iter = button.opts.label; // label contains cbor, first the file name, then multiple match indexes
if (!(cbor.matchString(&iter, &file_path) catch false))
file_path = "#ERROR#";
button.plane.set_style(style_keybind);
const dirty = if (self.buffer_manager) |bm| if (bm.is_buffer_dirty(file_path)) "" else " " else " ";
const pointer = if (selected) "" else dirty;
_ = button.plane.print("{s}", .{pointer}) catch {};
var buf: [std.fs.max_path_bytes]u8 = undefined;
var removed_prefix: usize = 0;
const max_len = max_menu_width() - 2;
button.plane.set_style(style_label);
_ = button.plane.print("{s} ", .{
if (file_path.len > max_len) root.shorten_path(&buf, file_path, &removed_prefix, max_len) else file_path,
}) catch {};
var index: usize = 0;
var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) {
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
const cell_idx = if (index < removed_prefix) 1 else index + 1 - removed_prefix;
render_cell(&button.plane, 0, cell_idx, theme.editor_match) catch break;
} else break;
}
return false;
} }
fn render_cell(plane: *Plane, y: usize, x: usize, style: Widget.Theme.Style) !void { fn prepare_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) Widget.Box {
plane.cursor_move_yx(@intCast(y), @intCast(x)) catch return; return self.prepare_resize();
var cell = plane.cell_init();
_ = plane.at_cursor_cell(&cell) catch return;
cell.set_style(style);
_ = plane.putc(&cell) catch {};
} }
fn on_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) void { fn prepare_resize(self: *Self) Widget.Box {
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = self.menu_width() }); const w = self.menu_width();
const x = self.menu_pos_x();
const h = self.menu.menu.widgets.items.len;
return .{ .y = 0, .x = x, .w = w, .h = h };
}
fn do_resize(self: *Self) void {
self.menu.resize(self.prepare_resize());
} }
fn menu_action_open_file(menu: **Menu.State(*Self), button: *Button.State(*Menu.State(*Self))) void { fn menu_action_open_file(menu: **Menu.State(*Self), button: *Button.State(*Menu.State(*Self))) void {
@ -156,12 +131,22 @@ fn menu_action_open_file(menu: **Menu.State(*Self), button: *Button.State(*Menu.
tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_path } }) catch |e| menu.*.opts.ctx.logger.err("navigate", e); tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_path } }) catch |e| menu.*.opts.ctx.logger.err("navigate", e);
} }
fn add_item(self: *Self, file_name: []const u8, matches: ?[]const u8) !void { fn add_item(
self: *Self,
file_name: []const u8,
file_icon: []const u8,
file_color: u24,
indicator: []const u8,
matches: ?[]const u8,
) !void {
var label = std.ArrayList(u8).init(self.allocator); var label = std.ArrayList(u8).init(self.allocator);
defer label.deinit(); defer label.deinit();
const writer = label.writer(); const writer = label.writer();
try cbor.writeValue(writer, file_name); try cbor.writeValue(writer, file_name);
if (matches) |cb| _ = try writer.write(cb); try cbor.writeValue(writer, file_icon);
try cbor.writeValue(writer, file_color);
try cbor.writeValue(writer, indicator);
if (matches) |cb| _ = try writer.write(cb) else try cbor.writeValue(writer, &[_]usize{});
try self.menu.add_item_with_handler(label.items, menu_action_open_file); try self.menu.add_item_with_handler(label.items, menu_action_open_file);
} }
@ -175,21 +160,43 @@ fn receive_project_manager(self: *Self, _: tp.pid_ref, m: tp.message) MessageFil
fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void { fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void {
var file_name: []const u8 = undefined; var file_name: []const u8 = undefined;
var file_type: []const u8 = undefined;
var file_icon: []const u8 = undefined;
var file_color: u24 = undefined;
var matches: []const u8 = undefined; var matches: []const u8 = undefined;
var query: []const u8 = undefined; var query: []const u8 = undefined;
if (try cbor.match(m.buf, .{ "PRJ", "recent", tp.extract(&self.longest), tp.extract(&file_name), tp.extract_cbor(&matches) })) { if (try cbor.match(m.buf, .{
"PRJ",
"recent",
tp.extract(&self.longest),
tp.extract(&file_name),
tp.extract(&file_type),
tp.extract(&file_icon),
tp.extract(&file_color),
tp.extract_cbor(&matches),
})) {
if (self.need_reset) self.reset_results(); if (self.need_reset) self.reset_results();
try self.add_item(file_name, matches); const indicator = if (self.buffer_manager) |bm| tui.get_file_state_indicator(bm, file_name) else "";
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = self.menu_width() }); try self.add_item(file_name, file_icon, file_color, indicator, matches);
self.do_resize();
if (self.need_select_first) { if (self.need_select_first) {
self.menu.select_down(); self.menu.select_down();
self.need_select_first = false; self.need_select_first = false;
} }
tui.need_render(); tui.need_render();
} else if (try cbor.match(m.buf, .{ "PRJ", "recent", tp.extract(&self.longest), tp.extract(&file_name) })) { } else if (try cbor.match(m.buf, .{
"PRJ",
"recent",
tp.extract(&self.longest),
tp.extract(&file_name),
tp.extract(&file_type),
tp.extract(&file_icon),
tp.extract(&file_color),
})) {
if (self.need_reset) self.reset_results(); if (self.need_reset) self.reset_results();
try self.add_item(file_name, null); const indicator = if (self.buffer_manager) |bm| tui.get_file_state_indicator(bm, file_name) else "";
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = self.menu_width() }); try self.add_item(file_name, file_icon, file_color, indicator, null);
self.do_resize();
if (self.need_select_first) { if (self.need_select_first) {
self.menu.select_down(); self.menu.select_down();
self.need_select_first = false; self.need_select_first = false;
@ -363,6 +370,14 @@ const cmds = struct {
} }
pub const overlay_toggle_inputview_meta: Meta = .{}; pub const overlay_toggle_inputview_meta: Meta = .{};
pub fn overlay_next_widget_style(self: *Self, _: Ctx) Result {
tui.set_next_style(widget_type);
self.do_resize();
tui.need_render();
try tui.save_config();
}
pub const overlay_next_widget_style_meta: Meta = .{};
pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result { pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result {
return overlay_insert_bytes(self, ctx); return overlay_insert_bytes(self, ctx);
} }

View file

@ -27,10 +27,12 @@ pub fn deinit(palette: *Type) void {
palette.allocator.free(entry.label); palette.allocator.free(entry.label);
} }
pub fn load_entries(palette: *Type) !usize { pub fn load_entries_with_args(palette: *Type, ctx: command.Context) !usize {
const rsp = try project_manager.request_recent_projects(palette.allocator); var items_cbor: []const u8 = undefined;
defer palette.allocator.free(rsp.buf); if (!(cbor.match(ctx.args.buf, .{ "PRJ", "recent_projects", tp.extract_cbor(&items_cbor) }) catch false))
var iter: []const u8 = rsp.buf; return error.InvalidRecentProjects;
var iter: []const u8 = items_cbor;
var len = try cbor.decodeArrayHeader(&iter); var len = try cbor.decodeArrayHeader(&iter);
while (len > 0) : (len -= 1) { while (len > 0) : (len -= 1) {
var name_: []const u8 = undefined; var name_: []const u8 = undefined;

View file

@ -20,6 +20,7 @@ const ModalBackground = @import("../../ModalBackground.zig");
pub const Menu = @import("../../Menu.zig"); pub const Menu = @import("../../Menu.zig");
const max_menu_width = 80; const max_menu_width = 80;
const widget_type: Widget.Type = .palette;
pub fn Create(options: type) type { pub fn Create(options: type) type {
return struct { return struct {
@ -46,6 +47,10 @@ pub fn Create(options: type) type {
pub const ButtonState = Button.State(*Menu.State(*Self)); pub const ButtonState = Button.State(*Menu.State(*Self));
pub fn create(allocator: std.mem.Allocator) !tui.Mode { pub fn create(allocator: std.mem.Allocator) !tui.Mode {
return create_with_args(allocator, .{});
}
pub fn create_with_args(allocator: std.mem.Allocator, ctx: command.Context) !tui.Mode {
const mv = tui.mainview() orelse return error.NotFound; const mv = tui.mainview() orelse return error.NotFound;
const self = try allocator.create(Self); const self = try allocator.create(Self);
errdefer allocator.destroy(self); errdefer allocator.destroy(self);
@ -57,8 +62,10 @@ pub fn Create(options: type) type {
}), }),
.menu = try Menu.create(*Self, allocator, tui.plane(), .{ .menu = try Menu.create(*Self, allocator, tui.plane(), .{
.ctx = self, .ctx = self,
.style = widget_type,
.on_render = if (@hasDecl(options, "on_render_menu")) options.on_render_menu else on_render_menu, .on_render = if (@hasDecl(options, "on_render_menu")) options.on_render_menu else on_render_menu,
.on_resize = on_resize_menu, .prepare_resize = prepare_resize_menu,
.after_resize = after_resize_menu,
.on_scroll = EventHandler.bind(self, Self.on_scroll), .on_scroll = EventHandler.bind(self, Self.on_scroll),
.on_click4 = mouse_click_button4, .on_click4 = mouse_click_button4,
.on_click5 = mouse_click_button5, .on_click5 = mouse_click_button5,
@ -67,12 +74,17 @@ pub fn Create(options: type) type {
.inputbox = (try self.menu.add_header(try InputBox.create(*Self, self.allocator, self.menu.menu.parent, .{ .inputbox = (try self.menu.add_header(try InputBox.create(*Self, self.allocator, self.menu.menu.parent, .{
.ctx = self, .ctx = self,
.label = options.label, .label = options.label,
.padding = 2,
.icon = if (@hasDecl(options, "icon")) options.icon else null,
}))).dynamic_cast(InputBox.State(*Self)) orelse unreachable, }))).dynamic_cast(InputBox.State(*Self)) orelse unreachable,
.view_rows = get_view_rows(tui.screen()), .view_rows = get_view_rows(tui.screen()),
.entries = std.ArrayList(Entry).init(allocator), .entries = std.ArrayList(Entry).init(allocator),
}; };
if (self.menu.scrollbar) |scrollbar| scrollbar.style_factory = scrollbar_style; if (self.menu.scrollbar) |scrollbar| scrollbar.style_factory = scrollbar_style;
self.longest_hint = try options.load_entries(self); self.longest_hint = if (@hasDecl(options, "load_entries_with_args"))
try options.load_entries_with_args(self, ctx)
else
try options.load_entries(self);
if (@hasDecl(options, "restore_state")) if (@hasDecl(options, "restore_state"))
options.restore_state(self) catch {}; options.restore_state(self) catch {};
try self.commands.init(self); try self.commands.init(self);
@ -130,8 +142,7 @@ pub fn Create(options: type) type {
if (!(cbor.matchString(&iter, &hint) catch false)) if (!(cbor.matchString(&iter, &hint) catch false))
hint = ""; hint = "";
button.plane.set_style(style_hint); button.plane.set_style(style_hint);
const pointer = if (selected) "" else " "; tui.render_pointer(&button.plane, selected);
_ = button.plane.print("{s}", .{pointer}) catch {};
button.plane.set_style(style_label); button.plane.set_style(style_label);
_ = button.plane.print("{s} ", .{label}) catch {}; _ = button.plane.print("{s} ", .{label}) catch {};
button.plane.set_style(style_hint); button.plane.set_style(style_hint);
@ -140,25 +151,38 @@ pub fn Create(options: type) type {
var len = cbor.decodeArrayHeader(&iter) catch return false; var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) { while (len > 0) : (len -= 1) {
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) { if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
tui.render_match_cell(&button.plane, 0, index + 1, theme) catch break; tui.render_match_cell(&button.plane, 0, index + 2, theme) catch break;
} else break; } else break;
} }
return false; return false;
} }
fn on_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) void { fn prepare_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) Widget.Box {
self.do_resize(); return self.prepare_resize();
// self.start_query(0) catch {};
} }
fn do_resize(self: *Self) void { fn prepare_resize(self: *Self) Widget.Box {
const screen = tui.screen(); const screen = tui.screen();
const w = @max(@min(self.longest, max_menu_width) + 2 + 1 + self.longest_hint, options.label.len + 2); const w = @max(@min(self.longest + 3, max_menu_width) + 2 + self.longest_hint, options.label.len + 2);
const x = if (screen.w > w) (screen.w - w) / 2 else 0; const x = if (screen.w > w) (screen.w - w) / 2 else 0;
self.view_rows = get_view_rows(screen); self.view_rows = get_view_rows(screen);
const h = @min(self.items + self.menu.header_count, self.view_rows + self.menu.header_count); const h = @min(self.items + self.menu.header_count, self.view_rows + self.menu.header_count);
self.menu.container.resize(.{ .y = 0, .x = x, .w = w, .h = h }); return .{ .y = 0, .x = x, .w = w, .h = h };
}
fn after_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) void {
return self.after_resize();
}
fn after_resize(self: *Self) void {
self.update_scrollbar(); self.update_scrollbar();
// self.start_query(0) catch {};
}
fn do_resize(self: *Self, padding: Widget.Style.Margin) void {
const box = self.prepare_resize();
self.menu.resize(self.menu.container.to_client_box(box, padding));
self.after_resize();
} }
fn get_view_rows(screen: Widget.Box) usize { fn get_view_rows(screen: Widget.Box) usize {
@ -239,7 +263,8 @@ pub fn Create(options: type) type {
var i = n; var i = n;
while (i > 0) : (i -= 1) while (i > 0) : (i -= 1)
self.menu.select_down(); self.menu.select_down();
self.do_resize(); const padding = tui.get_widget_style(widget_type).padding;
self.do_resize(padding);
tui.refresh_hover(); tui.refresh_hover();
self.selection_updated(); self.selection_updated();
} }
@ -449,15 +474,22 @@ pub fn Create(options: type) type {
const button = self.menu.get_selected() orelse return; const button = self.menu.get_selected() orelse return;
const refresh = options.delete_item(self.menu, button); const refresh = options.delete_item(self.menu, button);
if (refresh) { if (refresh) {
options.clear_entries(self); if (@hasDecl(options, "load_entries")) {
self.longest_hint = try options.load_entries(self); options.clear_entries(self);
if (self.entries.items.len > 0) self.longest_hint = try options.load_entries(self);
self.initial_selected = self.menu.selected; if (self.entries.items.len > 0)
try self.start_query(0); self.initial_selected = self.menu.selected;
try self.start_query(0);
} else {
return palette_menu_cancel(self, .{});
}
} }
} }
} }
pub const palette_menu_delete_item_meta: Meta = .{}; pub const palette_menu_delete_item_meta: Meta = .{
.description = "Delete item",
.icon = "󰗨",
};
pub fn palette_menu_activate(self: *Self, _: Ctx) Result { pub fn palette_menu_activate(self: *Self, _: Ctx) Result {
self.menu.activate_selected(); self.menu.activate_selected();
@ -511,6 +543,15 @@ pub fn Create(options: type) type {
} }
pub const overlay_toggle_inputview_meta: Meta = .{}; pub const overlay_toggle_inputview_meta: Meta = .{};
pub fn overlay_next_widget_style(self: *Self, _: Ctx) Result {
tui.set_next_style(widget_type);
const padding = tui.get_widget_style(widget_type).padding;
self.do_resize(padding);
tui.need_render();
try tui.save_config();
}
pub const overlay_next_widget_style_meta: Meta = .{};
pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result { pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result {
return overlay_insert_bytes(self, ctx); return overlay_insert_bytes(self, ctx);
} }

View file

@ -33,10 +33,8 @@ pub fn load_entries(palette: *Type) !usize {
(try palette.entries.addOne()).* = .{ .label = try palette.allocator.dupe(u8, task) }; (try palette.entries.addOne()).* = .{ .label = try palette.allocator.dupe(u8, task) };
} else return error.InvalidTaskMessageField; } else return error.InvalidTaskMessageField;
} }
(try palette.entries.addOne()).* = .{ (try palette.entries.addOne()).* = .{ .label = "", .command = "add_task" };
.label = try palette.allocator.dupe(u8, " Add new task"), (try palette.entries.addOne()).* = .{ .label = "", .command = "palette_menu_delete_item" };
.command = "add_task",
};
return if (palette.entries.items.len == 0) label.len else blk: { return if (palette.entries.items.len == 0) label.len else blk: {
var longest: usize = 0; var longest: usize = 0;
for (palette.entries.items) |item| longest = @max(longest, item.label.len); for (palette.entries.items) |item| longest = @max(longest, item.label.len);
@ -60,7 +58,7 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v
palette.items += 1; palette.items += 1;
} }
pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool { pub fn on_render_menu(palette: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
var entry: Entry = undefined; var entry: Entry = undefined;
var iter = button.opts.label; // label contains cbor entry object and matches var iter = button.opts.label; // label contains cbor entry object and matches
if (!(cbor.matchValue(&iter, cbor.extract(&entry)) catch false)) if (!(cbor.matchValue(&iter, cbor.extract(&entry)) catch false))
@ -84,11 +82,30 @@ pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.
button.plane.set_style(style_label); button.plane.set_style(style_label);
button.plane.fill(" "); button.plane.fill(" ");
button.plane.home(); button.plane.home();
button.plane.set_style(style_hint); button.plane.set_style(style_hint);
const pointer = if (selected) "" else " "; tui.render_pointer(&button.plane, selected);
_ = button.plane.print("{s}", .{pointer}) catch {};
button.plane.set_style(style_label); button.plane.set_style(style_label);
_ = button.plane.print("{s} ", .{entry.label}) catch {}; if (entry.command) |command_name| blk: {
button.plane.set_style(style_hint);
var label_: std.ArrayListUnmanaged(u8) = .empty;
defer label_.deinit(palette.allocator);
const id = command.get_id(command_name) orelse break :blk;
if (command.get_icon(id)) |icon|
label_.writer(palette.allocator).print("{s} ", .{icon}) catch {};
if (command.get_description(id)) |desc|
label_.writer(palette.allocator).print("{s}", .{desc}) catch {};
_ = button.plane.print("{s} ", .{label_.items}) catch {};
const hints = if (tui.input_mode()) |m| m.keybind_hints else @panic("no keybind hints");
if (hints.get(command_name)) |hint|
_ = button.plane.print_aligned_right(0, "{s} ", .{hint}) catch {};
} else {
_ = button.plane.print("{s} ", .{entry.label}) catch {};
}
var index: usize = 0; var index: usize = 0;
var len = cbor.decodeArrayHeader(&iter) catch return false; var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) { while (len > 0) : (len -= 1) {
@ -103,17 +120,13 @@ fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
var entry: Entry = undefined; var entry: Entry = undefined;
var iter = button.opts.label; var iter = button.opts.label;
if (!(cbor.matchValue(&iter, cbor.extract(&entry)) catch false)) return; if (!(cbor.matchValue(&iter, cbor.extract(&entry)) catch false)) return;
var buffer_name = std.ArrayList(u8).init(menu.*.opts.ctx.allocator); if (entry.command) |command_name| {
defer buffer_name.deinit();
buffer_name.writer().print("*{s}*", .{entry.label}) catch {};
if (entry.command) |cmd| {
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err(module_name, e); tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
tp.self_pid().send(.{ "cmd", cmd, .{entry.label} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e); tp.self_pid().send(.{ "cmd", command_name, .{} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
} else { } else {
project_manager.add_task(entry.label) catch {};
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err(module_name, e); tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
tp.self_pid().send(.{ "cmd", "create_scratch_buffer", .{ buffer_name.items, "", "conf" } }) catch |e| menu.*.opts.ctx.logger.err(module_name, e); project_manager.add_task(entry.label) catch {};
tp.self_pid().send(.{ "cmd", "shell_execute_stream", .{entry.label} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e); tp.self_pid().send(.{ "cmd", "run_task", .{entry.label} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
} }
} }

View file

@ -90,11 +90,11 @@ fn format(self: *Self) void {
const writer = fbs.writer(); const writer = fbs.writer();
const eol_mode = switch (self.eol_mode) { const eol_mode = switch (self.eol_mode) {
.lf => "", .lf => "",
.crlf => " [␍␊]", .crlf => " ␍␊",
}; };
const indent_mode = switch (self.indent_mode) { const indent_mode = switch (self.indent_mode) {
.spaces, .auto => "", .spaces, .auto => "",
.tabs => " [⭾]", .tabs => " ",
}; };
std.fmt.format(writer, "{s}{s} Ln ", .{ eol_mode, indent_mode }) catch {}; std.fmt.format(writer, "{s}{s} Ln ", .{ eol_mode, indent_mode }) catch {};
self.format_count(writer, self.line + 1, self.padding orelse 0) catch {}; self.format_count(writer, self.line + 1, self.padding orelse 0) catch {};

View file

@ -24,6 +24,7 @@ allocator: Allocator,
rdr_: renderer, rdr_: renderer,
config_: @import("config"), config_: @import("config"),
config_bufs: [][]const u8, config_bufs: [][]const u8,
session_tab_width: ?usize = null,
highlight_columns_: []const u16, highlight_columns_: []const u16,
highlight_columns_configured: []const u16, highlight_columns_configured: []const u16,
frame_time: usize, // in microseconds frame_time: usize, // in microseconds
@ -103,6 +104,8 @@ const InitError = error{
keybind.LoadError; keybind.LoadError;
fn init(allocator: Allocator) InitError!*Self { fn init(allocator: Allocator) InitError!*Self {
log.stdout(.disable);
var conf, const conf_bufs = root.read_config(@import("config"), allocator); var conf, const conf_bufs = root.read_config(@import("config"), allocator);
if (@hasDecl(renderer, "install_crash_handler") and conf.start_debugger_on_crash) if (@hasDecl(renderer, "install_crash_handler") and conf.start_debugger_on_crash)
@ -119,6 +122,8 @@ fn init(allocator: Allocator) InitError!*Self {
const frame_time = std.time.us_per_s / conf.frame_rate; const frame_time = std.time.us_per_s / conf.frame_rate;
const frame_clock = try tp.metronome.init(frame_time); const frame_clock = try tp.metronome.init(frame_time);
tp.env.get().set("lsp_verbose", conf.lsp_output == .verbose);
var self = try allocator.create(Self); var self = try allocator.create(Self);
// don't destroy // don't destroy
// if tui fails it is catastrophic anyway and we don't want to cause nock-on errors // if tui fails it is catastrophic anyway and we don't want to cause nock-on errors
@ -157,6 +162,8 @@ fn init(allocator: Allocator) InitError!*Self {
self.rdr_.dispatch_event = dispatch_event; self.rdr_.dispatch_event = dispatch_event;
try self.rdr_.run(); try self.rdr_.run();
log.stderr(.disable);
try project_manager.start(); try project_manager.start();
try frame_clock.start(); try frame_clock.start();
@ -384,6 +391,9 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
return; return;
} }
if (try m.match(.{"mouse_leave"}))
return;
if (try m.match(.{"focus_in"})) if (try m.match(.{"focus_in"}))
return; return;
@ -408,6 +418,9 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
return; return;
} }
if (try m.match(.{ "PRJ", "recent_projects", tp.more })) // async recent projects request
return self.enter_overlay_mode_with_args(@import("mode/overlay/open_recent_project.zig").Type, .{ .args = m });
if (try m.match(.{ "PRJ", tp.more })) // drop late project manager query responses if (try m.match(.{ "PRJ", tp.more })) // drop late project manager query responses
return; return;
@ -679,6 +692,17 @@ fn enter_overlay_mode(self: *Self, mode: type) command.Result {
refresh_hover(); refresh_hover();
} }
fn enter_overlay_mode_with_args(self: *Self, mode: type, ctx: command.Context) command.Result {
command.executeName("disable_fast_scroll", .{}) catch {};
command.executeName("disable_jump_mode", .{}) catch {};
if (self.mini_mode_) |_| try cmds.exit_mini_mode(self, .{});
if (self.input_mode_outer_) |_| try cmds.exit_overlay_mode(self, .{});
self.input_mode_outer_ = self.input_mode_;
self.input_mode_ = try mode.create_with_args(self.allocator, ctx);
if (self.input_mode_) |*m| m.run_init();
refresh_hover();
}
fn get_input_mode(self: *Self, mode_name: []const u8) !Mode { fn get_input_mode(self: *Self, mode_name: []const u8) !Mode {
return keybind.mode(mode_name, self.allocator, .{}); return keybind.mode(mode_name, self.allocator, .{});
} }
@ -709,17 +733,22 @@ fn refresh_input_mode(self: *Self) command.Result {
if (self.input_mode_) |*m| m.run_init(); if (self.input_mode_) |*m| m.run_init();
} }
fn set_theme_by_name(self: *Self, name: []const u8) !void { fn set_theme_by_name(self: *Self, name: []const u8, action: enum { none, store }) !void {
const old = self.parsed_theme; const old = self.parsed_theme;
defer if (old) |p| p.deinit(); defer if (old) |p| p.deinit();
self.theme_, self.parsed_theme = get_theme_by_name(self.allocator, name) orelse { self.theme_, self.parsed_theme = get_theme_by_name(self.allocator, name) orelse {
self.logger.print("theme not found: {s}", .{name}); self.logger.print("theme not found: {s}", .{name});
return; return;
}; };
self.config_.theme = self.theme_.name;
self.set_terminal_style(); self.set_terminal_style();
self.logger.print("theme: {s}", .{self.theme_.description}); self.logger.print("theme: {s}", .{self.theme_.description});
try save_config(); switch (action) {
.none => {},
.store => {
self.config_.theme = self.theme_.name;
try save_config();
},
}
} }
const cmds = struct { const cmds = struct {
@ -740,23 +769,69 @@ const cmds = struct {
} }
pub const force_terminate_meta: Meta = .{ .description = "Force quit without saving" }; pub const force_terminate_meta: Meta = .{ .description = "Force quit without saving" };
pub fn set_tab_width(self: *Self, ctx: Ctx) Result {
var tab_width: usize = 0;
if (!try ctx.args.match(.{tp.extract(&tab_width)}))
return enter_mini_mode(self, @import("mode/mini/tab_width.zig"), Ctx.fmt(.{"set_tab_width"}));
self.config_.tab_width = tab_width;
self.session_tab_width = null;
command.executeName("set_editor_tab_width", ctx) catch {};
try save_config();
self.logger.print("tab width {}", .{tab_width});
}
pub const set_tab_width_meta: Meta = .{
.description = "Set tab width",
.arguments = &.{.integer},
};
pub fn set_buffer_tab_width(self: *Self, ctx: Ctx) Result {
var tab_width: usize = 0;
if (!try ctx.args.match(.{tp.extract(&tab_width)}))
return enter_mini_mode(self, @import("mode/mini/tab_width.zig"), Ctx.fmt(.{"set_buffer_tab_width"}));
command.executeName("set_editor_tab_width", ctx) catch {};
self.logger.print("buffer tab width {}", .{tab_width});
}
pub const set_buffer_tab_width_meta: Meta = .{
.description = "Set tab width for current buffer",
.arguments = &.{.integer},
};
pub fn set_session_tab_width(self: *Self, ctx: Ctx) Result {
var tab_width: usize = 0;
if (!try ctx.args.match(.{tp.extract(&tab_width)}))
return enter_mini_mode(self, @import("mode/mini/tab_width.zig"), Ctx.fmt(.{"set_session_tab_width"}));
self.session_tab_width = tab_width;
command.executeName("set_editor_tab_width", ctx) catch {};
self.logger.print("session tab width {}", .{tab_width});
}
pub const set_session_tab_width_meta: Meta = .{
.description = "Set tab width for current session",
.arguments = &.{.integer},
};
pub fn set_theme(self: *Self, ctx: Ctx) Result { pub fn set_theme(self: *Self, ctx: Ctx) Result {
var name: []const u8 = undefined; var name: []const u8 = undefined;
if (!try ctx.args.match(.{tp.extract(&name)})) if (try ctx.args.match(.{tp.extract(&name)}))
return tp.exit_error(error.InvalidSetThemeArgument, null); return self.set_theme_by_name(name, .store);
return self.set_theme_by_name(name); if (try ctx.args.match(.{ tp.extract(&name), "no_store" }))
return self.set_theme_by_name(name, .none);
return tp.exit_error(error.InvalidSetThemeArgument, null);
} }
pub const set_theme_meta: Meta = .{ .arguments = &.{.string} }; pub const set_theme_meta: Meta = .{ .arguments = &.{.string} };
pub fn theme_next(self: *Self, _: Ctx) Result { pub fn theme_next(self: *Self, _: Ctx) Result {
const name = get_next_theme_by_name(self.theme_.name); const name = get_next_theme_by_name(self.theme_.name);
return self.set_theme_by_name(name); return self.set_theme_by_name(name, .store);
} }
pub const theme_next_meta: Meta = .{ .description = "Next color theme" }; pub const theme_next_meta: Meta = .{ .description = "Next color theme" };
pub fn theme_prev(self: *Self, _: Ctx) Result { pub fn theme_prev(self: *Self, _: Ctx) Result {
const name = get_prev_theme_by_name(self.theme_.name); const name = get_prev_theme_by_name(self.theme_.name);
return self.set_theme_by_name(name); return self.set_theme_by_name(name, .store);
} }
pub const theme_prev_meta: Meta = .{ .description = "Previous color theme" }; pub const theme_prev_meta: Meta = .{ .description = "Previous color theme" };
@ -858,8 +933,8 @@ const cmds = struct {
} }
pub const open_recent_meta: Meta = .{ .description = "Open recent" }; pub const open_recent_meta: Meta = .{ .description = "Open recent" };
pub fn open_recent_project(self: *Self, _: Ctx) Result { pub fn open_recent_project(_: *Self, _: Ctx) Result {
return self.enter_overlay_mode(@import("mode/overlay/open_recent_project.zig").Type); try project_manager.request_recent_projects();
} }
pub const open_recent_project_meta: Meta = .{ .description = "Open project" }; pub const open_recent_project_meta: Meta = .{ .description = "Open project" };
@ -868,12 +943,11 @@ const cmds = struct {
} }
pub const switch_buffers_meta: Meta = .{ .description = "Switch buffers" }; pub const switch_buffers_meta: Meta = .{ .description = "Switch buffers" };
pub fn select_task(self: *Self, _: Ctx) Result {
return self.enter_overlay_mode(@import("mode/overlay/task_palette.zig").Type);
}
pub const select_task_meta: Meta = .{ .description = "Run task" };
pub fn add_task(self: *Self, ctx: Ctx) Result { pub fn add_task(self: *Self, ctx: Ctx) Result {
var task: []const u8 = undefined;
if (try ctx.args.match(.{tp.extract(&task)}))
return call_add_task(task);
return enter_mini_mode(self, struct { return enter_mini_mode(self, struct {
pub const Type = @import("mode/mini/buffer.zig").Create(@This()); pub const Type = @import("mode/mini/buffer.zig").Create(@This());
pub const create = Type.create; pub const create = Type.create;
@ -881,17 +955,42 @@ const cmds = struct {
return @import("mode/overlay/task_palette.zig").name; return @import("mode/overlay/task_palette.zig").name;
} }
pub fn select(self_: *Type) void { pub fn select(self_: *Type) void {
project_manager.add_task(self_.input.items) catch |e| { tp.self_pid().send(.{ "cmd", "run_task", .{self_.input.items} }) catch {};
const logger = log.logger("tui");
logger.err("add_task", e);
logger.deinit();
};
command.executeName("exit_mini_mode", .{}) catch {}; command.executeName("exit_mini_mode", .{}) catch {};
command.executeName("select_task", .{}) catch {};
} }
}, ctx); }, ctx);
} }
pub const add_task_meta: Meta = .{ .description = "Add task" }; pub const add_task_meta: Meta = .{
.description = "Add new task",
.arguments = &.{.string},
.icon = "",
};
fn call_add_task(task: []const u8) void {
project_manager.add_task(task) catch |e| {
const logger = log.logger("tui");
logger.err("add_task", e);
logger.deinit();
};
}
pub fn run_task(self: *Self, ctx: Ctx) Result {
var task: []const u8 = undefined;
if (try ctx.args.match(.{tp.extract(&task)})) {
var buffer_name = std.ArrayList(u8).init(self.allocator);
defer buffer_name.deinit();
buffer_name.writer().print("*{s}*", .{task}) catch {};
call_add_task(task);
tp.self_pid().send(.{ "cmd", "create_scratch_buffer", .{ buffer_name.items, "", "conf" } }) catch |e| self.logger.err("task", e);
tp.self_pid().send(.{ "cmd", "shell_execute_stream", .{task} }) catch |e| self.logger.err("task", e);
} else {
return self.enter_overlay_mode(@import("mode/overlay/task_palette.zig").Type);
}
}
pub const run_task_meta: Meta = .{
.description = "Run a task",
.arguments = &.{.string},
};
pub fn delete_task(_: *Self, ctx: Ctx) Result { pub fn delete_task(_: *Self, ctx: Ctx) Result {
var task: []const u8 = undefined; var task: []const u8 = undefined;
@ -964,10 +1063,10 @@ const cmds = struct {
fn enter_mini_mode(self: *Self, comptime mode: anytype, ctx: Ctx) !void { fn enter_mini_mode(self: *Self, comptime mode: anytype, ctx: Ctx) !void {
command.executeName("disable_fast_scroll", .{}) catch {}; command.executeName("disable_fast_scroll", .{}) catch {};
command.executeName("disable_jump_mode", .{}) catch {}; command.executeName("disable_jump_mode", .{}) catch {};
const input_mode_, const mini_mode_ = try mode.create(self.allocator, ctx);
if (self.mini_mode_) |_| try exit_mini_mode(self, .{}); if (self.mini_mode_) |_| try exit_mini_mode(self, .{});
if (self.input_mode_outer_) |_| try exit_overlay_mode(self, .{}); if (self.input_mode_outer_) |_| try exit_overlay_mode(self, .{});
if (self.input_mode_outer_ != null) @panic("exit_overlay_mode failed"); if (self.input_mode_outer_ != null) @panic("exit_overlay_mode failed");
const input_mode_, const mini_mode_ = try mode.create(self.allocator, ctx);
self.input_mode_outer_ = self.input_mode_; self.input_mode_outer_ = self.input_mode_;
self.input_mode_ = input_mode_; self.input_mode_ = input_mode_;
self.mini_mode_ = mini_mode_; self.mini_mode_ = mini_mode_;
@ -1113,6 +1212,11 @@ pub fn config() *const @import("config") {
return &current().config_; return &current().config_;
} }
pub fn get_tab_width() usize {
const self = current();
return self.session_tab_width orelse self.config_.tab_width;
}
pub fn highlight_columns() []const u16 { pub fn highlight_columns() []const u16 {
return current().highlight_columns_; return current().highlight_columns_;
} }
@ -1375,7 +1479,19 @@ pub fn message(comptime fmt: anytype, args: anytype) void {
tp.self_pid().send(.{ "message", std.fmt.bufPrint(&buf, fmt, args) catch @panic("too large") }) catch {}; tp.self_pid().send(.{ "message", std.fmt.bufPrint(&buf, fmt, args) catch @panic("too large") }) catch {};
} }
pub fn render_file_icon(self: *renderer.Plane, icon: []const u8, color: u24) void { const dirty_indicator = "";
const hidden_indicator = "-";
pub fn get_file_state_indicator(buffer_manager: *const @import("Buffer").Manager, file_name: []const u8) []const u8 {
return if (buffer_manager.get_buffer_for_file(file_name)) |buffer| get_buffer_state_indicator(buffer) else "";
}
pub fn get_buffer_state_indicator(buffer: *const @import("Buffer")) []const u8 {
return if (buffer.is_dirty()) dirty_indicator else if (buffer.is_hidden()) hidden_indicator else "";
}
pub fn render_file_icon(self: *renderer.Plane, icon: []const u8, color: u24) usize {
if (!config().show_fileicons) return 0;
var cell = self.cell_init(); var cell = self.cell_init();
_ = self.at_cursor_cell(&cell) catch return; _ = self.at_cursor_cell(&cell) catch return;
if (!(color == 0xFFFFFF or color == 0x000000 or color == 0x000001)) { if (!(color == 0xFFFFFF or color == 0x000000 or color == 0x000001)) {
@ -1384,6 +1500,8 @@ pub fn render_file_icon(self: *renderer.Plane, icon: []const u8, color: u24) voi
_ = self.cell_load(&cell, icon) catch {}; _ = self.cell_load(&cell, icon) catch {};
_ = self.putc(&cell) catch {}; _ = self.putc(&cell) catch {};
self.cursor_move_rel(0, 1) catch {}; self.cursor_move_rel(0, 1) catch {};
_ = self.print(" ", .{}) catch {};
return 3;
} }
pub fn render_match_cell(self: *renderer.Plane, y: usize, x: usize, theme_: *const Widget.Theme) !void { pub fn render_match_cell(self: *renderer.Plane, y: usize, x: usize, theme_: *const Widget.Theme) !void {
@ -1394,6 +1512,74 @@ pub fn render_match_cell(self: *renderer.Plane, y: usize, x: usize, theme_: *con
_ = self.putc(&cell) catch {}; _ = self.putc(&cell) catch {};
} }
pub fn render_pointer(self: *renderer.Plane, selected: bool) void {
const pointer = if (selected) "" else " ";
_ = self.print("{s}", .{pointer}) catch {};
}
pub fn render_file_item(
self: *renderer.Plane,
file_path_: []const u8,
icon: []const u8,
color: u24,
indicator: []const u8,
matches_cbor: []const u8,
active: bool,
selected: bool,
hover: bool,
theme_: *const Widget.Theme,
) bool {
const style_base = theme_.editor_widget;
const style_label = if (active) theme_.editor_cursor else if (hover or selected) theme_.editor_selection else theme_.editor_widget;
const style_hint = if (find_scope_style(theme_, "entity.name")) |sty| sty.style else style_label;
self.set_base_style(style_base);
self.erase();
self.home();
self.set_style(style_label);
if (active or hover or selected) {
self.fill(" ");
self.home();
}
self.set_style(style_hint);
render_pointer(self, selected);
const icon_width = render_file_icon(self, icon, color);
self.set_style(style_label);
_ = self.print("{s} ", .{file_path_}) catch {};
self.set_style(style_hint);
_ = self.print_aligned_right(0, "{s} ", .{indicator}) catch {};
var iter = matches_cbor;
var index: usize = 0;
var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) {
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
render_match_cell(self, 0, index + 2 + icon_width, theme_) catch break;
} else break;
}
return false;
}
pub fn render_file_item_cbor(self: *renderer.Plane, file_item_cbor: []const u8, active: bool, selected: bool, hover: bool, theme_: *const Widget.Theme) bool {
var iter = file_item_cbor;
var file_path_: []const u8 = undefined;
var icon: []const u8 = undefined;
var color: u24 = undefined;
var indicator: []const u8 = undefined;
var matches_cbor: []const u8 = undefined;
if (!(cbor.matchString(&iter, &file_path_) catch false)) @panic("invalid buffer file path");
if (!(cbor.matchString(&iter, &icon) catch false)) @panic("invalid buffer file type icon");
if (!(cbor.matchInt(u24, &iter, &color) catch false)) @panic("invalid buffer file type color");
if (!(cbor.matchString(&iter, &indicator) catch false)) indicator = "";
if (!(cbor.matchValue(&iter, cbor.extract_cbor(&matches_cbor)) catch false)) @panic("invalid matches cbor");
return render_file_item(self, file_path_, icon, color, indicator, matches_cbor, active, selected, hover, theme_);
}
fn get_or_create_theme_file(self: *Self, allocator: std.mem.Allocator) ![]const u8 { fn get_or_create_theme_file(self: *Self, allocator: std.mem.Allocator) ![]const u8 {
const theme_name = self.theme_.name; const theme_name = self.theme_.name;
if (root.read_theme(allocator, theme_name)) |content| { if (root.read_theme(allocator, theme_name)) |content| {
@ -1422,3 +1608,40 @@ fn load_theme_file_internal(allocator: std.mem.Allocator, theme_name: []const u8
defer allocator.free(json_str); defer allocator.free(json_str);
return try std.json.parseFromSlice(Widget.Theme, allocator, json_str, .{ .allocate = .alloc_always }); return try std.json.parseFromSlice(Widget.Theme, allocator, json_str, .{ .allocate = .alloc_always });
} }
pub const WidgetType = @import("config").WidgetType;
pub const ConfigWidgetStyle = @import("config").WidgetStyle;
pub const WidgetStyle = @import("WidgetStyle.zig");
pub fn get_widget_style(widget_type: WidgetType) *const WidgetStyle {
const config_ = config();
return switch (widget_type) {
.none => WidgetStyle.from_tag(config_.widget_style),
.palette => WidgetStyle.from_tag(config_.palette_style),
.panel => WidgetStyle.from_tag(config_.panel_style),
.home => WidgetStyle.from_tag(config_.home_style),
};
}
pub fn set_next_style(widget_type: WidgetType) void {
const ref = widget_type_config_variable(widget_type);
ref.* = next_widget_style(ref.*);
const self = current();
self.logger.print("{s} style {s}", .{ @tagName(widget_type), @tagName(ref.*) });
}
fn next_widget_style(tag: ConfigWidgetStyle) ConfigWidgetStyle {
const max_tag = comptime std.meta.tags(ConfigWidgetStyle).len;
const new_value = @intFromEnum(tag) + 1;
return if (new_value >= max_tag) @enumFromInt(0) else @enumFromInt(new_value);
}
fn widget_type_config_variable(widget_type: WidgetType) *ConfigWidgetStyle {
const config_ = config_mut();
return switch (widget_type) {
.none => &config_.widget_style,
.palette => &config_.palette_style,
.panel => &config_.panel_style,
.home => &config_.home_style,
};
}