Compare commits

..

No commits in common. "e8dbff76a1c8d74d81e3e1109f65e6435bdc1643" and "9b3f6153dac683d0b981bb35890bc96546051c32" have entirely different histories.

3 changed files with 58 additions and 241 deletions

View file

@ -1,2 +1 @@
theme: []const u8 = "default", theme: []const u8 = "default",
include_files: []const u8 = "",

View file

@ -1,158 +1,54 @@
const std = @import("std"); const std = @import("std");
const cbor = @import("cbor"); const cbor = @import("cbor");
const Theme = @import("theme");
const themes = @import("themes");
const builtin = @import("builtin"); const builtin = @import("builtin");
const application_name = "flow"; const application_name = "flow";
pub fn read_config(T: type, allocator: std.mem.Allocator) struct { T, [][]const u8 } { const config = struct {
var bufs: [][]const u8 = &[_][]const u8{}; theme: []const u8 = "default",
const json_file_name = get_app_config_file_name(application_name, @typeName(T)) catch return .{ .{}, bufs }; };
const text_file_name = json_file_name[0 .. json_file_name.len - ".json".len];
var conf: T = .{}; pub fn read_config(a: std.mem.Allocator, buf: *?[]const u8) config {
if (!read_config_file(T, allocator, &conf, &bufs, text_file_name)) { const file_name = get_app_config_file_name(application_name) catch return .{};
_ = read_config_file(T, allocator, &conf, &bufs, json_file_name); return read_json_config_file(a, file_name, buf) catch .{};
}
read_nested_include_files(T, allocator, &conf, &bufs);
return .{ conf, bufs };
} }
pub fn free_config(allocator: std.mem.Allocator, bufs: [][]const u8) void { fn read_json_config_file(a: std.mem.Allocator, file_name: []const u8, buf: *?[]const u8) !config {
for (bufs) |buf| allocator.free(buf); var file = std.fs.openFileAbsolute(file_name, .{ .mode = .read_only }) catch |e| switch (e) {
} error.FileNotFound => return .{},
else => return e,
// returns true if the file was found
fn read_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs: *[][]const u8, file_name: []const u8) bool {
std.log.info("loading {s}", .{file_name});
const err: anyerror = blk: {
if (std.mem.endsWith(u8, file_name, ".json")) if (read_json_config_file(T, allocator, conf, bufs, file_name)) return true else |e| break :blk e;
if (read_text_config_file(T, allocator, conf, bufs, file_name)) return true else |e| break :blk e;
}; };
switch (err) {
error.FileNotFound => return false,
else => |e| std.log.err("error reading config file '{s}': {s}", .{ file_name, @errorName(e) }),
}
return true;
}
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(); defer file.close();
const text = try file.readToEndAlloc(allocator, 64 * 1024); const json = try file.readToEndAlloc(a, 64 * 1024);
defer allocator.free(text); defer a.free(json);
var cbor_buf = std.ArrayList(u8).init(allocator); const cbor_buf: []u8 = try a.alloc(u8, json.len);
defer cbor_buf.deinit(); buf.* = cbor_buf;
const writer = cbor_buf.writer();
var it = std.mem.splitScalar(u8, text, '\n');
var lineno: u32 = 0;
while (it.next()) |line| {
lineno += 1;
if (line.len == 0 or line[0] == '#')
continue;
const sep = std.mem.indexOfScalar(u8, line, ' ') orelse {
std.log.err("{s}:{}: {s} missing value", .{ file_name, lineno, line });
continue;
};
const name = line[0..sep];
const value_str = line[sep + 1 ..];
const cb = cbor.fromJsonAlloc(allocator, value_str) catch {
std.log.err("{s}:{}: {s} has bad value: {s}", .{ file_name, lineno, name, value_str });
continue;
};
defer allocator.free(cb);
try cbor.writeValue(writer, name);
try cbor_buf.appendSlice(cb);
}
const cb = try cbor_buf.toOwnedSlice();
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);
}
fn read_json_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 json = try file.readToEndAlloc(allocator, 64 * 1024);
defer allocator.free(json);
const cbor_buf: []u8 = try allocator.alloc(u8, json.len);
var bufs = std.ArrayListUnmanaged([]const u8).fromOwnedSlice(bufs_.*);
bufs.append(allocator, cbor_buf) catch @panic("OOM:read_json_config_file");
bufs_.* = bufs.toOwnedSlice(allocator) catch @panic("OOM:read_json_config_file");
const cb = try cbor.fromJson(json, cbor_buf); const cb = try cbor.fromJson(json, cbor_buf);
var iter = cb; var iter = cb;
_ = try cbor.decodeMapHeader(&iter); var len = try cbor.decodeMapHeader(&iter);
return read_cbor_config(T, conf, file_name, iter); var data: config = .{};
} while (len > 0) : (len -= 1) {
var found = false;
fn read_cbor_config( var field_name: []const u8 = undefined;
T: type, if (!(try cbor.matchString(&iter, &field_name))) return error.InvalidConfig;
conf: *T, inline for (@typeInfo(config).@"struct".fields) |field_info| {
file_name: []const u8, if (std.mem.eql(u8, field_name, field_info.name)) {
cb: []const u8,
) !void {
var iter = cb;
var field_name: []const u8 = undefined;
while (cbor.matchString(&iter, &field_name) catch |e| switch (e) {
error.TooShort => return,
else => return e,
}) {
var known = false;
inline for (@typeInfo(T).@"struct".fields) |field_info|
if (comptime std.mem.eql(u8, "include_files", field_info.name)) {
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))) {
if (conf.include_files.len > 0) {
std.log.warn("{s}: ignoring nested 'include_files' value '{s}'", .{ file_name, value });
} else {
@field(conf, field_info.name) = value;
}
} else {
try cbor.skipValue(&iter);
std.log.err("invalid value for key '{s}'", .{field_name});
}
}
} else if (std.mem.eql(u8, field_name, field_info.name)) {
known = true;
var value: field_info.type = undefined; var value: field_info.type = undefined;
if (try cbor.matchValue(&iter, cbor.extract(&value))) { if (!(try cbor.matchValue(&iter, cbor.extract(&value)))) return error.InvalidConfig;
@field(conf, field_info.name) = value; @field(data, field_info.name) = value;
} else { found = true;
try cbor.skipValue(&iter); }
std.log.err("invalid value for key '{s}'", .{field_name});
}
};
if (!known) {
try cbor.skipValue(&iter);
std.log.warn("unknown config value '{s}' ignored", .{field_name});
} }
if (!found) try cbor.skipValue(&iter);
} }
} return data;
fn read_nested_include_files(T: type, allocator: std.mem.Allocator, conf: *T, bufs: *[][]const u8) void {
if (conf.include_files.len == 0) return;
var it = std.mem.splitScalar(u8, conf.include_files, std.fs.path.delimiter);
while (it.next()) |path| if (!read_config_file(T, allocator, conf, bufs, path)) {
std.log.warn("config include file '{s}' is not found", .{path});
};
} }
pub fn get_config_dir() ![]const u8 { pub fn get_config_dir() ![]const u8 {
return get_app_config_dir(application_name); return get_app_config_dir(application_name);
} }
pub const ConfigDirError = error{ fn get_app_config_dir(appname: []const u8) ![]const u8 {
NoSpaceLeft,
MakeConfigDirFailed,
MakeHomeConfigDirFailed,
MakeAppConfigDirFailed,
AppConfigDirUnavailable,
};
fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 {
const a = std.heap.c_allocator; const a = std.heap.c_allocator;
const local = struct { const local = struct {
var config_dir_buffer: [std.posix.PATH_MAX]u8 = undefined; var config_dir_buffer: [std.posix.PATH_MAX]u8 = undefined;
@ -168,7 +64,7 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 {
const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/.config", .{home}); const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/.config", .{home});
std.fs.makeDirAbsolute(dir) catch |e| switch (e) { std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {}, error.PathAlreadyExists => {},
else => return error.MakeHomeConfigDirFailed, else => return e,
}; };
break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/.config/{s}", .{ home, appname }); break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/.config/{s}", .{ home, appname });
} else if (builtin.os.tag == .windows) ret: { } else if (builtin.os.tag == .windows) ret: {
@ -177,7 +73,7 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 {
const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/{s}", .{ appdata, appname }); const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/{s}", .{ appdata, appname });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) { std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {}, error.PathAlreadyExists => {},
else => return error.MakeAppConfigDirFailed, else => return e,
}; };
break :ret dir; break :ret dir;
} else return error.AppConfigDirUnavailable; } else return error.AppConfigDirUnavailable;
@ -186,79 +82,21 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 {
local.config_dir = config_dir; local.config_dir = config_dir;
std.fs.makeDirAbsolute(config_dir) catch |e| switch (e) { std.fs.makeDirAbsolute(config_dir) catch |e| switch (e) {
error.PathAlreadyExists => {}, error.PathAlreadyExists => {},
else => return error.MakeConfigDirFailed, else => return e,
}; };
var theme_dir_buffer: [std.posix.PATH_MAX]u8 = undefined;
std.fs.makeDirAbsolute(try std.fmt.bufPrint(&theme_dir_buffer, "{s}/{s}", .{ config_dir, theme_dir })) catch {};
return config_dir; return config_dir;
} }
fn get_app_config_file_name(appname: []const u8, comptime base_name: []const u8) ConfigDirError![]const u8 { fn get_app_config_file_name(appname: []const u8) ![]const u8 {
return get_app_config_dir_file_name(appname, base_name ++ ".json");
}
fn get_app_config_dir_file_name(appname: []const u8, comptime config_file_name: []const u8) ConfigDirError![]const u8 {
const local = struct { const local = struct {
var config_file_buffer: [std.posix.PATH_MAX]u8 = undefined; var config_file_buffer: [std.posix.PATH_MAX]u8 = undefined;
var config_file: ?[]const u8 = null;
}; };
return std.fmt.bufPrint(&local.config_file_buffer, "{s}/{s}", .{ try get_app_config_dir(appname), config_file_name }); const config_file_name = "config.json";
} const config_file = if (local.config_file) |file|
file
const theme_dir = "themes"; else
try std.fmt.bufPrint(&local.config_file_buffer, "{s}/{s}", .{ try get_app_config_dir(appname), config_file_name });
fn get_theme_directory() ![]const u8 { local.config_file = config_file;
const local = struct { return config_file;
var dir_buffer: [std.posix.PATH_MAX]u8 = undefined;
};
const a = std.heap.c_allocator;
if (std.process.getEnvVarOwned(a, "FLOW_THEMES_DIR") catch null) |dir| {
defer a.free(dir);
return try std.fmt.bufPrint(&local.dir_buffer, "{s}", .{dir});
}
return try std.fmt.bufPrint(&local.dir_buffer, "{s}/{s}", .{ try get_app_config_dir(application_name), theme_dir });
}
pub fn get_theme_file_name(theme_name: []const u8) ![]const u8 {
const dir = try get_theme_directory();
const local = struct {
var file_buffer: [std.posix.PATH_MAX]u8 = undefined;
};
return try std.fmt.bufPrint(&local.file_buffer, "{s}/{s}.json", .{ dir, theme_name });
}
fn read_theme(allocator: std.mem.Allocator, theme_name: []const u8) ?[]const u8 {
const file_name = get_theme_file_name(theme_name) catch return null;
var file = std.fs.openFileAbsolute(file_name, .{ .mode = .read_only }) catch return null;
defer file.close();
return file.readToEndAlloc(allocator, 64 * 1024) catch null;
}
fn load_theme_file(allocator: std.mem.Allocator, theme_name: []const u8) !?std.json.Parsed(Theme) {
return load_theme_file_internal(allocator, theme_name) catch |e| {
std.log.err("loaded theme from file failed: {}", .{e});
return e;
};
}
fn load_theme_file_internal(allocator: std.mem.Allocator, theme_name: []const u8) !?std.json.Parsed(Theme) {
_ = std.json.Scanner;
const json_str = read_theme(allocator, theme_name) orelse return null;
defer allocator.free(json_str);
return try std.json.parseFromSlice(Theme, allocator, json_str, .{ .allocate = .alloc_always });
}
pub fn get_theme_by_name(allocator: std.mem.Allocator, name: []const u8) ?struct { Theme, ?std.json.Parsed(Theme) } {
if (load_theme_file(allocator, name) catch null) |parsed_theme| {
std.log.info("loaded theme from file: {s}", .{name});
return .{ parsed_theme.value, parsed_theme };
}
std.log.info("loading theme: {s}", .{name});
for (themes.themes) |theme_| {
if (std.mem.eql(u8, theme_.name, name))
return .{ theme_, null };
}
return null;
} }

View file

@ -13,11 +13,6 @@ var lang_override: ?[]const u8 = null;
var lang_default: []const u8 = "conf"; var lang_default: []const u8 = "conf";
const no_highlight = std.math.maxInt(usize); const no_highlight = std.math.maxInt(usize);
const builtin = @import("builtin");
pub const std_options: std.Options = .{
.log_level = if (builtin.mode == .Debug) .info else .err,
};
pub fn main() !void { pub fn main() !void {
const params = comptime clap.parseParamsComptime( const params = comptime clap.parseParamsComptime(
\\-h, --help Display this help and exit. \\-h, --help Display this help and exit.
@ -25,7 +20,6 @@ pub fn main() !void {
\\-t, --theme <name> Select theme to use. \\-t, --theme <name> Select theme to use.
\\-d, --default <name> Set the language to use if guessing failed (default: conf). \\-d, --default <name> Set the language to use if guessing failed (default: conf).
\\-s, --show-language Show detected language in output. \\-s, --show-language Show detected language in output.
\\-T, --show-theme Show selected theme in output.
\\-C, --color Always produce color output, even if stdout is not a tty. \\-C, --color Always produce color output, even if stdout is not a tty.
\\--html Output HTML instead of ansi escape codes. \\--html Output HTML instead of ansi escape codes.
\\--list-themes Show available themes. \\--list-themes Show available themes.
@ -78,8 +72,8 @@ pub fn main() !void {
if (res.args.color == 0 and !stdout_file.supportsAnsiEscapeCodes()) if (res.args.color == 0 and !stdout_file.supportsAnsiEscapeCodes())
return plain_cat(res.positionals[0]); return plain_cat(res.positionals[0]);
const conf, const conf_bufs = config_loader.read_config(@import("config.zig"), a); var conf_buf: ?[]const u8 = null;
defer config_loader.free_config(a, conf_bufs); const conf = config_loader.read_config(a, &conf_buf);
const theme_name = if (res.args.theme) |theme| theme else conf.theme; const theme_name = if (res.args.theme) |theme| theme else conf.theme;
const limit_lines = res.args.limit; const limit_lines = res.args.limit;
@ -94,15 +88,14 @@ pub fn main() !void {
} }
if (highlight_line_end < highlight_line_start) { if (highlight_line_end < highlight_line_start) {
std.log.err("invalid range", .{}); try std.io.getStdErr().writer().print("invalid range\n", .{});
std.process.exit(1); std.process.exit(1);
} }
const theme, const parsed_theme = config_loader.get_theme_by_name(a, theme_name) orelse { const theme = get_theme_by_name(theme_name) orelse {
std.log.err("theme \"{s}\" not found", .{theme_name}); try std.io.getStdErr().writer().print("theme \"{s}\" not found\n", .{theme_name});
std.process.exit(1); std.process.exit(1);
}; };
_ = parsed_theme;
const set_style: StyleFn = if (res.args.html != 0) set_html_style else set_ansi_style; const set_style: StyleFn = if (res.args.html != 0) set_html_style else set_ansi_style;
const unset_style: StyleFn = if (res.args.html != 0) unset_html_style else unset_ansi_style; const unset_style: StyleFn = if (res.args.html != 0) unset_html_style else unset_ansi_style;
@ -129,7 +122,6 @@ pub fn main() !void {
arg, arg,
&theme, &theme,
res.args.@"show-language" != 0, res.args.@"show-language" != 0,
res.args.@"show-theme" != 0,
set_style, set_style,
unset_style, unset_style,
highlight_line_start, highlight_line_start,
@ -151,7 +143,6 @@ pub fn main() !void {
"-", "-",
&theme, &theme,
res.args.@"show-language" != 0, res.args.@"show-language" != 0,
res.args.@"show-theme" != 0,
set_style, set_style,
unset_style, unset_style,
highlight_line_start, highlight_line_start,
@ -175,7 +166,7 @@ fn get_parser(a: std.mem.Allocator, content: []const u8, file_path: []const u8,
} }
fn unknown_file_type(name: []const u8) noreturn { fn unknown_file_type(name: []const u8) noreturn {
std.log.err("unknown file type \'{s}\'\n", .{name}); std.io.getStdErr().writer().print("unknown file type \'{s}\'\n", .{name}) catch {};
std.process.exit(1); std.process.exit(1);
} }
@ -187,8 +178,7 @@ fn render_file(
content: []const u8, content: []const u8,
file_path: []const u8, file_path: []const u8,
theme: *const Theme, theme: *const Theme,
show_file_type: bool, show: bool,
show_theme: bool,
set_style: StyleFn, set_style: StyleFn,
unset_style: StyleFn, unset_style: StyleFn,
highlight_line_start: usize, highlight_line_start: usize,
@ -213,14 +203,10 @@ fn render_file(
const query_cache = try syntax.QueryCache.create(a, .{}); const query_cache = try syntax.QueryCache.create(a, .{});
const parser = get_parser(a, content, file_path, query_cache); const parser = get_parser(a, content, file_path, query_cache);
try parser.refresh_full(content); try parser.refresh_full(content);
if (show_file_type) { if (show) {
try render_file_type(writer, parser.file_type, theme); try render_file_type(writer, parser.file_type, theme);
end_line -= 1; end_line -= 1;
} }
if (show_theme) {
try render_theme_indicator(writer, theme);
end_line -= 1;
}
const Ctx = struct { const Ctx = struct {
writer: @TypeOf(writer), writer: @TypeOf(writer),
@ -392,6 +378,14 @@ pub const fallbacks: []const FallBack = &[_]FallBack{
.{ .ts = "field", .tm = "variable" }, .{ .ts = "field", .tm = "variable" },
}; };
fn get_theme_by_name(name: []const u8) ?Theme {
for (themes.themes) |theme| {
if (std.mem.eql(u8, theme.name, name))
return theme;
}
return null;
}
fn list_themes(writer: Writer) !void { fn list_themes(writer: Writer) !void {
var max_name_len: usize = 0; var max_name_len: usize = 0;
for (themes.themes) |theme| for (themes.themes) |theme|
@ -494,20 +488,6 @@ fn render_file_type(writer: Writer, file_type: *const syntax.FileType, theme: *c
try writer.writeAll("\n"); try writer.writeAll("\n");
} }
fn render_theme_indicator(writer: Writer, theme: *const Theme) !void {
const style = Theme.Style{ .bg = theme.editor_selection.bg, .fg = theme.editor.fg };
const reversed = Theme.Style{ .fg = theme.editor_selection.bg };
const plain: Theme.Style = Theme.Style{ .fg = theme.editor.fg };
try set_ansi_style(writer, reversed);
try writer.writeAll("");
try set_ansi_style(writer, style);
try writer.writeAll(theme.name);
try set_ansi_style(writer, reversed);
try writer.writeAll("");
try set_ansi_style(writer, plain);
try writer.writeAll("\n");
}
fn plain_cat(files: []const []const u8) !void { fn plain_cat(files: []const []const u8) !void {
const stdout = std.io.getStdOut(); const stdout = std.io.getStdOut();
if (files.len == 0) { if (files.len == 0) {