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",
|
.hash = "thespian-0.0.1-owFOjnUTBgBUlBtQ-SbN_6S7bXE6e2mKmCgk4A-RGBMA",
|
||||||
},
|
},
|
||||||
.themes = .{
|
.themes = .{
|
||||||
.url = "https://github.com/neurocyte/flow-themes/releases/download/master-59bf204551bcb238faddd06779063570e7e6d431/flow-themes.tar.gz",
|
.url = "https://github.com/neurocyte/flow-themes/releases/download/master-ac2e3fe2df3419b71276f86fa9c45fd39d668f23/flow-themes.tar.gz",
|
||||||
.hash = "N-V-__8AAM9UFwCaITo5LqgOpcfd4SXnFhuwJ4rEuZ253yt6",
|
.hash = "N-V-__8AAEtaFwAjAHCmWHRCrBxL7uSG4hQiIsSgS32Y67K6",
|
||||||
},
|
},
|
||||||
.fuzzig = .{
|
.fuzzig = .{
|
||||||
.url = "https://github.com/fjebaker/fuzzig/archive/44c04733c7c0fee3db83672aaaaf4ed03e943156.tar.gz",
|
.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();
|
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 {
|
pub fn get_config_dir() ![]const u8 {
|
||||||
return get_app_config_dir(application_name);
|
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;
|
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 {};
|
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;
|
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 });
|
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 {
|
fn restart() noreturn {
|
||||||
var executable: [:0]const u8 = std.mem.span(std.os.argv[0]);
|
var executable: [:0]const u8 = std.mem.span(std.os.argv[0]);
|
||||||
var is_basename = true;
|
var is_basename = true;
|
||||||
|
|
|
@ -44,6 +44,7 @@ commands: Commands = undefined,
|
||||||
logger: log.Logger,
|
logger: log.Logger,
|
||||||
drag_source: ?*Widget = null,
|
drag_source: ?*Widget = null,
|
||||||
theme_: Widget.Theme,
|
theme_: Widget.Theme,
|
||||||
|
parsed_theme: ?std.json.Parsed(Widget.Theme),
|
||||||
idle_frame_count: usize = 0,
|
idle_frame_count: usize = 0,
|
||||||
unrendered_input_events_count: usize = 0,
|
unrendered_input_events_count: usize = 0,
|
||||||
init_timer: ?tp.timeout,
|
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);
|
var conf, const conf_bufs = root.read_config(@import("config"), allocator);
|
||||||
defer root.free_config(allocator, conf_bufs);
|
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.theme = theme_.name;
|
||||||
conf.whitespace_mode = try allocator.dupe(u8, conf.whitespace_mode);
|
conf.whitespace_mode = try allocator.dupe(u8, conf.whitespace_mode);
|
||||||
conf.input_mode = try allocator.dupe(u8, conf.input_mode);
|
conf.input_mode = try allocator.dupe(u8, conf.input_mode);
|
||||||
|
@ -135,6 +136,7 @@ fn init(allocator: Allocator) InitError!*Self {
|
||||||
.theme_ = theme_,
|
.theme_ = theme_,
|
||||||
.no_sleep = tp.env.get().is("no-sleep"),
|
.no_sleep = tp.env.get().is("no-sleep"),
|
||||||
.query_cache_ = try syntax.QueryCache.create(allocator, .{}),
|
.query_cache_ = try syntax.QueryCache.create(allocator, .{}),
|
||||||
|
.parsed_theme = parsed_theme,
|
||||||
};
|
};
|
||||||
instance_ = self;
|
instance_ = self;
|
||||||
defer instance_ = null;
|
defer instance_ = null;
|
||||||
|
@ -687,6 +689,19 @@ fn refresh_input_mode(self: *Self) command.Result {
|
||||||
self.input_mode_ = new_mode;
|
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 {
|
const cmds = struct {
|
||||||
pub const Target = Self;
|
pub const Target = Self;
|
||||||
const Ctx = command.Context;
|
const Ctx = command.Context;
|
||||||
|
@ -709,32 +724,19 @@ const cmds = struct {
|
||||||
var name: []const u8 = undefined;
|
var name: []const u8 = undefined;
|
||||||
if (!try ctx.args.match(.{tp.extract(&name)}))
|
if (!try ctx.args.match(.{tp.extract(&name)}))
|
||||||
return tp.exit_error(error.InvalidSetThemeArgument, null);
|
return tp.exit_error(error.InvalidSetThemeArgument, null);
|
||||||
self.theme_ = get_theme_by_name(name) orelse {
|
return self.set_theme_by_name(name);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
pub const set_theme_meta: Meta = .{ .arguments = &.{.string} };
|
pub const set_theme_meta: Meta = .{ .arguments = &.{.string} };
|
||||||
|
|
||||||
pub fn theme_next(self: *Self, _: Ctx) Result {
|
pub fn theme_next(self: *Self, _: Ctx) Result {
|
||||||
self.theme_ = get_next_theme_by_name(self.theme_.name);
|
const name = get_next_theme_by_name(self.theme_.name);
|
||||||
self.config_.theme = self.theme_.name;
|
return self.set_theme_by_name(name);
|
||||||
self.set_terminal_style();
|
|
||||||
self.logger.print("theme: {s}", .{self.theme_.description});
|
|
||||||
try save_config();
|
|
||||||
}
|
}
|
||||||
pub const theme_next_meta: Meta = .{ .description = "Next color theme" };
|
pub const theme_next_meta: Meta = .{ .description = "Next color theme" };
|
||||||
|
|
||||||
pub fn theme_prev(self: *Self, _: Ctx) Result {
|
pub fn theme_prev(self: *Self, _: Ctx) Result {
|
||||||
self.theme_ = get_prev_theme_by_name(self.theme_.name);
|
const name = get_prev_theme_by_name(self.theme_.name);
|
||||||
self.config_.theme = self.theme_.name;
|
return self.set_theme_by_name(name);
|
||||||
self.set_terminal_style();
|
|
||||||
self.logger.print("theme: {s}", .{self.theme_.description});
|
|
||||||
try save_config();
|
|
||||||
}
|
}
|
||||||
pub const theme_prev_meta: Meta = .{ .description = "Previous color theme" };
|
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 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 {
|
pub fn run_async(self: *Self, ctx: Ctx) Result {
|
||||||
var iter = ctx.args.buf;
|
var iter = ctx.args.buf;
|
||||||
var len = try cbor.decodeArrayHeader(&iter);
|
var len = try cbor.decodeArrayHeader(&iter);
|
||||||
|
@ -1173,33 +1182,38 @@ pub fn theme() *const Widget.Theme {
|
||||||
return ¤t().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_| {
|
for (Widget.themes) |theme_| {
|
||||||
if (std.mem.eql(u8, theme_.name, name))
|
if (std.mem.eql(u8, theme_.name, name))
|
||||||
return theme_;
|
return .{ theme_, null };
|
||||||
}
|
}
|
||||||
return 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;
|
var next = false;
|
||||||
for (Widget.themes) |theme_| {
|
for (Widget.themes) |theme_| {
|
||||||
if (next)
|
if (next)
|
||||||
return theme_;
|
return theme_.name;
|
||||||
if (std.mem.eql(u8, theme_.name, name))
|
if (std.mem.eql(u8, theme_.name, name))
|
||||||
next = true;
|
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;
|
var prev: ?Widget.Theme = null;
|
||||||
for (Widget.themes) |theme_| {
|
for (Widget.themes) |theme_| {
|
||||||
if (std.mem.eql(u8, theme_.name, name))
|
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_;
|
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 {
|
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);
|
cell.set_style(theme_.editor_match);
|
||||||
_ = self.putc(&cell) catch {};
|
_ = 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