Merge branch 'master' into zig-0.15.0

This commit is contained in:
CJ van den Berg 2025-07-14 19:11:53 +02:00
commit 4e80bae8b8
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
24 changed files with 701 additions and 262 deletions

View file

@ -230,6 +230,7 @@ pub fn restore_state_v0(self: *Self, data: []const u8) error{
InvalidPIntType,
JsonIncompatibleType,
NotAnObject,
BadArrayAllocExtract,
}!void {
tp.trace(tp.channel.debug, .{"restore_state_v0"});
defer self.sort_files_by_mtime();

View file

@ -23,10 +23,51 @@ pub const RGB = struct {
return .{ .r = v[0], .g = v[1], .b = v[2] };
}
pub fn from_string(s: []const u8) ?RGB {
const nib = struct {
fn f(c: u8) ?u8 {
return switch (c) {
'0'...'9' => c - '0',
'A'...'F' => c - 'A' + 10,
'a'...'f' => c - 'a' + 10,
else => null,
};
}
}.f;
if (s.len != 7) return null;
if (s[0] != '#') return null;
const r = (nib(s[1]) orelse return null) << 4 | (nib(s[2]) orelse return null);
const g = (nib(s[3]) orelse return null) << 4 | (nib(s[4]) orelse return null);
const b = (nib(s[5]) orelse return null) << 4 | (nib(s[6]) orelse return null);
return .{ .r = r, .g = g, .b = b };
}
pub fn to_u8s(v: RGB) [3]u8 {
return [_]u8{ v.r, v.g, v.b };
}
pub fn to_string(v: RGB, s: *[7]u8) []u8 {
const nib = struct {
fn f(n: u8) u8 {
return switch (n) {
0...9 => '0' + n,
0xA...0xF => 'A' + n - 10,
else => unreachable,
};
}
}.f;
s[0] = '#';
s[1] = nib(v.r >> 4);
s[2] = nib(v.r & 0b00001111);
s[3] = nib(v.g >> 4);
s[4] = nib(v.g & 0b00001111);
s[5] = nib(v.b >> 4);
s[6] = nib(v.b & 0b00001111);
return s;
}
pub fn contrast(a_: RGB, b_: RGB) f32 {
const a = RGBf.from_RGB(a_).luminance();
const b = RGBf.from_RGB(b_).luminance();

202
src/file_type_config.zig Normal file
View file

@ -0,0 +1,202 @@
name: []const u8 = default.name,
description: ?[]const u8 = null,
extensions: ?[]const []const u8 = null,
icon: ?[]const u8 = null,
color: ?u24 = null,
comment: ?[]const u8 = null,
parser: ?[]const u8 = null,
formatter: ?[]const []const u8 = null,
language_server: ?[]const []const u8 = null,
first_line_matches_prefix: ?[]const u8 = null,
first_line_matches_content: ?[]const u8 = null,
first_line_matches: ?[]const u8 = null,
include_files: []const u8 = "",
pub const default = struct {
pub const name = "text";
pub const description = "Plain Text";
pub const icon = "🖹";
pub const color = 0x000000;
};
fn from_file_type(file_type: syntax.FileType) @This() {
return .{
.name = file_type.name,
.color = file_type.color,
.icon = file_type.icon,
.description = file_type.description,
.extensions = file_type.extensions,
.first_line_matches_prefix = if (file_type.first_line_matches) |flm| flm.prefix else null,
.first_line_matches_content = if (file_type.first_line_matches) |flm| flm.content else null,
.parser = file_type.name,
.comment = file_type.comment,
.formatter = file_type.formatter,
.language_server = file_type.language_server,
};
}
pub fn get_default(allocator: std.mem.Allocator, file_type_name: []const u8) ![]const u8 {
const file_type = syntax.FileType.get_by_name_static(file_type_name) orelse return error.UnknownFileType;
const config = from_file_type(file_type);
var content = std.ArrayListUnmanaged(u8).empty;
defer content.deinit(allocator);
root.write_config_to_writer(@This(), config, content.writer(allocator)) catch {};
return content.toOwnedSlice(allocator);
}
pub fn get_all_names() []const []const u8 {
cache_mutex.lock();
defer cache_mutex.unlock();
if (cache_list.len == 0)
cache_list = load_all(cache_allocator) catch &.{};
return cache_list;
}
const cache_allocator = std.heap.c_allocator;
var cache_mutex: std.Thread.Mutex = .{};
var cache: CacheType = .empty;
const CacheType = std.StringHashMapUnmanaged(?@This());
var cache_list: []const []const u8 = &.{};
pub fn get(file_type_name: []const u8) !?@This() {
cache_mutex.lock();
defer cache_mutex.unlock();
return if (cache.get(file_type_name)) |self| self else self: {
const file_type = file_type: {
const file_name = try get_config_file_path(cache_allocator, file_type_name);
defer cache_allocator.free(file_name);
const file: ?std.fs.File = std.fs.openFileAbsolute(file_name, .{ .mode = .read_only }) catch null;
if (file) |f| {
defer f.close();
const stat = try f.stat();
const buf = try cache_allocator.alloc(u8, @intCast(stat.size));
defer cache_allocator.free(buf);
const size = try f.readAll(buf);
std.debug.assert(size == stat.size);
var self: @This() = .{};
var bufs_: [][]const u8 = &.{}; // cached, no need to free
try root.parse_text_config_file(@This(), cache_allocator, &self, &bufs_, file_name, buf);
break :file_type self;
} else {
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);
break :self file_type;
};
}
pub fn get_config_file_path(allocator: std.mem.Allocator, file_type: []const u8) ![]u8 {
var stream = std.ArrayList(u8).fromOwnedSlice(allocator, try get_config_dir_path(allocator));
const writer = stream.writer();
_ = try writer.writeAll(file_type);
_ = try writer.writeAll(".conf");
return stream.toOwnedSlice();
}
fn get_config_dir_path(allocator: std.mem.Allocator) ![]u8 {
var stream = std.ArrayList(u8).init(allocator);
const writer = stream.writer();
_ = try writer.writeAll(try root.get_config_dir());
_ = try writer.writeByte(std.fs.path.sep);
_ = try writer.writeAll("file_type");
_ = try writer.writeByte(std.fs.path.sep);
std.fs.makeDirAbsolute(stream.items) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
};
return stream.toOwnedSlice();
}
const extension = ".conf";
fn load_all(allocator: std.mem.Allocator) ![]const []const u8 {
const dir_path = try get_config_dir_path(allocator);
defer allocator.free(dir_path);
var dir = try std.fs.cwd().openDir(dir_path, .{ .iterate = true });
defer dir.close();
var names: std.StringHashMapUnmanaged(void) = .empty;
defer names.deinit(allocator);
var iter = dir.iterate();
while (try iter.next()) |entry| {
if (entry.kind != .file) continue;
if (!std.mem.endsWith(u8, entry.name, extension)) continue;
const file_type_name = entry.name[0 .. entry.name.len - extension.len];
if (!names.contains(file_type_name))
try names.put(allocator, try allocator.dupe(u8, file_type_name), {});
}
for (syntax.FileType.get_all()) |file_type| {
if (!names.contains(file_type.name))
try names.put(allocator, try allocator.dupe(u8, file_type.name), {});
}
var list: std.ArrayListUnmanaged([]const u8) = .empty;
defer list.deinit(allocator);
var names_iter = names.keyIterator();
while (names_iter.next()) |key| {
(try list.addOne(allocator)).* = key.*;
}
const less_fn = struct {
fn less_fn(_: void, lhs: []const u8, rhs: []const u8) bool {
return std.mem.order(u8, lhs, rhs) == .lt;
}
}.less_fn;
std.mem.sort([]const u8, list.items, {}, less_fn);
return list.toOwnedSlice(allocator);
}
pub fn guess_file_type(file_path: ?[]const u8, content: []const u8) ?@This() {
return guess(file_path, content);
}
fn guess(file_path: ?[]const u8, content: []const u8) ?@This() {
if (guess_first_line(content)) |ft| return ft;
for (get_all_names()) |file_type_name| {
const file_type = get(file_type_name) catch unreachable orelse unreachable;
if (file_path) |fp| if (syntax.FileType.match_file_type(file_type.extensions orelse continue, fp))
return file_type;
}
return null;
}
fn guess_first_line(content: []const u8) ?@This() {
const first_line = if (std.mem.indexOf(u8, content, "\n")) |pos| content[0..pos] else content;
for (get_all_names()) |file_type_name| {
const file_type = get(file_type_name) catch unreachable orelse unreachable;
if (syntax.FileType.match_first_line(file_type.first_line_matches_prefix, file_type.first_line_matches_content, first_line))
return file_type;
}
return null;
}
pub fn create_syntax(file_type_config: @This(), allocator: std.mem.Allocator, query_cache: *syntax.QueryCache) !?*syntax {
return syntax.create(
syntax.FileType.get_by_name_static(file_type_config.parser orelse file_type_config.name) orelse return null,
allocator,
query_cache,
);
}
pub fn create_syntax_guess_file_type(
allocator: std.mem.Allocator,
content: []const u8,
file_path: ?[]const u8,
query_cache: *syntax.QueryCache,
) !?*syntax {
const file_type = guess(file_path, content) orelse return error.NotFound;
return create_syntax(file_type, allocator, query_cache);
}
const syntax = @import("syntax");
const std = @import("std");
const root = @import("root");

View file

@ -249,6 +249,7 @@
"deinit_command": ["resume_undo_history"],
"press": [
["<Esc>", ["move_left_vim"], ["enter_mode", "normal"]],
["<C-c>", ["move_left_vim"], ["enter_mode", "normal"]],
["<Del>", "delete_forward"],
["<BS>", "delete_backward"],
["<CR>", "smart_insert_line"],

View file

@ -1,5 +1,5 @@
const std = @import("std");
const syntax = @import("syntax");
const file_type_config = @import("file_type_config");
const builtin = @import("builtin");
const RGB = @import("color").RGB;
@ -16,7 +16,8 @@ pub fn list(allocator: std.mem.Allocator, writer: anytype, tty_config: std.io.tt
var max_formatter_len: usize = 0;
var max_extensions_len: usize = 0;
for (syntax.FileType.file_types) |file_type| {
for (file_type_config.get_all_names()) |file_type_name| {
const file_type = try file_type_config.get(file_type_name) orelse unreachable;
max_language_len = @max(max_language_len, file_type.name.len);
max_langserver_len = @max(max_langserver_len, args_string_length(file_type.language_server));
max_formatter_len = @max(max_formatter_len, args_string_length(file_type.formatter));
@ -31,10 +32,11 @@ pub fn list(allocator: std.mem.Allocator, writer: anytype, tty_config: std.io.tt
try tty_config.setColor(writer, .reset);
try writer.writeAll("\n");
for (syntax.FileType.file_types) |file_type| {
for (file_type_config.get_all_names()) |file_type_name| {
const file_type = try file_type_config.get(file_type_name) orelse unreachable;
try writer.writeAll(" ");
try setColorRgb(writer, file_type.color);
try writer.writeAll(file_type.icon);
try setColorRgb(writer, file_type.color orelse file_type_config.default.color);
try writer.writeAll(file_type.icon orelse file_type_config.default.icon);
try tty_config.setColor(writer, .reset);
try writer.writeAll(" ");
try write_string(writer, file_type.name, max_language_len + 1);

View file

@ -2,6 +2,7 @@ const std = @import("std");
const tui = @import("tui");
const cbor = @import("cbor");
const thespian = @import("thespian");
const color = @import("color");
const flags = @import("flags");
const builtin = @import("builtin");
const bin_path = @import("bin_path");
@ -148,11 +149,10 @@ pub fn main() anyerror!void {
return list_languages.list(a, stdout.writer(), tty_config);
}
if (builtin.os.tag != .windows)
if (std.posix.getenv("JITDEBUG")) |_|
thespian.install_debugger()
else if (@hasDecl(renderer, "install_crash_handler"))
renderer.install_crash_handler();
if (builtin.os.tag != .windows and @hasDecl(renderer, "install_crash_handler")) {
if (std.posix.getenv("JITDEBUG")) |_| renderer.jit_debugger_enabled = true;
renderer.install_crash_handler();
}
if (args.debug_wait) {
std.debug.print("press return to start", .{});
@ -445,13 +445,24 @@ pub fn exists_config(T: type) bool {
return true;
}
fn get_default(T: type) T {
return switch (@typeInfo(T)) {
.array => &.{},
.pointer => |info| switch (info.size) {
.slice => &.{},
else => @compileError("unsupported config type " ++ @typeName(T)),
},
else => .{},
};
}
pub fn read_config(T: type, allocator: std.mem.Allocator) struct { T, [][]const u8 } {
config_mutex.lock();
defer config_mutex.unlock();
var bufs: [][]const u8 = &[_][]const u8{};
const json_file_name = get_app_config_file_name(application_name, @typeName(T)) catch return .{ .{}, bufs };
const json_file_name = get_app_config_file_name(application_name, @typeName(T)) catch return .{ get_default(T), bufs };
const text_file_name = json_file_name[0 .. json_file_name.len - ".json".len];
var conf: T = .{};
var conf: T = get_default(T);
if (!read_config_file(T, allocator, &conf, &bufs, text_file_name)) {
_ = read_config_file(T, allocator, &conf, &bufs, json_file_name);
}
@ -476,12 +487,16 @@ fn read_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs: *[][]
fn read_text_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs_: *[][]const u8, file_name: []const u8) !void {
var file = try std.fs.openFileAbsolute(file_name, .{ .mode = .read_only });
defer file.close();
const text = try file.readToEndAlloc(allocator, 64 * 1024);
defer allocator.free(text);
const content = try file.readToEndAlloc(allocator, 64 * 1024);
defer allocator.free(content);
return parse_text_config_file(T, allocator, conf, bufs_, file_name, content);
}
pub fn parse_text_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs_: *[][]const u8, file_name: []const u8, content: []const u8) !void {
var cbor_buf = std.ArrayList(u8).init(allocator);
defer cbor_buf.deinit();
const writer = cbor_buf.writer();
var it = std.mem.splitScalar(u8, text, '\n');
var it = std.mem.splitScalar(u8, content, '\n');
var lineno: u32 = 0;
while (it.next()) |line| {
lineno += 1;
@ -505,7 +520,7 @@ fn read_text_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs_:
var bufs = std.ArrayListUnmanaged([]const u8).fromOwnedSlice(bufs_.*);
bufs.append(allocator, cb) catch @panic("OOM:read_text_config_file");
bufs_.* = bufs.toOwnedSlice(allocator) catch @panic("OOM:read_text_config_file");
return read_cbor_config(T, conf, file_name, cb);
return read_cbor_config(T, allocator, conf, file_name, cb);
}
fn read_json_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs_: *[][]const u8, file_name: []const u8) !void {
@ -520,11 +535,12 @@ fn read_json_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs_:
const cb = try cbor.fromJson(json, cbor_buf);
var iter = cb;
_ = try cbor.decodeMapHeader(&iter);
return read_cbor_config(T, conf, file_name, iter);
return read_cbor_config(T, allocator, conf, file_name, iter);
}
fn read_cbor_config(
T: type,
allocator: std.mem.Allocator,
conf: *T,
file_name: []const u8,
cb: []const u8,
@ -554,12 +570,29 @@ fn read_cbor_config(
}
} else if (std.mem.eql(u8, field_name, field_info.name)) {
known = true;
var value: field_info.type = undefined;
if (try cbor.matchValue(&iter, cbor.extract(&value))) {
@field(conf, field_info.name) = value;
} else {
try cbor.skipValue(&iter);
std.log.err("invalid value for key '{s}'", .{field_name});
switch (field_info.type) {
u24, ?u24 => {
var value: []const u8 = undefined;
if (try cbor.matchValue(&iter, cbor.extract(&value))) {
const color_ = color.RGB.from_string(value);
if (color_) |color__|
@field(conf, field_info.name) = color__.to_u24()
else
std.log.err("invalid value for key '{s}'", .{field_name});
} else {
try cbor.skipValue(&iter);
std.log.err("invalid value for key '{s}'", .{field_name});
}
},
else => {
var value: field_info.type = undefined;
if (try cbor.matchValue(&iter, cbor.extractAlloc(&value, allocator))) {
@field(conf, field_info.name) = value;
} else {
try cbor.skipValue(&iter);
std.log.err("invalid value for key '{s}'", .{field_name});
}
},
}
};
if (!known) {
@ -613,15 +646,36 @@ pub fn write_config_to_writer(comptime T: type, data: T, writer: anytype) @TypeO
} else {
try writer.print("{s} ", .{field_info.name});
}
var s = std.json.writeStream(writer, .{ .whitespace = .indent_4 });
try s.write(@field(data, field_info.name));
switch (field_info.type) {
u24 => try write_color_value(@field(data, field_info.name), writer),
?u24 => if (@field(data, field_info.name)) |value|
try write_color_value(value, writer)
else
try writer.writeAll("null"),
else => {
var s = std.json.writeStream(writer, .{ .whitespace = .minified });
try s.write(@field(data, field_info.name));
},
}
try writer.print("\n", .{});
}
}
fn write_color_value(value: u24, writer: anytype) @TypeOf(writer).Error!void {
var hex: [7]u8 = undefined;
try writer.writeByte('"');
try writer.writeAll(color.RGB.to_string(color.RGB.from_u24(value), &hex));
try writer.writeByte('"');
}
fn config_eql(comptime T: type, a: T, b: T) bool {
switch (T) {
[]const u8 => return std.mem.eql(u8, a, b),
[]const []const u8 => {
if (a.len != b.len) return false;
for (a, 0..) |x, i| if (!config_eql([]const u8, x, b[i])) return false;
return true;
},
else => {},
}
switch (@typeInfo(T)) {
@ -707,7 +761,7 @@ pub fn list_themes(allocator: std.mem.Allocator) ![]const []const u8 {
return result.toOwnedSlice();
}
pub fn get_config_dir() ![]const u8 {
pub fn get_config_dir() ConfigDirError![]const u8 {
return get_app_config_dir(application_name);
}

View file

@ -3,7 +3,7 @@ const tp = @import("thespian");
const cbor = @import("cbor");
const log = @import("log");
const tracy = @import("tracy");
const FileType = @import("syntax").FileType;
const file_type_config = @import("file_type_config");
const root = @import("root");
const Buffer = @import("Buffer");
const builtin = @import("builtin");
@ -137,7 +137,7 @@ pub fn delete_task(task: []const u8) (ProjectManagerError || ProjectError)!void
return send(.{ "delete_task", project, task });
}
pub fn did_open(file_path: []const u8, file_type: *const FileType, version: usize, text: []const u8, ephemeral: bool) (ProjectManagerError || ProjectError)!void {
pub fn did_open(file_path: []const u8, file_type: file_type_config, version: usize, text: []const u8, ephemeral: bool) (ProjectManagerError || ProjectError)!void {
if (ephemeral) return;
const project = tp.env.get().str("project");
if (project.len == 0)

View file

@ -11,10 +11,12 @@ const RGB = @import("color").RGB;
const Plane = @This();
const name_buf_len = 128;
window: vaxis.Window,
row: i32 = 0,
col: i32 = 0,
name_buf: [128]u8,
name_buf: [name_buf_len]u8,
name_len: usize,
cache: GraphemeCache = .{},
style: vaxis.Cell.Style = .{},
@ -27,7 +29,7 @@ pub const Options = struct {
x: usize = 0,
rows: usize = 0,
cols: usize = 0,
name: [*:0]const u8,
name: []const u8,
flags: option = .none,
};
@ -44,13 +46,14 @@ pub fn init(nopts: *const Options, parent_: Plane) !Plane {
.height = @as(u16, @intCast(nopts.rows)),
.border = .{},
};
const len = @min(nopts.name.len, name_buf_len);
var plane: Plane = .{
.window = parent_.window.child(opts),
.name_buf = undefined,
.name_len = std.mem.span(nopts.name).len,
.name_len = len,
.scrolling = nopts.flags == .VSCROLL,
};
@memcpy(plane.name_buf[0..plane.name_len], nopts.name);
@memcpy(plane.name_buf[0..len], nopts.name[0..len]);
return plane;
}

View file

@ -57,6 +57,7 @@ pub const Error = error{
InvalidPIntType,
JsonIncompatibleType,
NotAnObject,
BadArrayAllocExtract,
} || std.Thread.SpawnError;
pub fn init(allocator: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: bool, _: *const fn (ctx: *anyopaque) void) Error!Self {
@ -135,15 +136,46 @@ pub fn install_crash_handler() void {
std.posix.sigaction(std.posix.SIG.ILL, &act, null);
}
pub var jit_debugger_enabled: bool = false;
fn handle_crash(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn {
const debug = @import("std/debug.zig");
debug.lockStdErr();
if (panic_in_progress())
std.posix.abort();
in_panic.store(true, .release);
const cleanup = panic_cleanup;
panic_cleanup = null;
if (cleanup) |self| {
self.vx.deinit(self.allocator, self.tty.anyWriter());
self.tty.deinit();
}
@import("std/debug.zig").handleSegfaultPosix(sig, info, ctx_ptr);
if (builtin.os.tag == .linux and jit_debugger_enabled) {
handleSegfaultPosixNoAbort(sig, info, ctx_ptr);
@import("thespian").sighdl_debugger(sig, @ptrCast(@constCast(info)), ctx_ptr);
std.posix.abort();
} else {
debug.handleSegfaultPosix(sig, info, ctx_ptr);
}
unreachable;
}
fn handleSegfaultPosixNoAbort(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) void {
const debug = @import("std/debug.zig");
debug.resetSegfaultHandler();
const addr = switch (builtin.os.tag) {
.linux => @intFromPtr(info.fields.sigfault.addr),
.freebsd, .macos => @intFromPtr(info.addr),
.netbsd => @intFromPtr(info.info.reason.fault.addr),
.openbsd => @intFromPtr(info.data.fault.addr),
.solaris, .illumos => @intFromPtr(info.reason.fault.addr),
else => unreachable,
};
const code = if (builtin.os.tag == .netbsd) info.info.code else info.code;
debug.dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr);
}
pub fn run(self: *Self) Error!void {

View file

@ -1384,7 +1384,7 @@ pub fn attachSegfaultHandler() void {
updateSegfaultHandler(&act);
}
fn resetSegfaultHandler() void {
pub fn resetSegfaultHandler() void {
if (native_os == .windows) {
if (windows_segfault_handle) |handle| {
assert(windows.kernel32.RemoveVectoredExceptionHandler(handle) != 0);
@ -1442,7 +1442,7 @@ pub fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*an
posix.abort();
}
fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void {
pub fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void {
const stderr = io.getStdErr().writer();
_ = switch (sig) {
posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL

View file

@ -35,6 +35,7 @@ pub const Error = error{
InvalidPIntType,
JsonIncompatibleType,
NotAnObject,
BadArrayAllocExtract,
} || std.Thread.SpawnError;
pub const panic = messageBoxThenPanic(.{ .title = "Flow Panic" });

View file

@ -26,6 +26,7 @@ pub const Error = error{
InvalidPIntType,
JsonIncompatibleType,
NotAnObject,
BadArrayAllocExtract,
};
pub const OutputHandler = fn (context: usize, parent: tp.pid_ref, arg0: []const u8, output: []const u8) void;

View file

@ -24,7 +24,8 @@ const CacheEntry = struct {
query: ?*Query,
query_arena: ?*std.heap.ArenaAllocator,
query_type: QueryType,
file_type: *const FileType,
file_type_name: []const u8,
lang_fn: FileType.LangFn,
fn destroy(self: *@This(), allocator: std.mem.Allocator) void {
if (self.query_arena) |a| {
@ -101,7 +102,7 @@ fn release_cache_entry_hash_map(allocator: std.mem.Allocator, hash_map: *std.Str
hash_map.deinit(allocator);
}
fn get_cache_entry(self: *Self, file_type: *const FileType, comptime query_type: QueryType) CacheError!*CacheEntry {
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();
@ -119,7 +120,8 @@ fn get_cache_entry(self: *Self, file_type: *const FileType, comptime query_type:
.query = null,
.query_arena = null,
.mutex = if (self.mutex) |_| .{} else null,
.file_type = file_type,
.lang_fn = file_type.lang_fn,
.file_type_name = file_type.name,
.query_type = query_type,
};
entry_.value_ptr.* = q;
@ -133,8 +135,8 @@ fn get_cached_query(self: *Self, entry: *CacheEntry) Error!?*Query {
defer if (entry.mutex) |*mtx| mtx.unlock();
return if (entry.query) |query| query else blk: {
const lang = entry.file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{entry.file_type.name});
const queries = FileType.queries.get(entry.file_type.name) orelse return null;
const 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,
@ -166,7 +168,7 @@ fn ReturnType(comptime query_type: QueryType) type {
};
}
pub fn get(self: *Self, file_type: *const FileType, comptime query_type: QueryType) Error!ReturnType(query_type) {
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))) {

View file

@ -20,43 +20,45 @@ comment: []const u8,
formatter: ?[]const []const u8,
language_server: ?[]const []const u8,
pub fn get_by_name(name: []const u8) ?*const FileType {
for (file_types) |*file_type|
if (std.mem.eql(u8, file_type.name, name))
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;
}
pub fn guess(file_path: ?[]const u8, content: []const u8) ?*const FileType {
if (guess_first_line(content)) |ft| return ft;
for (file_types) |*file_type|
if (file_path) |fp| if (match_file_type(file_type, fp))
return file_type;
return null;
}
fn guess_first_line(content: []const u8) ?*const FileType {
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 (file_types) |*file_type|
for (static_file_types) |*file_type|
if (file_type.first_line_matches) |match|
if (match_first_line(match, first_line))
if (match_first_line(match.prefix, match.content, first_line))
return file_type;
return null;
}
fn match_first_line(match: FirstLineMatch, first_line: []const u8) bool {
if (match.prefix) |prefix|
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 (match_content) |content|
if (std.mem.indexOf(u8, first_line, content)) |_| {} else return false;
return true;
}
fn match_file_type(file_type: *const FileType, file_path: []const u8) bool {
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 (file_type.extensions) |ext| {
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..]))
@ -85,14 +87,15 @@ fn ft_func_name(comptime lang: []const u8) []const u8 {
return &func_name;
}
const LangFn = *const fn () callconv(.C) ?*const treez.Language;
pub const LangFn = *const fn () callconv(.C) ?*const treez.Language;
pub const FirstLineMatch = struct {
prefix: ?[]const u8 = null,
content: ?[]const u8 = null,
};
pub const file_types = load_file_types(@import("file_types.zig"));
const static_file_type_list = load_file_types(@import("file_types.zig"));
const static_file_types = std.static_string_map.StaticStringMap(FileType).initComptime(static_file_type_list);
fn vec(comptime args: anytype) []const []const u8 {
var cmd: []const []const u8 = &[_][]const u8{};
@ -102,7 +105,9 @@ fn vec(comptime args: anytype) []const []const u8 {
return cmd;
}
fn load_file_types(comptime Namespace: type) []const FileType {
const ListEntry = struct { []const u8, FileType };
fn load_file_types(comptime Namespace: type) []const ListEntry {
comptime switch (@typeInfo(Namespace)) {
.@"struct" => |info| {
var count = 0;
@ -110,12 +115,12 @@ fn load_file_types(comptime Namespace: type) []const FileType {
// @compileLog(decl.name, @TypeOf(@field(Namespace, decl.name)));
count += 1;
}
var construct_types: [count]FileType = undefined;
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] = .{
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,
@ -126,7 +131,7 @@ fn load_file_types(comptime Namespace: type) []const FileType {
.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;

View file

@ -21,14 +21,13 @@ pub const Node = treez.Node;
allocator: std.mem.Allocator,
lang: *const Language,
file_type: *const FileType,
parser: *Parser,
query: *Query,
errors_query: *Query,
injections: ?*Query,
tree: ?*treez.Tree = null,
pub fn create(file_type: *const FileType, allocator: std.mem.Allocator, query_cache: *QueryCache) !*Self {
pub fn create(file_type: FileType, allocator: std.mem.Allocator, query_cache: *QueryCache) !*Self {
const query = try query_cache.get(file_type, .highlights);
const errors_query = try query_cache.get(file_type, .errors);
const injections = try query_cache.get(file_type, .injections);
@ -36,7 +35,6 @@ pub fn create(file_type: *const FileType, allocator: std.mem.Allocator, query_ca
self.* = .{
.allocator = allocator,
.lang = file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{file_type.name}),
.file_type = file_type,
.parser = try Parser.create(),
.query = query,
.errors_query = errors_query,
@ -47,13 +45,13 @@ pub fn create(file_type: *const FileType, allocator: std.mem.Allocator, query_ca
return self;
}
pub fn create_file_type(allocator: std.mem.Allocator, lang_name: []const u8, query_cache: *QueryCache) !*Self {
const file_type = FileType.get_by_name(lang_name) orelse return error.NotFound;
pub fn static_create_file_type(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(allocator: std.mem.Allocator, content: []const u8, file_path: ?[]const u8, query_cache: *QueryCache) !*Self {
const file_type = FileType.guess(file_path, content) orelse return error.NotFound;
pub fn static_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);
}

View file

@ -280,6 +280,7 @@ pub const DeserializeError = error{
JsonIncompatibleType,
InvalidQueryCbor,
NotAnObject,
BadArrayAllocExtract,
};
pub fn fromCbor(cb: []const u8, allocator: std.mem.Allocator) DeserializeError!struct { *TSQuery, *std.heap.ArenaAllocator } {

View file

@ -8,6 +8,7 @@ const ripgrep = @import("ripgrep");
const tracy = @import("tracy");
const text_manip = @import("text_manip");
const syntax = @import("syntax");
const file_type_config = @import("file_type_config");
const project_manager = @import("project_manager");
const root_mod = @import("root");
@ -330,6 +331,7 @@ pub const Editor = struct {
utf8_sanitized: bool = false,
} = .{},
file_type: ?file_type_config = null,
syntax: ?*syntax = null,
syntax_no_render: bool = false,
syntax_report_timing: bool = false,
@ -579,30 +581,30 @@ pub const Editor = struct {
try new_buf.root.store(content.writer(std.heap.c_allocator), new_buf.file_eol_mode);
}
const syn_file_type = blk: {
self.file_type = blk: {
const frame_ = tracy.initZone(@src(), .{ .name = "guess" });
defer frame_.deinit();
break :blk if (lang_override.len > 0)
syntax.FileType.get_by_name(lang_override)
try file_type_config.get(lang_override)
else
syntax.FileType.guess(self.file_path, content.items);
file_type_config.guess_file_type(self.file_path, content.items);
};
const syn = blk: {
const frame_ = tracy.initZone(@src(), .{ .name = "create" });
defer frame_.deinit();
break :blk if (syn_file_type) |ft|
syntax.create(ft, self.allocator, tui.query_cache()) catch null
break :blk if (self.file_type) |ft|
ft.create_syntax(self.allocator, tui.query_cache()) catch null
else
null;
};
if (syn) |syn_| {
if (self.file_type) |ft| {
const frame_ = tracy.initZone(@src(), .{ .name = "did_open" });
defer frame_.deinit();
project_manager.did_open(
file_path,
syn_.file_type,
ft,
self.lsp_version,
try content.toOwnedSlice(std.heap.c_allocator),
new_buf.is_ephemeral(),
@ -614,9 +616,9 @@ pub const Editor = struct {
self.syntax_no_render = tp.env.get().is("no-syntax");
self.syntax_report_timing = tp.env.get().is("syntax-report-timing");
const ftn = if (self.syntax) |syn| syn.file_type.name else "text";
const fti = if (self.syntax) |syn| syn.file_type.icon else "🖹";
const ftc = if (self.syntax) |syn| syn.file_type.color else 0x000000;
const ftn = if (self.file_type) |ft| ft.name else file_type_config.default.name;
const fti = if (self.file_type) |ft| ft.icon orelse file_type_config.default.icon else file_type_config.default.icon;
const ftc = if (self.file_type) |ft| ft.color orelse file_type_config.default.color else file_type_config.default.color;
if (self.buffer) |buffer| {
buffer.file_type_name = ftn;
buffer.file_type_icon = fti;
@ -2946,30 +2948,46 @@ pub const Editor = struct {
for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| {
if (cursel.selection) |_| {
// just delete selection
root = self.delete_selection(root, cursel, b.allocator) catch continue;
all_stop = false;
continue;
}
// detect indentation
const first = find_first_non_ws(root, cursel.cursor.row, self.metrics);
// select char to the left
with_selection_const(root, move_cursor_left, cursel, self.metrics) catch continue;
// if we don't have a selection after move_cursor_left there is nothing to delete
if (cursel.selection) |*sel| {
const egc_left, _, _ = sel.end.egc_at(root, self.metrics) catch {
root = self.delete_selection(root, cursel, b.allocator) catch continue;
all_stop = false;
continue;
};
const egc_right, _, _ = sel.begin.egc_at(root, self.metrics) catch {
root = self.delete_selection(root, cursel, b.allocator) catch continue;
all_stop = false;
continue;
};
if (first > sel.end.col) {
// we are inside leading whitespace
// select to next indentation boundary
while (sel.end.col > 0 and sel.end.col % self.indent_size != 0)
with_selection_const(root, move_cursor_left, cursel, self.metrics) catch break;
} else {
// char being deleted
const egc_left, _, _ = sel.end.egc_at(root, self.metrics) catch {
root = self.delete_selection(root, cursel, b.allocator) catch continue;
all_stop = false;
continue;
};
// char to the right of char being deleted
const egc_right, _, _ = sel.begin.egc_at(root, self.metrics) catch {
root = self.delete_selection(root, cursel, b.allocator) catch continue;
all_stop = false;
continue;
};
for (Buffer.unicode.char_pairs) |pair| if (std.mem.eql(u8, egc_left, pair[0]) and std.mem.eql(u8, egc_right, pair[1])) {
sel.begin.move_right(root, self.metrics) catch {};
break;
};
// if left char is a smart pair left char, also delete smart pair right char
for (Buffer.unicode.char_pairs) |pair| if (std.mem.eql(u8, egc_left, pair[0]) and std.mem.eql(u8, egc_right, pair[1])) {
sel.begin.move_right(root, self.metrics) catch {};
break;
};
}
}
root = self.delete_selection(root, cursel, b.allocator) catch continue;
all_stop = false;
};
@ -3592,7 +3610,7 @@ pub const Editor = struct {
pub const toggle_prefix_meta: Meta = .{ .arguments = &.{.string} };
pub fn toggle_comment(self: *Self, _: Context) Result {
const comment = if (self.syntax) |syn| syn.file_type.comment else "//";
const comment = if (self.file_type) |file_type| file_type.comment else "#";
return self.toggle_prefix(command.fmt(.{comment}));
}
pub const toggle_comment_meta: Meta = .{ .description = "Toggle comment" };
@ -3634,7 +3652,7 @@ pub const Editor = struct {
var cursel: CurSel = .{};
cursel.cursor = cursor.*;
const first = find_first_non_ws(root, cursel.cursor.row, self.metrics);
if (first == 0) return error.Stop;
if (first == 0) return root;
const off = first % self.indent_size;
const cols = if (off == 0) self.indent_size else off;
const sel = cursel.enable_selection(root, self.metrics) catch return error.Stop;
@ -4631,7 +4649,7 @@ pub const Editor = struct {
var content = std.ArrayListUnmanaged(u8).empty;
defer content.deinit(self.allocator);
try root.store(content.writer(self.allocator), eol_mode);
self.syntax = syntax.create_guess_file_type(self.allocator, content.items, self.file_path, tui.query_cache()) catch |e| switch (e) {
self.syntax = file_type_config.create_syntax_guess_file_type(self.allocator, content.items, self.file_path, tui.query_cache()) catch |e| switch (e) {
error.NotFound => null,
else => return e,
};
@ -5521,7 +5539,7 @@ pub const Editor = struct {
pub const select_meta: Meta = .{ .arguments = &.{ .integer, .integer, .integer, .integer } };
fn get_formatter(self: *Self) ?[]const []const u8 {
if (self.syntax) |syn| if (syn.file_type.formatter) |fmtr| if (fmtr.len > 0) return fmtr;
if (self.file_type) |file_type| if (file_type.formatter) |fmtr| if (fmtr.len > 0) return fmtr;
return null;
}
@ -5727,11 +5745,11 @@ pub const Editor = struct {
saved.cursor = sel.end;
break :ret sel;
};
var result = std.ArrayListUnmanaged(u8).empty;
defer result.deinit(self.allocator);
var result = std.ArrayList(u8).init(self.allocator);
defer result.deinit();
const writer: struct {
self_: *Self,
result: *std.ArrayListUnmanaged(u8),
result: *std.ArrayList(u8),
allocator: std.mem.Allocator,
const Error = @typeInfo(@typeInfo(@TypeOf(Buffer.unicode.LetterCasing.toUpperStr)).@"fn".return_type.?).error_union.error_set;
@ -5742,7 +5760,7 @@ pub const Editor = struct {
else
try letter_casing.toLowerStr(writer.self_.allocator, bytes);
defer writer.self_.allocator.free(flipped);
return writer.result.appendSlice(writer.allocator, flipped);
return writer.result.appendSlice(flipped);
}
fn map_error(e: anyerror, _: ?*std.builtin.StackTrace) Error {
return @errorCast(e);
@ -5817,29 +5835,38 @@ pub const Editor = struct {
self.syntax_refresh_full = true;
self.syntax_incremental_reparse = false;
self.syntax = syntax: {
const file_type_config_ = try file_type_config.get(file_type);
self.file_type = file_type_config_;
self.syntax = blk: {
break :blk if (self.file_type) |ft|
ft.create_syntax(self.allocator, tui.query_cache()) catch null
else
null;
};
if (self.file_type) |ft| {
var content = std.ArrayListUnmanaged(u8).empty;
defer content.deinit(std.heap.c_allocator);
const root = try self.buf_root();
try root.store(content.writer(std.heap.c_allocator), try self.buf_eol_mode());
const syn = syntax.create_file_type(self.allocator, file_type, tui.query_cache()) catch null;
if (syn) |syn_| if (self.file_path) |file_path|
if (self.file_path) |file_path|
project_manager.did_open(
file_path,
syn_.file_type,
ft,
self.lsp_version,
try content.toOwnedSlice(std.heap.c_allocator),
if (self.buffer) |p| p.is_ephemeral() else true,
) catch |e|
self.logger.print("project_manager.did_open failed: {any}", .{e});
break :syntax syn;
};
}
self.syntax_no_render = tp.env.get().is("no-syntax");
self.syntax_report_timing = tp.env.get().is("syntax-report-timing");
const ftn = if (self.syntax) |syn| syn.file_type.name else "text";
const fti = if (self.syntax) |syn| syn.file_type.icon else "🖹";
const ftc = if (self.syntax) |syn| syn.file_type.color else 0x000000;
const ftn = if (self.file_type) |ft| ft.name else "text";
const fti = if (self.file_type) |ft| ft.icon orelse "🖹" else "🖹";
const ftc = if (self.file_type) |ft| ft.color orelse 0x000000 else 0x000000;
const file_exists = if (self.buffer) |b| b.file_exists else false;
try self.send_editor_open(self.file_path orelse "", file_exists, ftn, fti, ftc);
self.logger.print("file type {s}", .{file_type});

View file

@ -8,6 +8,8 @@ const location_history = @import("location_history");
const project_manager = @import("project_manager");
const log = @import("log");
const shell = @import("shell");
const syntax = @import("syntax");
const file_type_config = @import("file_type_config");
const builtin = @import("builtin");
const build_options = @import("build_options");
@ -492,6 +494,46 @@ const cmds = struct {
}
pub const open_home_style_config_meta: Meta = .{ .description = "Edit home screen" };
pub fn change_file_type(_: *Self, _: Ctx) Result {
return tui.open_overlay(
@import("mode/overlay/file_type_palette.zig").Variant("set_file_type", "Select file type", false).Type,
);
}
pub const change_file_type_meta: Meta = .{ .description = "Change file type" };
pub fn open_file_type_config(self: *Self, ctx: Ctx) Result {
var file_type_name: []const u8 = undefined;
if (!(ctx.args.match(.{tp.extract(&file_type_name)}) catch false))
return tui.open_overlay(
@import("mode/overlay/file_type_palette.zig").Variant("open_file_type_config", "Edit file type", true).Type,
);
const file_name = try file_type_config.get_config_file_path(self.allocator, file_type_name);
defer self.allocator.free(file_name);
const file: ?std.fs.File = std.fs.openFileAbsolute(file_name, .{ .mode = .read_only }) catch null;
if (file) |f| {
f.close();
return tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_name } });
}
const content = try file_type_config.get_default(self.allocator, file_type_name);
defer self.allocator.free(content);
tui.reset_drag_context();
try self.create_editor();
try command.executeName("open_scratch_buffer", command.fmt(.{
file_name,
content,
"conf",
}));
if (self.get_active_buffer()) |buffer| buffer.mark_not_ephemeral();
}
pub const open_file_type_config_meta: Meta = .{
.arguments = &.{.string},
.description = "Edit file type configuration",
};
pub fn create_scratch_buffer(self: *Self, ctx: Ctx) Result {
const args = try ctx.args.clone(self.allocator);
defer self.allocator.free(args.buf);

View file

@ -2,129 +2,135 @@ const std = @import("std");
const cbor = @import("cbor");
const tp = @import("thespian");
const syntax = @import("syntax");
const file_type_config = @import("file_type_config");
const Widget = @import("../../Widget.zig");
const tui = @import("../../tui.zig");
pub const Type = @import("palette.zig").Create(@This());
pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_previous: bool) type {
return struct {
pub const Type = @import("palette.zig").Create(@This());
pub const label = "Select file type";
pub const name = " file type";
pub const description = "file type";
pub const label = label_;
pub const name = " file type";
pub const description = "file type";
pub const Entry = struct {
label: []const u8,
name: []const u8,
icon: []const u8,
color: u24,
};
pub const Entry = struct {
label: []const u8,
name: []const u8,
icon: []const u8,
color: u24,
};
pub const Match = struct {
name: []const u8,
score: i32,
matches: []const usize,
};
pub const Match = struct {
name: []const u8,
score: i32,
matches: []const usize,
};
var previous_file_type: ?[]const u8 = null;
var previous_file_type: ?[]const u8 = null;
pub fn load_entries(palette: *Type) !usize {
var longest_hint: usize = 0;
var idx: usize = 0;
previous_file_type = blk: {
if (tui.get_active_editor()) |editor|
if (editor.syntax) |editor_syntax|
break :blk editor_syntax.file_type.name;
break :blk null;
pub fn load_entries(palette: *Type) !usize {
var longest_hint: usize = 0;
var idx: usize = 0;
previous_file_type = blk: {
if (tui.get_active_editor()) |editor|
if (editor.file_type) |editor_file_type|
break :blk editor_file_type.name;
break :blk null;
};
for (file_type_config.get_all_names()) |file_type_name| {
const file_type = try file_type_config.get(file_type_name) orelse unreachable;
idx += 1;
(try palette.entries.addOne()).* = .{
.label = file_type.description orelse file_type_config.default.description,
.name = file_type.name,
.icon = file_type.icon orelse file_type_config.default.icon,
.color = file_type.color orelse file_type_config.default.color,
};
if (previous_file_type) |previous_name| if (std.mem.eql(u8, file_type.name, previous_name)) {
palette.initial_selected = idx;
};
longest_hint = @max(longest_hint, file_type.name.len);
}
return longest_hint;
}
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 cbor.writeValue(writer, entry.label);
try cbor.writeValue(writer, entry.icon);
try cbor.writeValue(writer, entry.color);
try cbor.writeValue(writer, entry.name);
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 {
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 description_: []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.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 {};
}
button.plane.set_style(style_label);
_ = button.plane.print("{s} ", .{description_}) catch {};
var name_: []const u8 = undefined;
if (!(cbor.matchString(&iter, &name_) catch false))
name_ = "";
button.plane.set_style(style_hint);
_ = button.plane.print_aligned_right(0, "{s} ", .{name_}) catch {};
var index: usize = 0;
var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) {
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
tui.render_match_cell(&button.plane, 0, index + 4, theme) catch break;
} else break;
}
return false;
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
var description_: []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.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_))
return;
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err("file_type_palette", e);
tp.self_pid().send(.{ "cmd", command, .{name_} }) catch |e| menu.*.opts.ctx.logger.err("file_type_palette", e);
}
};
for (syntax.FileType.file_types) |file_type| {
idx += 1;
(try palette.entries.addOne()).* = .{
.label = file_type.description,
.name = file_type.name,
.icon = file_type.icon,
.color = file_type.color,
};
if (previous_file_type) |file_type_name| if (std.mem.eql(u8, file_type.name, file_type_name)) {
palette.initial_selected = idx;
};
longest_hint = @max(longest_hint, file_type.name.len);
}
return longest_hint;
}
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 cbor.writeValue(writer, entry.label);
try cbor.writeValue(writer, entry.icon);
try cbor.writeValue(writer, entry.color);
try cbor.writeValue(writer, entry.name);
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 {
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 description_: []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.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 {};
}
button.plane.set_style(style_label);
_ = button.plane.print("{s} ", .{description_}) catch {};
var name_: []const u8 = undefined;
if (!(cbor.matchString(&iter, &name_) catch false))
name_ = "";
button.plane.set_style(style_hint);
_ = button.plane.print_aligned_right(0, "{s} ", .{name_}) catch {};
var index: usize = 0;
var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) {
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
tui.render_match_cell(&button.plane, 0, index + 4, theme) catch break;
} else break;
}
return false;
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
var description_: []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.matchInt(u24, &iter, &color) catch false)) return;
if (!(cbor.matchString(&iter, &name_) catch false)) return;
if (previous_file_type) |prev| if (std.mem.eql(u8, prev, name_))
return;
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err("file_type_palette", e);
tp.self_pid().send(.{ "cmd", "set_file_type", .{name_} }) catch |e| menu.*.opts.ctx.logger.err("file_type_palette", e);
}

View file

@ -105,8 +105,8 @@ fn init(allocator: Allocator) InitError!*Self {
var conf, const conf_bufs = root.read_config(@import("config"), allocator);
defer root.free_config(allocator, conf_bufs);
if (conf.start_debugger_on_crash)
tp.install_debugger();
if (@hasDecl(renderer, "install_crash_handler") and conf.start_debugger_on_crash)
renderer.jit_debugger_enabled = true;
const theme_, const parsed_theme = get_theme_by_name(allocator, conf.theme) orelse get_theme_by_name(allocator, "dark_modern") orelse return error.UnknownTheme;
conf.theme = theme_.name;
@ -917,11 +917,6 @@ const cmds = struct {
}
pub const change_theme_meta: Meta = .{ .description = "Change color theme" };
pub fn change_file_type(self: *Self, _: Ctx) Result {
return self.enter_overlay_mode(@import("mode/overlay/file_type_palette.zig").Type);
}
pub const change_file_type_meta: Meta = .{ .description = "Change file type" };
pub fn change_fontface(self: *Self, _: Ctx) Result {
if (build_options.gui)
self.rdr_.get_fontfaces();
@ -1117,6 +1112,10 @@ pub fn mini_mode() ?*MiniMode {
return if (current().mini_mode_) |*p| p else null;
}
pub fn open_overlay(mode: type) command.Result {
return current().enter_overlay_mode(mode);
}
pub fn query_cache() *syntax.QueryCache {
return current().query_cache_;
}