feat: merge configured and static file type lists

This allows adding of new file types by adding config files.
This commit is contained in:
CJ van den Berg 2025-07-14 16:34:28 +02:00
parent abd1e683a3
commit f7cea96844
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
3 changed files with 97 additions and 34 deletions

View file

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

View file

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

View file

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