feat: complete loading of configurable file types

This commit is contained in:
CJ van den Berg 2025-07-14 13:25:21 +02:00
parent 4ad8728669
commit 818b2cf915
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9

View file

@ -1,3 +1,4 @@
name: []const u8 = "none",
description: ?[]const u8 = null, description: ?[]const u8 = null,
extensions: ?[]const []const u8 = null, extensions: ?[]const []const u8 = null,
icon: ?[]const u8 = null, icon: ?[]const u8 = null,
@ -7,11 +8,13 @@ formatter: ?[]const []const u8 = null,
language_server: ?[]const []const u8 = null, language_server: ?[]const []const u8 = null,
first_line_matches_prefix: ?[]const u8 = null, first_line_matches_prefix: ?[]const u8 = null,
first_line_matches_content: ?[]const u8 = null, first_line_matches_content: ?[]const u8 = null,
first_line_matches: ?[]const u8 = null,
include_files: []const u8 = "", include_files: []const u8 = "",
pub fn from_file_type(file_type: *const FileType) @This() { fn from_file_type(file_type: *const syntax.FileType) @This() {
return .{ return .{
.name = file_type.name,
.color = file_type.color, .color = file_type.color,
.icon = file_type.icon, .icon = file_type.icon,
.description = file_type.description, .description = file_type.description,
@ -24,4 +27,103 @@ pub fn from_file_type(file_type: *const FileType) @This() {
}; };
} }
const FileType = @import("syntax").FileType; 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);
}
const cache_allocator = std.heap.c_allocator;
var cache_mutex: std.Thread.Mutex = .{};
var cache: std.StringHashMapUnmanaged(*@This()) = .{};
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);
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;
};
return self;
}
pub fn get_config_file_path(allocator: std.mem.Allocator, file_type: []const u8) ![]const 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,
};
_ = try writer.writeAll(file_type);
_ = try writer.writeAll(".conf");
return stream.toOwnedSlice();
}
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| {
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;
}
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 (syntax.FileType.static_file_types) |*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;
}
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.name) orelse return error.FileTypeNotFound,
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");