From f7cea9684455868811500a437341fda7d35cd1dd Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 14 Jul 2025 16:34:28 +0200 Subject: [PATCH] feat: merge configured and static file type lists This allows adding of new file types by adding config files. --- src/file_type_config.zig | 112 ++++++++++++++++----- src/list_languages.zig | 13 ++- src/tui/mode/overlay/file_type_palette.zig | 6 +- 3 files changed, 97 insertions(+), 34 deletions(-) diff --git a/src/file_type_config.zig b/src/file_type_config.zig index 4092f0a..4718eba 100644 --- a/src/file_type_config.zig +++ b/src/file_type_config.zig @@ -43,37 +43,59 @@ pub fn get_default(allocator: std.mem.Allocator, file_type_name: []const u8) ![] 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: std.StringHashMapUnmanaged(*@This()) = .{}; +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(); - const self = if (cache.get(file_type_name)) |self| self.* else blk: { - const file_name = try get_config_file_path(cache_allocator, file_type_name); - defer cache_allocator.free(file_name); + 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 :blk self; - } else break :blk if (syntax.FileType.get_by_name_static(file_type_name)) |ft| from_file_type(ft) else null; + 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; }; - - return self; } -pub fn get_config_file_path(allocator: std.mem.Allocator, file_type: []const u8) ![]const u8 { +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()); @@ -84,18 +106,60 @@ pub fn get_config_file_path(allocator: std.mem.Allocator, file_type: []const u8) error.PathAlreadyExists => {}, else => return e, }; - _ = try writer.writeAll(file_type); - _ = try writer.writeAll(".conf"); 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()) |static_file_type| { + if (!names.contains(static_file_type.name)) + try names.put(allocator, try allocator.dupe(u8, static_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 (syntax.FileType.static_file_types) |*static_file_type| { + for (syntax.FileType.get_all()) |static_file_type| { const file_type = get(static_file_type.name) catch unreachable orelse unreachable; if (file_path) |fp| if (syntax.FileType.match_file_type(file_type.extensions orelse static_file_type.extensions, fp)) return file_type; @@ -105,7 +169,7 @@ fn guess(file_path: ?[]const u8, content: []const u8) ?@This() { 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 (syntax.FileType.static_file_types) |*static_file_type| { + for (syntax.FileType.get_all()) |static_file_type| { const file_type = get(static_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; diff --git a/src/list_languages.zig b/src/list_languages.zig index fbef96e..6efbd10 100644 --- a/src/list_languages.zig +++ b/src/list_languages.zig @@ -1,5 +1,4 @@ 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; @@ -17,8 +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.static_file_types) |static_file_type| { - const file_type = try file_type_config.get(static_file_type.name) orelse unreachable; + 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)); @@ -33,11 +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.static_file_types) |static_file_type| { - const file_type = try file_type_config.get(static_file_type.name) orelse unreachable; + 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 orelse static_file_type.color); - try writer.writeAll(file_type.icon orelse static_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); diff --git a/src/tui/mode/overlay/file_type_palette.zig b/src/tui/mode/overlay/file_type_palette.zig index 44b34f2..90f1305 100644 --- a/src/tui/mode/overlay/file_type_palette.zig +++ b/src/tui/mode/overlay/file_type_palette.zig @@ -40,8 +40,8 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_ break :blk null; }; - for (file_type_config.get_all()) |static_file_type| { - const file_type = try file_type_config.get(static_file_type.name) orelse unreachable; + 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, @@ -49,7 +49,7 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_ .icon = file_type.icon orelse file_type_config.default.icon, .color = file_type.color orelse file_type_config.default.color, }; - if (previous_file_type) |file_type_name| if (std.mem.eql(u8, file_type.name, file_type_name)) { + 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);