feat: add Customise theme command

This commit is contained in:
CJ van den Berg 2025-03-26 23:05:49 +01:00
parent c3cda5b7fe
commit 58082ed2a1
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
3 changed files with 125 additions and 29 deletions

View file

@ -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 &current().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 });
}