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,
.dependencies = .{
.syntax = .{ .path = "src/syntax" },
.syntax = .{
.url = "git+https://github.com/neurocyte/flow-syntax?ref=zig-0.14#410d19e633f237cd1602175450bd7d3bb03a1898",
.hash = "flow_syntax-0.1.0-X8jOoT4OAQDibKKzYlJls3u5KczVh__cWYN7vTqCE1o3",
},
.flags = .{
.url = "https://github.com/n0s4/flags/archive/372501d1576b5723829bcba98e41361132c7b618.tar.gz",
.hash = "flags-0.8.0-AAAAAJV0AACuGBBnpUnHqZzAhoGTp4ibFROBQQQZGRqx",

View file

@ -3,7 +3,6 @@ const tp = @import("thespian");
const cbor = @import("cbor");
const root = @import("root");
const tracy = @import("tracy");
const log = @import("log");
allocator: std.mem.Allocator,
pid: tp.pid,
@ -172,6 +171,7 @@ const Process = struct {
project: [:0]const u8,
sp_tag: [:0]const u8,
log_file: ?std.fs.File = null,
log_file_path: ?[]const u8 = null,
next_id: i32 = 0,
requests: std.StringHashMap(tp.pid),
state: enum { init, running } = .init,
@ -188,10 +188,8 @@ const Process = struct {
} else if (try cbor.match(cmd.buf, .{ tp.extract(&tag), tp.more })) {
//
} else {
const logger = log.logger("LSP");
defer logger.deinit();
var buf: [1024]u8 = undefined;
logger.print_err("create", "invalid command: {d} {s}", .{ cmd.buf.len, cmd.to_json(&buf) catch "{command too large}" });
send_msg(tp.self_pid().clone(), tag, .err, "invalid command: {d} {s}", .{ cmd.buf.len, cmd.to_json(&buf) catch "{command too large}" });
return error.InvalidLspCommand;
}
const self = try allocator.create(Process);
@ -227,6 +225,7 @@ const Process = struct {
self.close() catch {};
self.write_log("### terminated LSP process ###\n", .{});
if (self.log_file) |file| file.close();
if (self.log_file_path) |file_path| self.allocator.free(file_path);
}
fn close(self: *Process) error{CloseFailed}!void {
@ -245,6 +244,20 @@ const Process = struct {
}
}
fn msg(self: *const Process, comptime fmt: anytype, args: anytype) void {
send_msg(self.parent, self.tag, .msg, fmt, args);
}
fn err_msg(self: *const Process, comptime fmt: anytype, args: anytype) void {
send_msg(self.parent, self.tag, .err, fmt, args);
}
fn send_msg(proc: tp.pid, tag: []const u8, type_: enum { msg, err }, comptime fmt: anytype, args: anytype) void {
var buf: [@import("log").max_log_message]u8 = undefined;
const output = std.fmt.bufPrint(&buf, fmt, args) catch "MESSAGE TOO LARGE";
proc.send(.{ "lsp", type_, tag, output }) catch {};
}
fn start(self: *Process) tp.result {
const frame = tracy.initZone(@src(), .{ .name = module_name ++ " start" });
defer frame.deinit();
@ -255,8 +268,9 @@ const Process = struct {
var log_file_path = std.ArrayList(u8).init(self.allocator);
defer log_file_path.deinit();
const state_dir = root.get_state_dir() catch |e| return tp.exit_error(e, @errorReturnTrace());
log_file_path.writer().print("{s}/lsp-{s}.log", .{ state_dir, self.tag }) catch |e| return tp.exit_error(e, @errorReturnTrace());
log_file_path.writer().print("{s}{c}lsp-{s}.log", .{ state_dir, std.fs.path.sep, self.tag }) catch |e| return tp.exit_error(e, @errorReturnTrace());
self.log_file = std.fs.createFileAbsolute(log_file_path.items, .{ .truncate = true }) catch |e| return tp.exit_error(e, @errorReturnTrace());
self.log_file_path = log_file_path.toOwnedSlice() catch null;
}
fn receive(self: *Process, from: tp.pid_ref, m: tp.message) tp.result {
@ -440,18 +454,14 @@ const Process = struct {
}
fn handle_not_found(self: *Process) error{ExitNormal}!void {
const logger = log.logger("LSP");
defer logger.deinit();
logger.print_err("init", "'{s}' executable not found", .{self.tag});
self.err_msg("'{s}' executable not found", .{self.tag});
self.write_log("### '{s}' executable not found ###\n", .{self.tag});
self.parent.send(.{ sp_tag, self.tag, "not found" }) catch {};
return error.ExitNormal;
}
fn handle_terminated(self: *Process, err: []const u8, code: u32) error{ExitNormal}!void {
const logger = log.logger("LSP");
defer logger.deinit();
logger.print("terminated: {s} {d}", .{ err, code });
self.msg("terminated: {s} {d}", .{ err, code });
self.write_log("### subprocess terminated {s} {d} ###\n", .{ err, code });
self.parent.send(.{ sp_tag, self.tag, "done" }) catch {};
return error.ExitNormal;
@ -463,9 +473,9 @@ const Process = struct {
const id = self.next_id;
self.next_id += 1;
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const msg_writer = msg.writer();
var request = std.ArrayList(u8).init(self.allocator);
defer request.deinit();
const msg_writer = request.writer();
try cbor.writeMapHeader(msg_writer, 4);
try cbor.writeValue(msg_writer, "jsonrpc");
try cbor.writeValue(msg_writer, "2.0");
@ -476,7 +486,7 @@ const Process = struct {
try cbor.writeValue(msg_writer, "params");
_ = try msg_writer.write(params_cb);
const json = try cbor.toJsonAlloc(self.allocator, msg.items);
const json = try cbor.toJsonAlloc(self.allocator, request.items);
defer self.allocator.free(json);
var output = std.ArrayList(u8).init(self.allocator);
defer output.deinit();
@ -499,9 +509,9 @@ const Process = struct {
fn send_response(self: *Process, cbor_id: []const u8, result_cb: []const u8) (error{Closed} || SendError || cbor.Error || cbor.JsonEncodeError)!void {
const sp = if (self.sp) |*sp| sp else return error.Closed;
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const msg_writer = msg.writer();
var response = std.ArrayList(u8).init(self.allocator);
defer response.deinit();
const msg_writer = response.writer();
try cbor.writeMapHeader(msg_writer, 3);
try cbor.writeValue(msg_writer, "jsonrpc");
try cbor.writeValue(msg_writer, "2.0");
@ -510,7 +520,7 @@ const Process = struct {
try cbor.writeValue(msg_writer, "result");
_ = try msg_writer.write(result_cb);
const json = try cbor.toJsonAlloc(self.allocator, msg.items);
const json = try cbor.toJsonAlloc(self.allocator, response.items);
defer self.allocator.free(json);
var output = std.ArrayList(u8).init(self.allocator);
defer output.deinit();
@ -528,9 +538,9 @@ const Process = struct {
fn send_error_response(self: *Process, cbor_id: []const u8, error_code: ErrorCode, message: []const u8) (error{Closed} || SendError || cbor.Error || cbor.JsonEncodeError)!void {
const sp = if (self.sp) |*sp| sp else return error.Closed;
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const msg_writer = msg.writer();
var response = std.ArrayList(u8).init(self.allocator);
defer response.deinit();
const msg_writer = response.writer();
try cbor.writeMapHeader(msg_writer, 3);
try cbor.writeValue(msg_writer, "jsonrpc");
try cbor.writeValue(msg_writer, "2.0");
@ -543,7 +553,7 @@ const Process = struct {
try cbor.writeValue(msg_writer, "message");
try cbor.writeValue(msg_writer, message);
const json = try cbor.toJsonAlloc(self.allocator, msg.items);
const json = try cbor.toJsonAlloc(self.allocator, response.items);
defer self.allocator.free(json);
var output = std.ArrayList(u8).init(self.allocator);
defer output.deinit();
@ -563,9 +573,9 @@ const Process = struct {
const have_params = !(cbor.match(params_cb, cbor.null_) catch false);
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const msg_writer = msg.writer();
var notification = std.ArrayList(u8).init(self.allocator);
defer notification.deinit();
const msg_writer = notification.writer();
try cbor.writeMapHeader(msg_writer, 3);
try cbor.writeValue(msg_writer, "jsonrpc");
try cbor.writeValue(msg_writer, "2.0");
@ -578,7 +588,7 @@ const Process = struct {
try cbor.writeMapHeader(msg_writer, 0);
}
const json = try cbor.toJsonAlloc(self.allocator, msg.items);
const json = try cbor.toJsonAlloc(self.allocator, notification.items);
defer self.allocator.free(json);
var output = std.ArrayList(u8).init(self.allocator);
defer output.deinit();
@ -617,9 +627,9 @@ const Process = struct {
const json = if (params) |p| try cbor.toJsonPrettyAlloc(self.allocator, p) else null;
defer if (json) |p| self.allocator.free(p);
self.write_log("### RECV req: {s}\nmethod: {s}\n{s}\n###\n", .{ json_id, method, json orelse "no params" });
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const writer = msg.writer();
var request = std.ArrayList(u8).init(self.allocator);
defer request.deinit();
const writer = request.writer();
try cbor.writeArrayHeader(writer, 7);
try cbor.writeValue(writer, sp_tag);
try cbor.writeValue(writer, self.project);
@ -628,7 +638,7 @@ const Process = struct {
try cbor.writeValue(writer, method);
try writer.writeAll(cbor_id);
if (params) |p| _ = try writer.write(p) else try cbor.writeValue(writer, null);
self.parent.send_raw(.{ .buf = msg.items }) catch return error.SendFailed;
self.parent.send_raw(.{ .buf = request.items }) catch return error.SendFailed;
}
fn receive_lsp_response(self: *Process, cbor_id: []const u8, result: ?[]const u8, err: ?[]const u8) Error!void {
@ -640,9 +650,9 @@ const Process = struct {
defer if (json_err) |p| self.allocator.free(p);
self.write_log("### RECV rsp: {s} {s}\n{s}\n###\n", .{ json_id, if (json_err) |_| "error" else "response", json_err orelse json orelse "no result" });
const from = self.requests.get(cbor_id) orelse return;
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const writer = msg.writer();
var response = std.ArrayList(u8).init(self.allocator);
defer response.deinit();
const writer = response.writer();
try cbor.writeArrayHeader(writer, 4);
try cbor.writeValue(writer, sp_tag);
try cbor.writeValue(writer, self.tag);
@ -653,16 +663,16 @@ const Process = struct {
try cbor.writeValue(writer, "result");
_ = try writer.write(result_);
}
from.send_raw(.{ .buf = msg.items }) catch return error.SendFailed;
from.send_raw(.{ .buf = response.items }) catch return error.SendFailed;
}
fn receive_lsp_notification(self: *Process, method: []const u8, params: ?[]const u8) Error!void {
const json = if (params) |p| try cbor.toJsonPrettyAlloc(self.allocator, p) else null;
defer if (json) |p| self.allocator.free(p);
self.write_log("### RECV notify:\nmethod: {s}\n{s}\n###\n", .{ method, json orelse "no params" });
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const writer = msg.writer();
var notification = std.ArrayList(u8).init(self.allocator);
defer notification.deinit();
const writer = notification.writer();
try cbor.writeArrayHeader(writer, 6);
try cbor.writeValue(writer, sp_tag);
try cbor.writeValue(writer, self.project);
@ -670,7 +680,7 @@ const Process = struct {
try cbor.writeValue(writer, "notify");
try cbor.writeValue(writer, method);
if (params) |p| _ = try writer.write(p) else try cbor.writeValue(writer, null);
self.parent.send_raw(.{ .buf = msg.items }) catch return error.SendFailed;
self.parent.send_raw(.{ .buf = notification.items }) catch return error.SendFailed;
}
fn write_log(self: *Process, comptime format: []const u8, args: anytype) void {

View file

@ -8,6 +8,7 @@ const Buffer = @import("Buffer");
const fuzzig = @import("fuzzig");
const tracy = @import("tracy");
const git = @import("git");
const file_type_config = @import("file_type_config");
const builtin = @import("builtin");
const LSP = @import("LSP.zig");
@ -52,6 +53,9 @@ pub const LspOrClientError = (LspError || ClientError);
const File = struct {
path: []const u8,
type: []const u8,
icon: []const u8,
color: u24,
mtime: i128,
pos: FilePos = .{},
visited: bool = false,
@ -341,7 +345,7 @@ pub fn request_n_most_recent_file(self: *Self, from: tp.pid_ref, n: usize) Clien
pub fn request_recent_files(self: *Self, from: tp.pid_ref, max: usize) ClientError!void {
defer from.send(.{ "PRJ", "recent_done", self.longest_file_path, "" }) catch {};
for (self.files.items, 0..) |file, i| {
from.send(.{ "PRJ", "recent", self.longest_file_path, file.path }) catch return error.ClientFailed;
from.send(.{ "PRJ", "recent", self.longest_file_path, file.path, file.type, file.icon, file.color }) catch return error.ClientFailed;
if (i >= max) return;
}
}
@ -356,7 +360,7 @@ fn simple_query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: [
defer self.allocator.free(matches);
var n: usize = 0;
while (n < query.len) : (n += 1) matches[n] = idx + n;
from.send(.{ "PRJ", "recent", self.longest_file_path, file.path, matches }) catch return error.ClientFailed;
from.send(.{ "PRJ", "recent", self.longest_file_path, file.path, file.type, file.icon, file.color, matches }) catch return error.ClientFailed;
i += 1;
if (i >= max) return i;
}
@ -379,6 +383,9 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co
const Match = struct {
path: []const u8,
type: []const u8,
icon: []const u8,
color: u24,
score: i32,
matches: []const usize,
};
@ -389,6 +396,9 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co
if (match.score) |score| {
(try matches.addOne()).* = .{
.path = file.path,
.type = file.type,
.icon = file.icon,
.color = file.color,
.score = score,
.matches = try self.allocator.dupe(usize, match.matches),
};
@ -404,13 +414,24 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co
std.mem.sort(Match, matches.items, {}, less_fn);
for (matches.items[0..@min(max, matches.items.len)]) |match|
from.send(.{ "PRJ", "recent", self.longest_file_path, match.path, match.matches }) catch return error.ClientFailed;
from.send(.{ "PRJ", "recent", self.longest_file_path, match.path, match.type, match.icon, match.color, match.matches }) catch return error.ClientFailed;
return @min(max, matches.items.len);
}
pub fn walk_tree_entry(self: *Self, file_path: []const u8, mtime: i128) OutOfMemoryError!void {
pub fn walk_tree_entry(
self: *Self,
file_path: []const u8,
mtime: i128,
) OutOfMemoryError!void {
const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(file_path);
self.longest_file_path = @max(self.longest_file_path, file_path.len);
(try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime };
(try self.pending.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, file_path),
.type = file_type,
.icon = file_icon,
.color = file_color,
.mtime = mtime,
};
}
pub fn walk_tree_done(self: *Self, parent: tp.pid_ref) OutOfMemoryError!void {
@ -420,6 +441,35 @@ pub fn walk_tree_done(self: *Self, parent: tp.pid_ref) OutOfMemoryError!void {
return self.loaded(parent);
}
fn default_ft() struct { []const u8, []const u8, u24 } {
return .{
file_type_config.default.name,
file_type_config.default.icon,
file_type_config.default.color,
};
}
pub fn guess_path_file_type(path: []const u8, file_name: []const u8) struct { []const u8, []const u8, u24 } {
var buf: [4096]u8 = undefined;
const file_path = std.fmt.bufPrint(&buf, "{s}{}{s}", .{ path, std.fs.path.sep, file_name }) catch return default_ft();
return guess_file_type(file_path);
}
pub fn guess_file_type(file_path: []const u8) struct { []const u8, []const u8, u24 } {
var buf: [1024]u8 = undefined;
const content: []const u8 = blk: {
const file = std.fs.cwd().openFile(file_path, .{ .mode = .read_only }) catch break :blk &.{};
defer file.close();
const size = file.read(&buf) catch break :blk &.{};
break :blk buf[0..size];
};
return if (file_type_config.guess_file_type(file_path, content)) |ft| .{
ft.name,
ft.icon orelse file_type_config.default.icon,
ft.color orelse file_type_config.default.color,
} else default_ft();
}
fn merge_pending_files(self: *Self) OutOfMemoryError!void {
defer self.sort_files_by_mtime();
const existing = try self.files.toOwnedSlice(self.allocator);
@ -469,9 +519,13 @@ fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usi
}
return;
}
const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(file_path);
if (row != 0) {
(try self.files.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, file_path),
.type = file_type,
.icon = file_icon,
.color = file_color,
.mtime = mtime,
.pos = .{ .row = row, .col = col },
.visited = true,
@ -479,6 +533,9 @@ fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usi
} else {
(try self.files.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, file_path),
.type = file_type,
.icon = file_icon,
.color = file_color,
.mtime = mtime,
};
}
@ -1473,7 +1530,16 @@ fn read_position(position: []const u8) !Position {
return .{ .line = line.?, .character = character.? };
}
pub fn show_message(self: *Self, _: tp.pid_ref, params_cb: []const u8) !void {
pub fn show_message(self: *Self, params_cb: []const u8) !void {
return self.show_or_log_message(.show, params_cb);
}
pub fn log_message(self: *Self, params_cb: []const u8) !void {
return self.show_or_log_message(.log, params_cb);
}
fn show_or_log_message(self: *Self, operation: enum { show, log }, params_cb: []const u8) !void {
if (!tp.env.get().is("lsp_verbose")) return;
var type_: i32 = 0;
var message: ?[]const u8 = null;
var iter = params_cb;
@ -1493,7 +1559,14 @@ pub fn show_message(self: *Self, _: tp.pid_ref, params_cb: []const u8) !void {
if (type_ <= 2)
self.logger_lsp.err_msg("lsp", msg)
else
self.logger_lsp.print("{s}", .{msg});
self.logger_lsp.print("{s}: {s}", .{ @tagName(operation), msg });
}
pub fn show_notification(self: *Self, method: []const u8, params_cb: []const u8) !void {
if (!tp.env.get().is("lsp_verbose")) return;
const params = try cbor.toJsonAlloc(self.allocator, params_cb);
defer self.allocator.free(params);
self.logger_lsp.print("LSP notification: {s} -> {s}", .{ method, params });
}
pub fn register_capability(self: *Self, from: tp.pid_ref, cbor_id: []const u8, params_cb: []const u8) ClientError!void {
@ -1941,7 +2014,14 @@ pub fn process_git(self: *Self, parent: tp.pid_ref, m: tp.message) (OutOfMemoryE
} else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.extract(&path) })) {
self.longest_file_path = @max(self.longest_file_path, path.len);
const stat = std.fs.cwd().statFile(path) catch return;
(try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, path), .mtime = stat.mtime };
const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(path);
(try self.pending.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, path),
.type = file_type,
.icon = file_icon,
.color = file_color,
.mtime = stat.mtime,
};
} else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.null_ })) {
self.state.workspace_files = .done;
try self.loaded(parent);

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

View file

@ -33,6 +33,7 @@ const Vtable = struct {
pub const Metadata = struct {
description: []const u8 = &[_]u8{},
arguments: []const ArgumentType = &[_]ArgumentType{},
icon: ?[]const u8 = null,
};
pub const ArgumentType = enum {
@ -188,6 +189,11 @@ pub fn get_arguments(id: ID) ?[]const ArgumentType {
return (commands.items[id] orelse return null).meta.arguments;
}
pub fn get_icon(id: ID) ?[]const u8 {
if (id >= commands.items.len) return null;
return (commands.items[id] orelse return null).meta.icon;
}
const suppressed_errors = std.StaticStringMap(void).initComptime(.{
.{ "enable_fast_scroll", void },
.{ "disable_fast_scroll", void },

View file

@ -36,6 +36,13 @@ show_fileicons: bool = true,
start_debugger_on_crash: bool = false,
widget_style: WidgetStyle = .compact,
palette_style: WidgetStyle = .bars_top_bottom,
panel_style: WidgetStyle = .compact,
home_style: WidgetStyle = .bars_top_bottom,
lsp_output: enum { quiet, verbose } = .quiet,
include_files: []const u8 = "",
pub const DigitStyle = enum {
@ -56,3 +63,25 @@ pub const IndentMode = enum {
spaces,
tabs,
};
pub const WidgetType = enum {
none,
palette,
panel,
home,
};
pub const WidgetStyle = enum {
bars_top_bottom,
bars_left_right,
thick_boxed,
extra_thick_boxed,
dotted_boxed,
rounded_boxed,
double_boxed,
single_double_top_bottom_boxed,
single_double_left_right_boxed,
boxed,
spacious,
compact,
};

View file

@ -20,6 +20,8 @@ pub const default = struct {
pub const color = 0x000000;
};
pub const folder_icon = "";
fn from_file_type(file_type: syntax.FileType) @This() {
return .{
.name = file_type.name,
@ -84,7 +86,7 @@ pub fn get(file_type_name: []const u8) !?@This() {
break :file_type if (syntax.FileType.get_by_name_static(file_type_name)) |ft| from_file_type(ft) else null;
}
};
try cache.put(cache_allocator, file_type_name, file_type);
try cache.put(cache_allocator, try cache_allocator.dupe(u8, file_type_name), file_type);
break :self file_type;
};
}

View file

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

View file

@ -11,6 +11,8 @@ subscriber: ?tp.pid,
heap: [32 + 1024]u8,
fba: std.heap.FixedBufferAllocator,
msg_store: MsgStore,
no_stdout: bool = false,
no_stderr: bool = false,
const MsgStore = std.DoublyLinkedList;
const MsgStoreEntry = struct {
@ -85,12 +87,23 @@ fn store_reset(self: *Self) void {
fn receive(self: *Self, from: tp.pid_ref, m: tp.message) tp.result {
errdefer self.deinit();
if (try m.match(.{ "log", tp.more })) {
var output: []const u8 = undefined;
if (try m.match(.{ "log", "error", tp.string, "std.log", "->", tp.extract(&output) })) {
if (self.subscriber) |subscriber| {
subscriber.send_raw(m) catch {};
} else {
self.store(m);
}
if (!self.no_stderr)
std.io.getStdErr().writer().print("{s}\n", .{output}) catch {};
} else if (try m.match(.{ "log", tp.string, tp.extract(&output) })) {
if (self.subscriber) |subscriber| {
subscriber.send_raw(m) catch {};
} else {
self.store(m);
}
if (!self.no_stdout)
std.io.getStdOut().writer().print("{s}\n", .{output}) catch {};
} else if (try m.match(.{"subscribe"})) {
// log("subscribed");
if (self.subscriber) |*s| s.deinit();
@ -101,6 +114,14 @@ fn receive(self: *Self, from: tp.pid_ref, m: tp.message) tp.result {
if (self.subscriber) |*s| s.deinit();
self.subscriber = null;
self.store_reset();
} else if (try m.match(.{ "stdout", "enable" })) {
self.no_stdout = false;
} else if (try m.match(.{ "stdout", "disable" })) {
self.no_stdout = true;
} else if (try m.match(.{ "stderr", "enable" })) {
self.no_stderr = false;
} else if (try m.match(.{ "stderr", "disable" })) {
self.no_stderr = true;
} else if (try m.match(.{"shutdown"})) {
return tp.exit_normal();
}
@ -208,6 +229,14 @@ pub fn unsubscribe() tp.result {
return tp.env.get().proc("log").send(.{"unsubscribe"});
}
pub fn stdout(state: enum { enable, disable }) void {
tp.env.get().proc("log").send(.{ "stdout", state }) catch {};
}
pub fn stderr(state: enum { enable, disable }) void {
tp.env.get().proc("log").send(.{ "stderr", state }) catch {};
}
var std_log_pid: ?tp.pid_ref = null;
pub fn set_std_log_pid(pid: ?tp.pid_ref) void {

View file

@ -6,6 +6,7 @@ const color = @import("color");
const flags = @import("flags");
const builtin = @import("builtin");
const bin_path = @import("bin_path");
const sep = std.fs.path.sep;
const list_languages = @import("list_languages.zig");
pub const file_link = @import("file_link.zig");
@ -330,7 +331,12 @@ pub fn main() anyerror!void {
try cbor.writeValue(writer, cmd_);
try cbor.writeArrayHeader(writer, count - 1);
while (cmd_args.next()) |arg| try cbor.writeValue(writer, arg);
while (cmd_args.next()) |arg| {
if (std.fmt.parseInt(isize, arg, 10) catch null) |i|
try cbor.writeValue(writer, i)
else
try cbor.writeValue(writer, arg);
}
try tui_proc.send_raw(.{ .buf = msg.items });
}
@ -398,7 +404,7 @@ fn trace_to_file(m: thespian.message.c_buffer_type) callconv(.C) void {
const a = std.heap.c_allocator;
var path = std.ArrayList(u8).init(a);
defer path.deinit();
path.writer().print("{s}/trace.log", .{get_state_dir() catch return}) catch return;
path.writer().print("{s}{c}trace.log", .{ get_state_dir() catch return, sep }) catch return;
const file = std.fs.createFileAbsolute(path.items, .{ .truncate = true }) catch return;
State.state = .{
.file = file,
@ -502,12 +508,12 @@ pub fn parse_text_config_file(T: type, allocator: std.mem.Allocator, conf: *T, b
lineno += 1;
if (line.len == 0 or line[0] == '#')
continue;
const sep = std.mem.indexOfScalar(u8, line, ' ') orelse {
const spc = std.mem.indexOfScalar(u8, line, ' ') orelse {
std.log.err("{s}:{}: {s} missing value", .{ file_name, lineno, line });
continue;
};
const name = line[0..sep];
const value_str = line[sep + 1 ..];
const name = line[0..spc];
const value_str = line[spc + 1 ..];
const cb = cbor.fromJsonAlloc(allocator, value_str) catch {
std.log.err("{s}:{}: {s} has bad value: {s}", .{ file_name, lineno, name, value_str });
continue;
@ -781,6 +787,11 @@ pub const ConfigDirError = error{
AppConfigDirUnavailable,
};
fn make_dir_error(path: []const u8, err: anytype) @TypeOf(err) {
std.log.err("failed to create directory: '{s}'", .{path});
return err;
}
fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 {
const a = std.heap.c_allocator;
const local = struct {
@ -791,22 +802,22 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 {
dir
else if (std.process.getEnvVarOwned(a, "XDG_CONFIG_HOME") catch null) |xdg| ret: {
defer a.free(xdg);
break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/{s}", .{ xdg, appname });
break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}{c}{s}", .{ xdg, sep, appname });
} else if (std.process.getEnvVarOwned(a, "HOME") catch null) |home| ret: {
defer a.free(home);
const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/.config", .{home});
const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}{c}.config", .{ home, sep });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return error.MakeHomeConfigDirFailed,
else => return make_dir_error(dir, error.MakeHomeConfigDirFailed),
};
break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/.config/{s}", .{ home, appname });
break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}{c}.config{c}{s}", .{ home, sep, sep, appname });
} else if (builtin.os.tag == .windows) ret: {
if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| {
defer a.free(appdata);
const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/{s}", .{ appdata, appname });
const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}{c}{s}", .{ appdata, sep, appname });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return error.MakeAppConfigDirFailed,
else => return make_dir_error(dir, error.MakeAppConfigDirFailed),
};
break :ret dir;
} else return error.AppConfigDirUnavailable;
@ -815,14 +826,14 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 {
local.config_dir = config_dir;
std.fs.makeDirAbsolute(config_dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return error.MakeConfigDirFailed,
else => return make_dir_error(config_dir, error.MakeConfigDirFailed),
};
var keybind_dir_buffer: [std.posix.PATH_MAX]u8 = undefined;
std.fs.makeDirAbsolute(try std.fmt.bufPrint(&keybind_dir_buffer, "{s}/{s}", .{ config_dir, keybind_dir })) catch {};
std.fs.makeDirAbsolute(try std.fmt.bufPrint(&keybind_dir_buffer, "{s}{c}{s}", .{ config_dir, sep, keybind_dir })) catch {};
var theme_dir_buffer: [std.posix.PATH_MAX]u8 = undefined;
std.fs.makeDirAbsolute(try std.fmt.bufPrint(&theme_dir_buffer, "{s}/{s}", .{ config_dir, theme_dir })) catch {};
std.fs.makeDirAbsolute(try std.fmt.bufPrint(&theme_dir_buffer, "{s}{c}{s}", .{ config_dir, sep, theme_dir })) catch {};
return config_dir;
}
@ -841,22 +852,22 @@ fn get_app_cache_dir(appname: []const u8) ![]const u8 {
dir
else if (std.process.getEnvVarOwned(a, "XDG_CACHE_HOME") catch null) |xdg| ret: {
defer a.free(xdg);
break :ret try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}/{s}", .{ xdg, appname });
break :ret try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}{c}{s}", .{ xdg, sep, appname });
} else if (std.process.getEnvVarOwned(a, "HOME") catch null) |home| ret: {
defer a.free(home);
const dir = try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}/.cache", .{home});
const dir = try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}{c}.cache", .{ home, sep });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
else => return make_dir_error(dir, e),
};
break :ret try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}/.cache/{s}", .{ home, appname });
break :ret try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}{c}.cache{c}{s}", .{ home, sep, sep, appname });
} else if (builtin.os.tag == .windows) ret: {
if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| {
defer a.free(appdata);
const dir = try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}/{s}", .{ appdata, appname });
const dir = try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}{c}{s}", .{ appdata, sep, appname });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
else => return make_dir_error(dir, e),
};
break :ret dir;
} else return error.AppCacheDirUnavailable;
@ -865,7 +876,7 @@ fn get_app_cache_dir(appname: []const u8) ![]const u8 {
local.cache_dir = cache_dir;
std.fs.makeDirAbsolute(cache_dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
else => return make_dir_error(cache_dir, e),
};
return cache_dir;
}
@ -884,27 +895,27 @@ fn get_app_state_dir(appname: []const u8) ![]const u8 {
dir
else if (std.process.getEnvVarOwned(a, "XDG_STATE_HOME") catch null) |xdg| ret: {
defer a.free(xdg);
break :ret try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/{s}", .{ xdg, appname });
break :ret try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}{s}", .{ xdg, sep, appname });
} else if (std.process.getEnvVarOwned(a, "HOME") catch null) |home| ret: {
defer a.free(home);
var dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/.local", .{home});
var dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}.local", .{ home, sep });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
else => return make_dir_error(dir, e),
};
dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/.local/state", .{home});
dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}.local{c}state", .{ home, sep, sep });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
else => return make_dir_error(dir, e),
};
break :ret try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/.local/state/{s}", .{ home, appname });
break :ret try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}.local{c}state{c}{s}", .{ home, sep, sep, sep, appname });
} else if (builtin.os.tag == .windows) ret: {
if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| {
defer a.free(appdata);
const dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/{s}", .{ appdata, appname });
const dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}{s}", .{ appdata, sep, appname });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
else => return make_dir_error(dir, e),
};
break :ret dir;
} else return error.AppCacheDirUnavailable;
@ -913,7 +924,7 @@ fn get_app_state_dir(appname: []const u8) ![]const u8 {
local.state_dir = state_dir;
std.fs.makeDirAbsolute(state_dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
else => return make_dir_error(state_dir, e),
};
return state_dir;
}
@ -926,7 +937,7 @@ fn get_app_config_dir_file_name(appname: []const u8, comptime config_file_name:
const local = struct {
var config_file_buffer: [std.posix.PATH_MAX]u8 = undefined;
};
return std.fmt.bufPrint(&local.config_file_buffer, "{s}/{s}", .{ try get_app_config_dir(appname), config_file_name });
return std.fmt.bufPrint(&local.config_file_buffer, "{s}{c}{s}", .{ try get_app_config_dir(appname), sep, config_file_name });
}
pub fn get_config_file_name(T: type) ![]const u8 {
@ -942,7 +953,7 @@ pub fn get_restore_file_name() ![]const u8 {
const restore_file = if (local.restore_file) |file|
file
else
try std.fmt.bufPrint(&local.restore_file_buffer, "{s}/{s}", .{ try get_app_state_dir(application_name), restore_file_name });
try std.fmt.bufPrint(&local.restore_file_buffer, "{s}{c}{s}", .{ try get_app_state_dir(application_name), sep, restore_file_name });
local.restore_file = restore_file;
return restore_file;
}
@ -958,7 +969,7 @@ fn get_keybind_namespaces_directory() ![]const u8 {
defer a.free(dir);
return try std.fmt.bufPrint(&local.dir_buffer, "{s}", .{dir});
}
return try std.fmt.bufPrint(&local.dir_buffer, "{s}/{s}", .{ try get_app_config_dir(application_name), keybind_dir });
return try std.fmt.bufPrint(&local.dir_buffer, "{s}{c}{s}", .{ try get_app_config_dir(application_name), sep, keybind_dir });
}
pub fn get_keybind_namespace_file_name(namespace_name: []const u8) ![]const u8 {
@ -966,7 +977,7 @@ pub fn get_keybind_namespace_file_name(namespace_name: []const u8) ![]const u8 {
const local = struct {
var file_buffer: [std.posix.PATH_MAX]u8 = undefined;
};
return try std.fmt.bufPrint(&local.file_buffer, "{s}/{s}.json", .{ dir, namespace_name });
return try std.fmt.bufPrint(&local.file_buffer, "{s}{c}{s}.json", .{ dir, sep, namespace_name });
}
const theme_dir = "themes";
@ -980,7 +991,7 @@ fn get_theme_directory() ![]const u8 {
defer a.free(dir);
return try std.fmt.bufPrint(&local.dir_buffer, "{s}", .{dir});
}
return try std.fmt.bufPrint(&local.dir_buffer, "{s}/{s}", .{ try get_app_config_dir(application_name), theme_dir });
return try std.fmt.bufPrint(&local.dir_buffer, "{s}{c}{s}", .{ try get_app_config_dir(application_name), sep, theme_dir });
}
pub fn get_theme_file_name(theme_name: []const u8) ![]const u8 {
@ -988,7 +999,7 @@ pub fn get_theme_file_name(theme_name: []const u8) ![]const u8 {
const local = struct {
var file_buffer: [std.posix.PATH_MAX]u8 = undefined;
};
return try std.fmt.bufPrint(&local.file_buffer, "{s}/{s}.json", .{ dir, theme_name });
return try std.fmt.bufPrint(&local.file_buffer, "{s}{c}{s}.json", .{ dir, sep, theme_name });
}
fn restart() noreturn {

View file

@ -27,7 +27,7 @@ const OutOfMemoryError = error{OutOfMemory};
const FileSystemError = error{FileSystem};
const SetCwdError = if (builtin.os.tag == .windows) error{UnrecognizedVolume} else error{};
const CallError = tp.CallError;
const ProjectManagerError = (SpawnError || error{ProjectManagerFailed});
const ProjectManagerError = (SpawnError || error{ ProjectManagerFailed, InvalidProjectDirectory });
pub fn get() SpawnError!Self {
const pid = tp.env.get().proc(module_name);
@ -63,6 +63,7 @@ pub fn open(rel_project_directory: []const u8) (ProjectManagerError || FileSyste
const project_directory = std.fs.cwd().realpath(rel_project_directory, &path_buf) catch "(none)";
const current_project = tp.env.get().str("project");
if (std.mem.eql(u8, current_project, project_directory)) return;
if (!root.is_directory(project_directory)) return error.InvalidProjectDirectory;
var dir = try std.fs.openDirAbsolute(project_directory, .{});
try dir.setAsCwd();
dir.close();
@ -97,9 +98,9 @@ pub fn request_recent_files(max: usize) (ProjectManagerError || ProjectError)!vo
return send(.{ "request_recent_files", project, max });
}
pub fn request_recent_projects(allocator: std.mem.Allocator) (ProjectError || CallError)!tp.message {
pub fn request_recent_projects() (ProjectManagerError || ProjectError)!void {
const project = tp.env.get().str("project");
return (try get()).pid.call(allocator, request_timeout, .{ "request_recent_projects", project });
return send(.{ "request_recent_projects", project });
}
pub fn query_recent_files(max: usize, query: []const u8) (ProjectManagerError || ProjectError)!void {
@ -332,6 +333,8 @@ const Process = struct {
var n: usize = 0;
var task: []const u8 = undefined;
var context: usize = undefined;
var tag: []const u8 = undefined;
var message: []const u8 = undefined;
var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf);
@ -404,6 +407,11 @@ const Process = struct {
self.hover(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed;
} else if (try cbor.match(m.buf, .{ "get_mru_position", tp.extract(&project_directory), tp.extract(&path) })) {
self.get_mru_position(from, project_directory, path) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed;
} else if (try cbor.match(m.buf, .{ "lsp", "msg", tp.extract(&tag), tp.extract(&message) })) {
if (tp.env.get().is("lsp_verbose"))
self.logger.print("{s}: {s}", .{ tag, message });
} else if (try cbor.match(m.buf, .{ "lsp", "err", tp.extract(&tag), tp.extract(&message) })) {
self.logger.print("{s} error: {s}", .{ tag, message });
} else if (try cbor.match(m.buf, .{"shutdown"})) {
self.persist_projects();
from.send(.{ "project_manager", "shutdown" }) catch return error.ClientFailed;
@ -461,6 +469,9 @@ const Process = struct {
self.sort_projects_by_last_used(&recent_projects);
var message = std.ArrayList(u8).init(self.allocator);
const writer = message.writer();
try cbor.writeArrayHeader(writer, 3);
try cbor.writeValue(writer, "PRJ");
try cbor.writeValue(writer, "recent_projects");
try cbor.writeArrayHeader(writer, recent_projects.items.len);
for (recent_projects.items) |project| {
try cbor.writeArrayHeader(writer, 2);
@ -468,6 +479,7 @@ const Process = struct {
try cbor.writeValue(writer, if (self.projects.get(project.name)) |_| true else false);
}
from.send_raw(.{ .buf = message.items }) catch return error.ClientFailed;
self.logger.print("{d} projects found", .{recent_projects.items.len});
}
fn query_recent_files(self: *Process, from: tp.pid_ref, project_directory: []const u8, max: usize, query: []const u8) (ProjectError || Project.ClientError)!void {
@ -603,14 +615,11 @@ const Process = struct {
return if (std.mem.eql(u8, method, "textDocument/publishDiagnostics"))
project.publish_diagnostics(self.parent.ref(), params_cb)
else if (std.mem.eql(u8, method, "window/showMessage"))
project.show_message(self.parent.ref(), params_cb)
project.show_message(params_cb)
else if (std.mem.eql(u8, method, "window/logMessage"))
project.show_message(self.parent.ref(), params_cb)
else {
const params = try cbor.toJsonAlloc(self.allocator, params_cb);
defer self.allocator.free(params);
self.logger.print("LSP notification: {s} -> {s}", .{ method, params });
};
project.log_message(params_cb)
else
project.show_notification(method, params_cb);
}
fn dispatch_request(self: *Process, from: tp.pid_ref, project_directory: []const u8, language_server: []const u8, method: []const u8, cbor_id: []const u8, params_cb: []const u8) (ProjectError || Project.ClientError || cbor.Error || cbor.JsonEncodeError || UnsupportedError)!void {
@ -793,12 +802,19 @@ fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_
var iter = self.dir.iterateAssumeFirstIteration();
errdefer |e| self.parent.send(.{ "PRJ", "path_error", self.project_name, self.path, e }) catch {};
while (try iter.next()) |entry| {
switch (entry.kind) {
.directory => try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, "DIR", entry.name }),
.sym_link => try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, "LINK", entry.name }),
.file => try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, "FILE", entry.name }),
const event_type = switch (entry.kind) {
.directory => "DIR",
.sym_link => "LINK",
.file => "FILE",
else => continue,
}
};
const default = file_type_config.default;
const file_type, const icon, const color = switch (entry.kind) {
.directory => .{ "directory", file_type_config.folder_icon, default.color },
.sym_link, .file => Project.guess_path_file_type(self.path, entry.name),
else => .{ default.name, default.icon, default.color },
};
try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, event_type, entry.name, file_type, icon, color });
count += 1;
if (count >= self.max) break;
}

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 => {
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",
pos: Widget.Box = .{ .y = 0, .x = 0, .w = 12, .h = 1 },
ctx: Context,
padding: u8 = 1,
icon: ?[]const u8 = null,
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,
@ -29,18 +31,22 @@ pub fn Options(context: type) type {
self.plane.set_style(style_label);
self.plane.fill(" ");
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) {
_ = self.plane.print(" {s} ", .{self.text.items}) catch {};
_ = self.plane.print("{s} ", .{self.text.items}) catch {};
} else {
_ = self.plane.print(" {s} ", .{self.label.items}) catch {};
_ = self.plane.print("{s} ", .{self.label.items}) catch {};
}
if (self.cursor) |cursor| {
const pos: c_int = @intCast(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 {};
} 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();
_ = self.plane.at_cursor_cell(&cell) catch return false;
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,
.label = 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);
self.opts.label = self.label.items;
@ -83,6 +90,7 @@ pub fn State(ctx_type: type) type {
label: std.ArrayList(u8),
opts: Options(ctx_type),
text: std.ArrayList(u8),
icon_width: c_int,
cursor: ?usize = 0,
const Self = @This();

View file

@ -14,13 +14,15 @@ pub const scroll_lines = 3;
pub fn Options(context: type) type {
return struct {
ctx: Context,
style: Widget.Type,
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_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_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,
pub const Context = context;
@ -46,23 +48,26 @@ pub fn Options(context: type) type {
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_;
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) {
const self = try allocator.create(State(ctx_type));
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.* = .{
.allocator = allocator,
.menu = try WidgetList.createV(allocator, container.plane, @typeName(@This()), .dynamic),
.container = container,
.container_widget = container.widget(),
.frame_widget = null,
.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
else
@ -72,7 +77,8 @@ pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts:
self.menu.ctx = self;
self.menu.on_render = State(ctx_type).on_render_menu;
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());
if (self.scrollbar) |sb| try container.add(sb.widget());
return self;
@ -84,6 +90,7 @@ pub fn State(ctx_type: type) type {
menu: *WidgetList,
container: *WidgetList,
container_widget: Widget,
frame_widget: ?Widget,
scrollbar: ?*scrollbar_v,
opts: options_type,
selected: ?usize = null,
@ -146,9 +153,14 @@ pub fn State(ctx_type: type) type {
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));
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 {
@ -170,7 +182,7 @@ pub fn State(ctx_type: type) type {
}
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 {

View file

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

View file

@ -10,6 +10,9 @@ pub const Box = @import("Box.zig");
pub const Theme = @import("theme");
pub const themes = @import("themes").themes;
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,
plane: *Plane,

View file

@ -6,6 +6,7 @@ const tp = @import("thespian");
const Plane = @import("renderer").Plane;
const tui = @import("tui.zig");
const Widget = @import("Widget.zig");
const Box = @import("Box.zig");
@ -26,25 +27,35 @@ widgets: ArrayList(WidgetState),
layout_: Layout,
layout_empty: bool = true,
direction: Direction,
box: ?Widget.Box = null,
deco_box: Widget.Box,
ctx: ?*anyopaque = null,
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,
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,
widget_type: Widget.Type,
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);
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();
return 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);
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();
return self;
}
@ -57,15 +68,21 @@ pub fn createBox(allocator: Allocator, parent: Plane, name: [:0]const u8, dir: D
return self;
}
fn init(allocator: Allocator, parent: Plane, name: [:0]const u8, dir: Direction, layout_: Layout, box: Box) !Self {
return .{
.plane = try Plane.init(&box.opts(name), parent),
fn init(allocator: Allocator, parent: Plane, name: [:0]const u8, dir: Direction, layout_: Layout, box_: Box, widget_type: Widget.Type) !Self {
var self: Self = .{
.plane = undefined,
.parent = parent,
.allocator = allocator,
.widgets = ArrayList(WidgetState).init(allocator),
.layout_ = layout_,
.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 {
@ -147,18 +164,27 @@ pub fn update(self: *Self) void {
}
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())) {
self.refresh_layout();
self.refresh_layout(padding);
break;
};
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;
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)) {
more = true;
};
}
}
self.after_render(self.ctx, theme);
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 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 {
if (try m.match(.{ "H", tp.more }))
return false;
@ -176,6 +237,13 @@ pub fn receive(self: *Self, from_: tp.pid_ref, m: tp.message) error{Exit}!bool {
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 {
return switch (self.direction) {
.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 {
return switch (self.direction) {
.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 {
return switch (self.direction) {
.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 {
return switch (self.direction) {
.vertical => &pos.x,
@ -204,28 +293,62 @@ fn get_loc_b(self: *Self, pos: *Widget.Box) *usize {
};
}
fn refresh_layout(self: *Self) void {
return if (self.box) |box| self.handle_resize(box);
fn refresh_layout(self: *Self, padding: Widget.Style.Margin) void {
return self.handle_resize(self.to_client_box(self.deco_box, padding));
}
pub fn handle_resize(self: *Self, pos: Widget.Box) void {
self.on_resize(self.ctx, self, pos);
pub fn handle_resize(self: *Self, box: Widget.Box) void {
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 {
self.resize(pos);
pub inline fn to_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;
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 {
return self.layout_;
}
pub fn resize(self: *Self, pos_: Widget.Box) void {
self.box = pos_;
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;
const total = self.get_size_a(&pos).*;
pub fn resize(self: *Self, box: Widget.Box) void {
return self.handle_resize(box);
}
fn do_resize(self: *Self, padding: Widget.Style.Margin) void {
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 statics: 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 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;
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;
cur_loc += size;
self.get_size_b(&w_pos).* = self.get_size_b(&pos).*;
self.get_loc_b(&w_pos).* = self.get_loc_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_const(&client_box);
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 {
begin: Cursor = Cursor{},
end: Cursor = Cursor{},
@ -351,6 +353,9 @@ pub const Editor = struct {
diag_hints: usize = 0,
completions: std.ArrayListUnmanaged(u8) = .empty,
completion_row: usize = 0,
completion_col: usize = 0,
completion_is_complete: bool = true,
enable_auto_save: bool,
enable_format_on_save: bool,
@ -432,6 +437,7 @@ pub const Editor = struct {
tp.extract_cbor(&cursels_cbor),
}))
return error.RestoreStateMatch;
self.refresh_tab_width();
if (op == .open_file)
try self.open(file_path);
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 {
const logger = log.logger("editor");
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_size = if (indent_mode == .tabs) tab_width else tui.config().indent_size;
self.* = Self{
@ -702,6 +708,23 @@ pub const Editor = struct {
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 {
var meta = std.ArrayListUnmanaged(u8).empty;
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 leading_ws = @min(find_first_non_ws(root, row, self.metrics), cursel.cursor.col);
var sfa = std.heap.stackFallback(512, self.allocator);
@ -4468,6 +4493,7 @@ pub const Editor = struct {
_ = try writer.write("\n");
try self.generate_leading_ws(&writer, leading_ws);
var root_ = try self.insert(root, cursel, stream.items, 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);
if (leading_ws_ > leading_ws and leading_ws_ > cursel.cursor.col) {
@ -4499,11 +4525,11 @@ pub const Editor = struct {
(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) {
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;
if (indent_extra)
root = try self.indent_cursel(root, cursel, b.allocator);
@ -5389,11 +5415,18 @@ pub const Editor = struct {
var column: usize = 0;
var have_sel: bool = false;
var sel: Selection = .{};
var pos_type: PosType = .column;
if (try ctx.args.match(.{
tp.extract(&line),
tp.extract(&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(.{
tp.extract(&line),
tp.extract(&column),
@ -5404,9 +5437,29 @@ pub const Editor = struct {
})) {
// self.logger.print("goto: l:{d} c:{d} {any}", .{ line, column, sel });
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;
self.cancel_all_selections();
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();
try primary.cursor.move_to(
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 {
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);
_ = row;
_ = col;
_ = is_incomplete;
self.completion_is_complete = is_incomplete;
}
pub fn select(self: *Self, ctx: Context) Result {
@ -6029,6 +6085,8 @@ pub const EditorWidget = struct {
last_btn: input.Mouse = .none,
last_btn_time_ms: i64 = 0,
last_btn_count: usize = 0,
last_btn_x: c_int = 0,
last_btn_y: c_int = 0,
hover: bool = false,
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 {
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) {
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) {
self.last_btn_count = 3;
try self.editor.primary_triple_click(y_, x_);

View file

@ -33,8 +33,10 @@ view_rows: usize = 0,
view_cols: usize = 0,
entries: std.ArrayList(Entry) = undefined,
selected: ?usize = null,
box: Widget.Box = .{},
const path_column_ratio = 4;
const widget_type: Widget.Type = .panel;
const Entry = struct {
path: []const u8,
@ -44,6 +46,7 @@ const Entry = struct {
end_pos: usize,
lines: []const u8,
severity: editor.Diagnostic.Severity = .Information,
pos_type: editor.PosType,
};
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),
.menu = try Menu.create(*Self, allocator, tui.plane(), .{
.ctx = self,
.style = widget_type,
.on_render = handle_render_menu,
.on_scroll = EventHandler.bind(self, Self.handle_scroll),
.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 {
const padding = tui.get_widget_style(widget_type).padding;
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.menu.container_widget.resize(pos);
self.view_rows = pos.h;
self.view_cols = pos.w;
self.box = pos;
self.menu.container.resize(self.box);
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();
}
@ -107,7 +114,7 @@ pub fn add_item(self: *Self, entry_: Entry) !void {
const writer = label.writer();
cbor.writeValue(writer, idx) 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();
}
@ -160,8 +167,8 @@ fn handle_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), th
button.plane.home();
}
const entry = &self.entries.items[idx];
const pointer = if (selected) "" else " ";
_ = button.plane.print("{s} ", .{pointer}) catch {};
button.plane.set_style(style_label);
tui.render_pointer(&button.plane, selected);
var buf: [std.fs.max_path_bytes]u8 = undefined;
var removed_prefix: usize = 0;
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,
entry.end_line,
entry.end_pos + 1,
entry.pos_type,
},
} }) catch |e| self.logger.err("navigate", e);
}

View file

@ -30,7 +30,7 @@ const style = struct {
\\open_recent_project
\\find_in_files
\\open_command_palette
\\select_task
\\run_task
\\add_task
\\open_config
\\open_gui_config
@ -48,7 +48,7 @@ const style = struct {
\\open_recent_project
\\find_in_files
\\open_command_palette
\\select_task
\\run_task
\\add_task
\\open_config
\\open_keybind_config
@ -70,6 +70,8 @@ fire: ?Fire = null,
commands: Commands = undefined,
menu: *Menu.State(*Self),
menu_w: usize = 0,
menu_label_max: usize = 0,
menu_count: usize = 0,
menu_len: usize = 0,
max_desc_len: usize = 0,
input_namespace: []const u8,
@ -79,6 +81,8 @@ home_style_bufs: [][]const u8,
const Self = @This();
const widget_type: Widget.Type = .home;
pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
const logger = log.logger("home");
const self = try allocator.create(Self);
@ -95,7 +99,11 @@ pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
.allocator = allocator,
.parent = parent.plane.*,
.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(),
.home_style = home_style,
.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);
var it = std.mem.splitAny(u8, self.home_style.menu_commands, "\n ");
while (it.next()) |command_name| {
self.menu_len += 1;
const id = command.get_id(command_name) orelse {
logger.print("{s} is not defined", .{command_name});
continue;
@ -112,11 +119,14 @@ pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
logger.print("{s} has no description", .{command_name});
continue;
};
self.menu_count += 1;
var hints = std.mem.splitScalar(u8, keybind_mode.keybind_hints.get(command_name) orelse "", ',');
const hint = hints.first();
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);
}
const padding = tui.get_widget_style(widget_type).padding;
self.menu_len = self.menu_count + padding.top + padding.bottom;
self.position_menu(15, 9);
return w;
}
@ -145,7 +155,9 @@ fn add_menu_command(self: *Self, command_name: []const u8, description: []const
_ = try writer.write(leader);
try writer.print(" :{s}", .{hint});
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);
@ -228,8 +240,8 @@ fn menu_on_render(self: *Self, button: *Button.State(*Menu.State(*Self)), theme:
} else {
button.plane.set_style_bg_transparent(style_text);
}
const pointer = if (selected) "" else " ";
_ = button.plane.print("{s}{s}", .{ pointer, description }) catch {};
tui.render_pointer(&button.plane, selected);
_ = button.plane.print("{s}", .{description}) catch {};
if (button.active or button.hover or selected) {
button.plane.set_style(style_leader);
} 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;
}
const more = self.menu.render(theme);
const more = self.menu.container.render(theme);
return more or self.fire != null;
}
fn position_menu(self: *Self, y: usize, x: usize) void {
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 {
@ -388,6 +400,16 @@ const cmds = struct {
}
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 {
self.fire = if (self.fire) |*fire| ret: {
fire.deinit();

View file

@ -322,6 +322,7 @@ const cmds = struct {
if (!try ctx.args.match(.{tp.extract(&project_dir)}))
return;
try self.check_all_not_dirty();
try project_manager.open(project_dir);
for (self.editors.items) |editor| {
editor.clear_diagnostics();
try editor.close_file(.{});
@ -332,7 +333,6 @@ const cmds = struct {
try self.toggle_panel_view(filelist_view, false);
self.buffer_manager.deinit();
self.buffer_manager = Buffer.Manager.init(self.allocator);
try project_manager.open(project_dir);
const project = tp.env.get().str("project");
tui.rdr().set_terminal_working_directory(project);
if (self.top_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" });
@ -827,8 +827,11 @@ const cmds = struct {
tp.more,
})) return error.InvalidAddDiagnosticArgument;
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| {
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 = .{
.arguments = &.{
@ -938,6 +941,12 @@ const cmds = struct {
}
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 {
if (builtin.os.tag == .windows) {
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 {
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();
const stat = try file.stat();
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,
.lines = lines,
.severity = severity,
.pos_type = .byte,
}) catch |e| return tp.exit_error(e, @errorReturnTrace());
}

View file

@ -2,6 +2,7 @@ const std = @import("std");
const tp = @import("thespian");
const cbor = @import("cbor");
const log = @import("log");
const file_type_config = @import("file_type_config");
const root = @import("root");
const input = @import("input");
@ -20,6 +21,7 @@ pub fn Create(options: type) type {
return struct {
allocator: std.mem.Allocator,
file_path: std.ArrayList(u8),
rendered_mini_buffer: std.ArrayListUnmanaged(u8) = .empty,
query: std.ArrayList(u8),
match: std.ArrayList(u8),
entries: std.ArrayList(Entry),
@ -33,8 +35,12 @@ pub fn Create(options: type) type {
const Entry = struct {
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 } {
const self = try allocator.create(Self);
@ -66,6 +72,7 @@ pub fn Create(options: type) type {
self.match.deinit();
self.query.deinit();
self.file_path.deinit();
self.rendered_mini_buffer.deinit(self.allocator);
self.allocator.destroy(self);
}
@ -80,7 +87,11 @@ pub fn Create(options: type) type {
}
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();
}
@ -112,14 +123,11 @@ pub fn Create(options: type) type {
self.complete_trigger_count = 0;
self.file_path.clearRetainingCapacity();
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 {
try self.file_path.appendSlice(self.query.items);
}
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);
}
self.update_mini_mode_text();
return;
}
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 {
defer {
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);
}
}
defer self.update_mini_mode_text();
var count: usize = undefined;
if (try cbor.match(m.buf, .{ "PRJ", "path_entry", tp.more })) {
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 {
var path: []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) })) {
(try self.entries.addOne()).* = .{ .name = try self.allocator.dupe(u8, file_name), .type = .dir };
} else if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "LINK", tp.extract(&file_name) })) {
(try self.entries.addOne()).* = .{ .name = try self.allocator.dupe(u8, file_name), .type = .link };
} else if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "FILE", tp.extract(&file_name) })) {
(try self.entries.addOne()).* = .{ .name = try self.allocator.dupe(u8, file_name), .type = .file };
var file_type: []const u8 = undefined;
var icon: []const u8 = undefined;
var color: u24 = undefined;
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) })) {
try self.add_entry(file_name, .dir, file_type, icon, color);
} 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 {
log.logger("file_browser").err("receive", tp.unexpected(m));
}
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 {
self.complete_trigger_count = @min(self.complete_trigger_count, self.entries.items.len);
self.file_path.clearRetainingCapacity();
@ -179,9 +195,10 @@ pub fn Create(options: type) type {
if (self.total_matches == 1)
self.complete_trigger_count = 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 {
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.total_matches > 1)
@ -192,14 +209,14 @@ pub fn Create(options: type) type {
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;
const path = project_manager.normalize_file_path(path_);
try self.file_path.appendSlice(path);
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.appendSlice(entry.name);
if (entry.type == .dir)
try self.file_path.appendSlice(entry_name);
if (entry_type == .dir)
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)) {
matched += 1;
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;
}
last = entry;
@ -222,11 +239,11 @@ pub fn Create(options: type) type {
self.total_matches = matched;
if (found_match) |_| return;
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;
} else {
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 {
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);
const icon = if (self.entries.items.len > 0 and self.complete_trigger_count > 0)
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 EventHandler = @import("EventHandler");
const tui = @import("../../tui.zig");
const Allocator = @import("std").mem.Allocator;
const fmt = @import("std").fmt;
pub const Type = @import("numeric_input.zig").Create(@This());
pub const create = Type.create;
const Self = @This();
const name = "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 name(_: *Type) []const u8 {
return "goto";
}
pub fn deinit(self: *Self) void {
self.commands.deinit();
self.allocator.destroy(self);
pub fn start(_: *Type) usize {
const editor = tui.get_active_editor() orelse return 1;
return editor.get_primary().cursor.row + 1;
}
pub fn receive(self: *Self, _: tp.pid_ref, _: tp.message) error{Exit}!bool {
self.update_mini_mode_text();
return false;
}
pub const preview = goto;
pub const apply = goto;
pub const cancel = goto;
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 goto(self: *Self) void {
fn goto(self: *Type, _: command.Context) void {
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 name = " buffer";
pub const description = "buffer";
const dirty_indicator = "";
const hidden_indicator = "-";
pub const icon = "󰈞 ";
pub const Entry = struct {
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);
defer palette.allocator.free(buffers);
for (buffers) |buffer| {
const indicator = if (buffer.is_dirty())
dirty_indicator
else if (buffer.is_hidden())
hidden_indicator
else
"";
const indicator = tui.get_buffer_state_indicator(buffer);
(try palette.entries.addOne()).* = .{
.label = buffer.get_file_path(),
.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 {
const style_base = theme.editor_widget;
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;
return tui.render_file_item_cbor(&button.plane, button.opts.label, button.active, selected, button.hover, theme);
}
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 name = " file type";
pub const description = "file type";
pub const icon = "";
pub const Entry = struct {
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);
const pointer = if (selected) "" else " ";
_ = button.plane.print("{s}", .{pointer}) catch {};
tui.render_pointer(&button.plane, selected);
var iter = button.opts.label;
var description_: []const u8 = undefined;
var icon: []const u8 = undefined;
var icon_: []const u8 = undefined;
var color: u24 = undefined;
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 (tui.config().show_fileicons) {
tui.render_file_icon(&button.plane, icon, color);
_ = button.plane.print(" ", .{}) catch {};
}
const icon_width = tui.render_file_icon(&button.plane, icon_, color);
button.plane.set_style(style_label);
_ = 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;
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;
tui.render_match_cell(&button.plane, 0, index + 2 + icon_width, theme) catch break;
} else break;
}
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 {
var description_: []const u8 = undefined;
var icon: []const u8 = undefined;
var icon_: []const u8 = undefined;
var color: u24 = undefined;
var name_: []const u8 = undefined;
var iter = button.opts.label;
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.matchString(&iter, &name_) catch false)) return;
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 log = @import("log");
const cbor = @import("cbor");
const file_type_config = @import("file_type_config");
const root = @import("root");
const Plane = @import("renderer").Plane;
@ -22,6 +23,7 @@ const ModalBackground = @import("../../ModalBackground.zig");
const Self = @This();
const max_recent_files: usize = 25;
const widget_type: Widget.Type = .palette;
allocator: std.mem.Allocator,
f: usize = 0,
@ -32,7 +34,7 @@ logger: log.Logger,
query_pending: bool = false,
need_reset: bool = false,
need_select_first: bool = true,
longest: usize = 0,
longest: usize,
commands: Commands = undefined,
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 }),
.menu = try Menu.create(*Self, allocator, tui.plane(), .{
.ctx = self,
.style = widget_type,
.on_render = on_render_menu,
.on_resize = on_resize_menu,
.prepare_resize = prepare_resize_menu,
}),
.logger = log.logger(@typeName(Self)),
.inputbox = (try self.menu.add_header(try InputBox.create(*Self, self.allocator, self.menu.menu.parent, .{
.ctx = self,
.label = inputbox_label,
.padding = 2,
.icon = "󰈞 ",
}))).dynamic_cast(InputBox.State(*Self)) orelse unreachable,
.buffer_manager = tui.get_buffer_manager(),
.longest = inputbox_label.len,
};
try self.commands.init(self);
try tui.message_filters().add(MessageFilter.bind(self, receive_project_manager));
self.query_pending = true;
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.menu.container_widget);
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 {
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 {
@ -98,54 +104,23 @@ inline fn max_menu_width() usize {
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 {
const style_base = theme.editor_widget;
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 on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool {
return tui.render_file_item_cbor(&button.plane, button.opts.label, button.active, selected, button.hover, theme);
}
fn render_cell(plane: *Plane, y: usize, x: usize, style: Widget.Theme.Style) !void {
plane.cursor_move_yx(@intCast(y), @intCast(x)) catch return;
var cell = plane.cell_init();
_ = plane.at_cursor_cell(&cell) catch return;
cell.set_style(style);
_ = plane.putc(&cell) catch {};
fn prepare_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) Widget.Box {
return self.prepare_resize();
}
fn on_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) void {
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = self.menu_width() });
fn prepare_resize(self: *Self) Widget.Box {
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 {
@ -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);
}
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);
defer label.deinit();
const writer = label.writer();
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);
}
@ -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 {
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 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();
try self.add_item(file_name, matches);
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = self.menu_width() });
const indicator = if (self.buffer_manager) |bm| tui.get_file_state_indicator(bm, file_name) else "";
try self.add_item(file_name, file_icon, file_color, indicator, matches);
self.do_resize();
if (self.need_select_first) {
self.menu.select_down();
self.need_select_first = false;
}
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();
try self.add_item(file_name, null);
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = self.menu_width() });
const indicator = if (self.buffer_manager) |bm| tui.get_file_state_indicator(bm, file_name) else "";
try self.add_item(file_name, file_icon, file_color, indicator, null);
self.do_resize();
if (self.need_select_first) {
self.menu.select_down();
self.need_select_first = false;
@ -363,6 +370,14 @@ const cmds = struct {
}
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 {
return overlay_insert_bytes(self, ctx);
}

View file

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

View file

@ -20,6 +20,7 @@ const ModalBackground = @import("../../ModalBackground.zig");
pub const Menu = @import("../../Menu.zig");
const max_menu_width = 80;
const widget_type: Widget.Type = .palette;
pub fn Create(options: type) type {
return struct {
@ -46,6 +47,10 @@ pub fn Create(options: type) type {
pub const ButtonState = Button.State(*Menu.State(*Self));
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 self = try allocator.create(Self);
errdefer allocator.destroy(self);
@ -57,8 +62,10 @@ pub fn Create(options: type) type {
}),
.menu = try Menu.create(*Self, allocator, tui.plane(), .{
.ctx = self,
.style = widget_type,
.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_click4 = mouse_click_button4,
.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, .{
.ctx = self,
.label = options.label,
.padding = 2,
.icon = if (@hasDecl(options, "icon")) options.icon else null,
}))).dynamic_cast(InputBox.State(*Self)) orelse unreachable,
.view_rows = get_view_rows(tui.screen()),
.entries = std.ArrayList(Entry).init(allocator),
};
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"))
options.restore_state(self) catch {};
try self.commands.init(self);
@ -130,8 +142,7 @@ pub fn Create(options: type) type {
if (!(cbor.matchString(&iter, &hint) catch false))
hint = "";
button.plane.set_style(style_hint);
const pointer = if (selected) "" else " ";
_ = button.plane.print("{s}", .{pointer}) catch {};
tui.render_pointer(&button.plane, selected);
button.plane.set_style(style_label);
_ = button.plane.print("{s} ", .{label}) catch {};
button.plane.set_style(style_hint);
@ -140,25 +151,38 @@ pub fn Create(options: type) type {
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 + 1, theme) catch break;
tui.render_match_cell(&button.plane, 0, index + 2, theme) catch break;
} else break;
}
return false;
}
fn on_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) void {
self.do_resize();
// self.start_query(0) catch {};
fn prepare_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) Widget.Box {
return self.prepare_resize();
}
fn do_resize(self: *Self) void {
fn prepare_resize(self: *Self) Widget.Box {
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;
self.view_rows = get_view_rows(screen);
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.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 {
@ -239,7 +263,8 @@ pub fn Create(options: type) type {
var i = n;
while (i > 0) : (i -= 1)
self.menu.select_down();
self.do_resize();
const padding = tui.get_widget_style(widget_type).padding;
self.do_resize(padding);
tui.refresh_hover();
self.selection_updated();
}
@ -449,15 +474,22 @@ pub fn Create(options: type) type {
const button = self.menu.get_selected() orelse return;
const refresh = options.delete_item(self.menu, button);
if (refresh) {
if (@hasDecl(options, "load_entries")) {
options.clear_entries(self);
self.longest_hint = try options.load_entries(self);
if (self.entries.items.len > 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 {
self.menu.activate_selected();
@ -511,6 +543,15 @@ pub fn Create(options: type) type {
}
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 {
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) };
} else return error.InvalidTaskMessageField;
}
(try palette.entries.addOne()).* = .{
.label = try palette.allocator.dupe(u8, " Add new task"),
.command = "add_task",
};
(try palette.entries.addOne()).* = .{ .label = "", .command = "add_task" };
(try palette.entries.addOne()).* = .{ .label = "", .command = "palette_menu_delete_item" };
return if (palette.entries.items.len == 0) label.len else blk: {
var longest: usize = 0;
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;
}
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 iter = button.opts.label; // label contains cbor entry object and matches
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.fill(" ");
button.plane.home();
button.plane.set_style(style_hint);
const pointer = if (selected) "" else " ";
_ = button.plane.print("{s}", .{pointer}) catch {};
tui.render_pointer(&button.plane, selected);
button.plane.set_style(style_label);
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 len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) {
@ -103,17 +120,13 @@ fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
var entry: Entry = undefined;
var iter = button.opts.label;
if (!(cbor.matchValue(&iter, cbor.extract(&entry)) catch false)) return;
var buffer_name = std.ArrayList(u8).init(menu.*.opts.ctx.allocator);
defer buffer_name.deinit();
buffer_name.writer().print("*{s}*", .{entry.label}) catch {};
if (entry.command) |cmd| {
if (entry.command) |command_name| {
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 {
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", "create_scratch_buffer", .{ buffer_name.items, "", "conf" } }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
tp.self_pid().send(.{ "cmd", "shell_execute_stream", .{entry.label} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
project_manager.add_task(entry.label) catch {};
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 eol_mode = switch (self.eol_mode) {
.lf => "",
.crlf => " [␍␊]",
.crlf => " ␍␊",
};
const indent_mode = switch (self.indent_mode) {
.spaces, .auto => "",
.tabs => " [⭾]",
.tabs => " ",
};
std.fmt.format(writer, "{s}{s} Ln ", .{ eol_mode, indent_mode }) catch {};
self.format_count(writer, self.line + 1, self.padding orelse 0) catch {};

View file

@ -24,6 +24,7 @@ allocator: Allocator,
rdr_: renderer,
config_: @import("config"),
config_bufs: [][]const u8,
session_tab_width: ?usize = null,
highlight_columns_: []const u16,
highlight_columns_configured: []const u16,
frame_time: usize, // in microseconds
@ -103,6 +104,8 @@ const InitError = error{
keybind.LoadError;
fn init(allocator: Allocator) InitError!*Self {
log.stdout(.disable);
var conf, const conf_bufs = root.read_config(@import("config"), allocator);
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_clock = try tp.metronome.init(frame_time);
tp.env.get().set("lsp_verbose", conf.lsp_output == .verbose);
var self = try allocator.create(Self);
// don't destroy
// 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;
try self.rdr_.run();
log.stderr(.disable);
try project_manager.start();
try frame_clock.start();
@ -384,6 +391,9 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
return;
}
if (try m.match(.{"mouse_leave"}))
return;
if (try m.match(.{"focus_in"}))
return;
@ -408,6 +418,9 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
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
return;
@ -679,6 +692,17 @@ fn enter_overlay_mode(self: *Self, mode: type) command.Result {
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 {
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();
}
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;
defer if (old) |p| p.deinit();
self.theme_, self.parsed_theme = get_theme_by_name(self.allocator, name) orelse {
self.logger.print("theme not found: {s}", .{name});
return;
};
self.config_.theme = self.theme_.name;
self.set_terminal_style();
self.logger.print("theme: {s}", .{self.theme_.description});
switch (action) {
.none => {},
.store => {
self.config_.theme = self.theme_.name;
try save_config();
},
}
}
const cmds = struct {
@ -740,23 +769,69 @@ const cmds = struct {
}
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 {
var name: []const u8 = undefined;
if (!try ctx.args.match(.{tp.extract(&name)}))
if (try ctx.args.match(.{tp.extract(&name)}))
return self.set_theme_by_name(name, .store);
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);
return self.set_theme_by_name(name);
}
pub const set_theme_meta: Meta = .{ .arguments = &.{.string} };
pub fn theme_next(self: *Self, _: Ctx) Result {
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 fn theme_prev(self: *Self, _: Ctx) Result {
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" };
@ -858,8 +933,8 @@ const cmds = struct {
}
pub const open_recent_meta: Meta = .{ .description = "Open recent" };
pub fn open_recent_project(self: *Self, _: Ctx) Result {
return self.enter_overlay_mode(@import("mode/overlay/open_recent_project.zig").Type);
pub fn open_recent_project(_: *Self, _: Ctx) Result {
try project_manager.request_recent_projects();
}
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 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 {
var task: []const u8 = undefined;
if (try ctx.args.match(.{tp.extract(&task)}))
return call_add_task(task);
return enter_mini_mode(self, struct {
pub const Type = @import("mode/mini/buffer.zig").Create(@This());
pub const create = Type.create;
@ -881,17 +955,42 @@ const cmds = struct {
return @import("mode/overlay/task_palette.zig").name;
}
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 {};
command.executeName("exit_mini_mode", .{}) catch {};
}
}, ctx);
}
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();
};
command.executeName("exit_mini_mode", .{}) catch {};
command.executeName("select_task", .{}) catch {};
}
}, ctx);
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 add_task_meta: Meta = .{ .description = "Add task" };
}
pub const run_task_meta: Meta = .{
.description = "Run a task",
.arguments = &.{.string},
};
pub fn delete_task(_: *Self, ctx: Ctx) Result {
var task: []const u8 = undefined;
@ -964,10 +1063,10 @@ const cmds = struct {
fn enter_mini_mode(self: *Self, comptime mode: anytype, ctx: Ctx) !void {
command.executeName("disable_fast_scroll", .{}) 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.input_mode_outer_) |_| try exit_overlay_mode(self, .{});
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_ = input_mode_;
self.mini_mode_ = mini_mode_;
@ -1113,6 +1212,11 @@ pub fn config() *const @import("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 {
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 {};
}
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();
_ = self.at_cursor_cell(&cell) catch return;
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.putc(&cell) 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 {
@ -1394,6 +1512,74 @@ pub fn render_match_cell(self: *renderer.Plane, y: usize, x: usize, theme_: *con
_ = 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 {
const theme_name = self.theme_.name;
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);
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,
};
}