feat: add Customise theme
command
This commit is contained in:
parent
c3cda5b7fe
commit
58082ed2a1
3 changed files with 125 additions and 29 deletions
|
@ -19,8 +19,8 @@
|
|||
.hash = "thespian-0.0.1-owFOjnUTBgBUlBtQ-SbN_6S7bXE6e2mKmCgk4A-RGBMA",
|
||||
},
|
||||
.themes = .{
|
||||
.url = "https://github.com/neurocyte/flow-themes/releases/download/master-59bf204551bcb238faddd06779063570e7e6d431/flow-themes.tar.gz",
|
||||
.hash = "N-V-__8AAM9UFwCaITo5LqgOpcfd4SXnFhuwJ4rEuZ253yt6",
|
||||
.url = "https://github.com/neurocyte/flow-themes/releases/download/master-ac2e3fe2df3419b71276f86fa9c45fd39d668f23/flow-themes.tar.gz",
|
||||
.hash = "N-V-__8AAEtaFwAjAHCmWHRCrBxL7uSG4hQiIsSgS32Y67K6",
|
||||
},
|
||||
.fuzzig = .{
|
||||
.url = "https://github.com/fjebaker/fuzzig/archive/44c04733c7c0fee3db83672aaaaf4ed03e943156.tar.gz",
|
||||
|
|
53
src/main.zig
53
src/main.zig
|
@ -671,6 +671,34 @@ pub fn list_keybind_namespaces(allocator: std.mem.Allocator) ![]const []const u8
|
|||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub 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;
|
||||
}
|
||||
|
||||
pub fn write_theme(theme_name: []const u8, content: []const u8) !void {
|
||||
const file_name = try get_theme_file_name(theme_name);
|
||||
var file = try std.fs.createFileAbsolute(file_name, .{ .truncate = true });
|
||||
defer file.close();
|
||||
return file.writeAll(content);
|
||||
}
|
||||
|
||||
pub fn list_themes(allocator: std.mem.Allocator) ![]const []const u8 {
|
||||
var dir = try std.fs.openDirAbsolute(try get_theme_directory(), .{ .iterate = true });
|
||||
defer dir.close();
|
||||
var result = std.ArrayList([]const u8).init(allocator);
|
||||
var iter = dir.iterateAssumeFirstIteration();
|
||||
while (try iter.next()) |entry| {
|
||||
switch (entry.kind) {
|
||||
.file, .sym_link => try result.append(try allocator.dupe(u8, std.fs.path.stem(entry.name))),
|
||||
else => continue,
|
||||
}
|
||||
}
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub fn get_config_dir() ![]const u8 {
|
||||
return get_app_config_dir(application_name);
|
||||
}
|
||||
|
@ -723,6 +751,9 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 {
|
|||
var keybind_dir_buffer: [std.posix.PATH_MAX]u8 = undefined;
|
||||
std.fs.makeDirAbsolute(try std.fmt.bufPrint(&keybind_dir_buffer, "{s}/{s}", .{ config_dir, keybind_dir })) catch {};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -868,6 +899,28 @@ pub fn get_keybind_namespace_file_name(namespace_name: []const u8) ![]const u8 {
|
|||
return try std.fmt.bufPrint(&local.file_buffer, "{s}/{s}.json", .{ dir, namespace_name });
|
||||
}
|
||||
|
||||
const theme_dir = "themes";
|
||||
|
||||
fn get_theme_directory() ![]const u8 {
|
||||
const local = struct {
|
||||
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 restart() noreturn {
|
||||
var executable: [:0]const u8 = std.mem.span(std.os.argv[0]);
|
||||
var is_basename = true;
|
||||
|
|
|
@ -44,6 +44,7 @@ commands: Commands = undefined,
|
|||
logger: log.Logger,
|
||||
drag_source: ?*Widget = null,
|
||||
theme_: Widget.Theme,
|
||||
parsed_theme: ?std.json.Parsed(Widget.Theme),
|
||||
idle_frame_count: usize = 0,
|
||||
unrendered_input_events_count: usize = 0,
|
||||
init_timer: ?tp.timeout,
|
||||
|
@ -101,7 +102,7 @@ fn init(allocator: Allocator) InitError!*Self {
|
|||
var conf, const conf_bufs = root.read_config(@import("config"), allocator);
|
||||
defer root.free_config(allocator, conf_bufs);
|
||||
|
||||
const theme_ = get_theme_by_name(conf.theme) orelse get_theme_by_name("dark_modern") orelse return error.UnknownTheme;
|
||||
const theme_, const parsed_theme = get_theme_by_name(allocator, conf.theme) orelse get_theme_by_name(allocator, "dark_modern") orelse return error.UnknownTheme;
|
||||
conf.theme = theme_.name;
|
||||
conf.whitespace_mode = try allocator.dupe(u8, conf.whitespace_mode);
|
||||
conf.input_mode = try allocator.dupe(u8, conf.input_mode);
|
||||
|
@ -135,6 +136,7 @@ fn init(allocator: Allocator) InitError!*Self {
|
|||
.theme_ = theme_,
|
||||
.no_sleep = tp.env.get().is("no-sleep"),
|
||||
.query_cache_ = try syntax.QueryCache.create(allocator, .{}),
|
||||
.parsed_theme = parsed_theme,
|
||||
};
|
||||
instance_ = self;
|
||||
defer instance_ = null;
|
||||
|
@ -687,6 +689,19 @@ fn refresh_input_mode(self: *Self) command.Result {
|
|||
self.input_mode_ = new_mode;
|
||||
}
|
||||
|
||||
fn set_theme_by_name(self: *Self, name: []const u8) !void {
|
||||
const old = self.parsed_theme;
|
||||
defer if (old) |p| p.deinit();
|
||||
self.theme_, self.parsed_theme = get_theme_by_name(self.allocator, name) orelse {
|
||||
self.logger.print("theme not found: {s}", .{name});
|
||||
return;
|
||||
};
|
||||
self.config_.theme = self.theme_.name;
|
||||
self.set_terminal_style();
|
||||
self.logger.print("theme: {s}", .{self.theme_.description});
|
||||
try save_config();
|
||||
}
|
||||
|
||||
const cmds = struct {
|
||||
pub const Target = Self;
|
||||
const Ctx = command.Context;
|
||||
|
@ -709,32 +724,19 @@ const cmds = struct {
|
|||
var name: []const u8 = undefined;
|
||||
if (!try ctx.args.match(.{tp.extract(&name)}))
|
||||
return tp.exit_error(error.InvalidSetThemeArgument, null);
|
||||
self.theme_ = get_theme_by_name(name) orelse {
|
||||
self.logger.print("theme not found: {s}", .{name});
|
||||
return;
|
||||
};
|
||||
self.config_.theme = self.theme_.name;
|
||||
self.set_terminal_style();
|
||||
self.logger.print("theme: {s}", .{self.theme_.description});
|
||||
try save_config();
|
||||
return self.set_theme_by_name(name);
|
||||
}
|
||||
pub const set_theme_meta: Meta = .{ .arguments = &.{.string} };
|
||||
|
||||
pub fn theme_next(self: *Self, _: Ctx) Result {
|
||||
self.theme_ = get_next_theme_by_name(self.theme_.name);
|
||||
self.config_.theme = self.theme_.name;
|
||||
self.set_terminal_style();
|
||||
self.logger.print("theme: {s}", .{self.theme_.description});
|
||||
try save_config();
|
||||
const name = get_next_theme_by_name(self.theme_.name);
|
||||
return self.set_theme_by_name(name);
|
||||
}
|
||||
pub const theme_next_meta: Meta = .{ .description = "Next color theme" };
|
||||
|
||||
pub fn theme_prev(self: *Self, _: Ctx) Result {
|
||||
self.theme_ = get_prev_theme_by_name(self.theme_.name);
|
||||
self.config_.theme = self.theme_.name;
|
||||
self.set_terminal_style();
|
||||
self.logger.print("theme: {s}", .{self.theme_.description});
|
||||
try save_config();
|
||||
const name = get_prev_theme_by_name(self.theme_.name);
|
||||
return self.set_theme_by_name(name);
|
||||
}
|
||||
pub const theme_prev_meta: Meta = .{ .description = "Previous color theme" };
|
||||
|
||||
|
@ -967,6 +969,13 @@ const cmds = struct {
|
|||
}
|
||||
pub const open_keybind_config_meta: Meta = .{ .description = "Edit key bindings" };
|
||||
|
||||
pub fn open_custom_theme(self: *Self, _: Ctx) Result {
|
||||
const file_name = try self.get_or_create_theme_file(self.allocator);
|
||||
try tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_name } });
|
||||
self.logger.print("restart flow to use changed theme", .{});
|
||||
}
|
||||
pub const open_custom_theme_meta: Meta = .{ .description = "Customize theme" };
|
||||
|
||||
pub fn run_async(self: *Self, ctx: Ctx) Result {
|
||||
var iter = ctx.args.buf;
|
||||
var len = try cbor.decodeArrayHeader(&iter);
|
||||
|
@ -1173,33 +1182,38 @@ pub fn theme() *const Widget.Theme {
|
|||
return ¤t().theme_;
|
||||
}
|
||||
|
||||
pub fn get_theme_by_name(name: []const u8) ?Widget.Theme {
|
||||
pub fn get_theme_by_name(allocator: std.mem.Allocator, name: []const u8) ?struct { Widget.Theme, ?std.json.Parsed(Widget.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 };
|
||||
}
|
||||
|
||||
for (Widget.themes) |theme_| {
|
||||
if (std.mem.eql(u8, theme_.name, name))
|
||||
return theme_;
|
||||
return .{ theme_, null };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn get_next_theme_by_name(name: []const u8) Widget.Theme {
|
||||
fn get_next_theme_by_name(name: []const u8) []const u8 {
|
||||
var next = false;
|
||||
for (Widget.themes) |theme_| {
|
||||
if (next)
|
||||
return theme_;
|
||||
return theme_.name;
|
||||
if (std.mem.eql(u8, theme_.name, name))
|
||||
next = true;
|
||||
}
|
||||
return Widget.themes[0];
|
||||
return Widget.themes[0].name;
|
||||
}
|
||||
|
||||
pub fn get_prev_theme_by_name(name: []const u8) Widget.Theme {
|
||||
fn get_prev_theme_by_name(name: []const u8) []const u8 {
|
||||
var prev: ?Widget.Theme = null;
|
||||
for (Widget.themes) |theme_| {
|
||||
if (std.mem.eql(u8, theme_.name, name))
|
||||
return prev orelse Widget.themes[Widget.themes.len - 1];
|
||||
return (prev orelse Widget.themes[Widget.themes.len - 1]).name;
|
||||
prev = theme_;
|
||||
}
|
||||
return Widget.themes[Widget.themes.len - 1];
|
||||
return Widget.themes[Widget.themes.len - 1].name;
|
||||
}
|
||||
|
||||
pub fn find_scope_style(theme_: *const Widget.Theme, scope: []const u8) ?Widget.Theme.Token {
|
||||
|
@ -1342,3 +1356,32 @@ pub fn render_match_cell(self: *renderer.Plane, y: usize, x: usize, theme_: *con
|
|||
cell.set_style(theme_.editor_match);
|
||||
_ = self.putc(&cell) catch {};
|
||||
}
|
||||
|
||||
fn get_or_create_theme_file(self: *Self, allocator: std.mem.Allocator) ![]const u8 {
|
||||
const theme_name = self.theme_.name;
|
||||
if (root.read_theme(allocator, theme_name)) |content| {
|
||||
allocator.free(content);
|
||||
} else {
|
||||
var buf = std.ArrayList(u8).init(self.allocator);
|
||||
defer buf.deinit();
|
||||
try std.json.stringify(self.theme_, .{ .whitespace = .indent_2 }, buf.writer());
|
||||
try root.write_theme(
|
||||
theme_name,
|
||||
buf.items,
|
||||
);
|
||||
}
|
||||
return try root.get_theme_file_name(theme_name);
|
||||
}
|
||||
|
||||
fn load_theme_file(allocator: std.mem.Allocator, theme_name: []const u8) !?std.json.Parsed(Widget.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(Widget.Theme) {
|
||||
_ = std.json.Scanner;
|
||||
const json_str = root.read_theme(allocator, theme_name) orelse return null;
|
||||
defer allocator.free(json_str);
|
||||
return try std.json.parseFromSlice(Widget.Theme, allocator, json_str, .{ .allocate = .alloc_always });
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue