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,15 +43,26 @@ pub fn get_default(allocator: std.mem.Allocator, file_type_name: []const u8) ![]
return content.toOwnedSlice(allocator); 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; const cache_allocator = std.heap.c_allocator;
var cache_mutex: std.Thread.Mutex = .{}; 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() { pub fn get(file_type_name: []const u8) !?@This() {
cache_mutex.lock(); cache_mutex.lock();
defer cache_mutex.unlock(); defer cache_mutex.unlock();
const self = if (cache.get(file_type_name)) |self| self.* else blk: { 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); const file_name = try get_config_file_path(cache_allocator, file_type_name);
defer cache_allocator.free(file_name); defer cache_allocator.free(file_name);
@ -66,14 +77,25 @@ pub fn get(file_type_name: []const u8) !?@This() {
var self: @This() = .{}; var self: @This() = .{};
var bufs_: [][]const u8 = &.{}; // cached, no need to free var bufs_: [][]const u8 = &.{}; // cached, no need to free
try root.parse_text_config_file(@This(), cache_allocator, &self, &bufs_, file_name, buf); try root.parse_text_config_file(@This(), cache_allocator, &self, &bufs_, file_name, buf);
break :blk self; break :file_type self;
} else break :blk if (syntax.FileType.get_by_name_static(file_type_name)) |ft| from_file_type(ft) else null; } 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); var stream = std.ArrayList(u8).init(allocator);
const writer = stream.writer(); const writer = stream.writer();
_ = try writer.writeAll(try root.get_config_dir()); _ = 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 => {}, error.PathAlreadyExists => {},
else => return e, else => return e,
}; };
_ = try writer.writeAll(file_type);
_ = try writer.writeAll(".conf");
return stream.toOwnedSlice(); 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() { pub fn guess_file_type(file_path: ?[]const u8, content: []const u8) ?@This() {
return guess(file_path, content); return guess(file_path, content);
} }
fn guess(file_path: ?[]const u8, content: []const u8) ?@This() { fn guess(file_path: ?[]const u8, content: []const u8) ?@This() {
if (guess_first_line(content)) |ft| return ft; 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; 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)) if (file_path) |fp| if (syntax.FileType.match_file_type(file_type.extensions orelse static_file_type.extensions, fp))
return file_type; 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() { fn guess_first_line(content: []const u8) ?@This() {
const first_line = if (std.mem.indexOf(u8, content, "\n")) |pos| content[0..pos] else content; 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; 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)) if (syntax.FileType.match_first_line(file_type.first_line_matches_prefix, file_type.first_line_matches_content, first_line))
return file_type; return file_type;

View file

@ -1,5 +1,4 @@
const std = @import("std"); const std = @import("std");
const syntax = @import("syntax");
const file_type_config = @import("file_type_config"); const file_type_config = @import("file_type_config");
const builtin = @import("builtin"); const builtin = @import("builtin");
const RGB = @import("color").RGB; 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_formatter_len: usize = 0;
var max_extensions_len: usize = 0; var max_extensions_len: usize = 0;
for (syntax.FileType.static_file_types) |static_file_type| { for (file_type_config.get_all_names()) |file_type_name| {
const file_type = try file_type_config.get(static_file_type.name) orelse unreachable; 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_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_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)); 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 tty_config.setColor(writer, .reset);
try writer.writeAll("\n"); try writer.writeAll("\n");
for (syntax.FileType.static_file_types) |static_file_type| { for (file_type_config.get_all_names()) |file_type_name| {
const file_type = try file_type_config.get(static_file_type.name) orelse unreachable; const file_type = try file_type_config.get(file_type_name) orelse unreachable;
try writer.writeAll(" "); try writer.writeAll(" ");
try setColorRgb(writer, file_type.color orelse static_file_type.color); try setColorRgb(writer, file_type.color orelse file_type_config.default.color);
try writer.writeAll(file_type.icon orelse static_file_type.icon); try writer.writeAll(file_type.icon orelse file_type_config.default.icon);
try tty_config.setColor(writer, .reset); try tty_config.setColor(writer, .reset);
try writer.writeAll(" "); try writer.writeAll(" ");
try write_string(writer, file_type.name, max_language_len + 1); 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; break :blk null;
}; };
for (file_type_config.get_all()) |static_file_type| { for (file_type_config.get_all_names()) |file_type_name| {
const file_type = try file_type_config.get(static_file_type.name) orelse unreachable; const file_type = try file_type_config.get(file_type_name) orelse unreachable;
idx += 1; idx += 1;
(try palette.entries.addOne()).* = .{ (try palette.entries.addOne()).* = .{
.label = file_type.description orelse file_type_config.default.description, .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, .icon = file_type.icon orelse file_type_config.default.icon,
.color = file_type.color orelse file_type_config.default.color, .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; palette.initial_selected = idx;
}; };
longest_hint = @max(longest_hint, file_type.name.len); longest_hint = @max(longest_hint, file_type.name.len);