feat: add palette for changing the current file type

This commit is contained in:
CJ van den Berg 2024-12-10 20:10:36 +01:00
parent c3021de372
commit 9fba9eba3c
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
5 changed files with 187 additions and 1 deletions

View file

@ -19,6 +19,7 @@
["ctrl+c", "copy"],
["ctrl+v", "system_paste"],
["ctrl+u", "pop_cursor"],
["ctrl+k>m", "change_file_type"],
["ctrl+k>ctrl+u", "delete_to_begin"],
["ctrl+k>ctrl+k", "delete_to_end"],
["ctrl+k>ctrl+d", "move_cursor_next_match"],

View file

@ -4282,6 +4282,39 @@ pub const Editor = struct {
self.syntax_report_timing = !self.syntax_report_timing;
}
pub const toggle_syntax_timing_meta = .{ .description = "Toggle tree-sitter timing reports" };
pub fn set_file_type(self: *Self, ctx: Context) Result {
var file_type: []const u8 = undefined;
if (!try ctx.args.match(.{tp.extract(&file_type)}))
return error.InvalidArgument;
if (self.syntax) |syn| syn.destroy();
self.syntax_last_rendered_root = null;
self.syntax_refresh_full = true;
self.syntax_incremental_reparse = false;
self.syntax = syntax: {
var content = std.ArrayList(u8).init(self.allocator);
defer content.deinit();
const root = try self.buf_root();
try root.store(content.writer(), try self.buf_eol_mode());
const syn = syntax.create_file_type(self.allocator, file_type) catch null;
if (syn) |syn_| if (self.file_path) |file_path|
project_manager.did_open(file_path, syn_.file_type, self.lsp_version, try content.toOwnedSlice()) catch |e|
self.logger.print("project_manager.did_open failed: {any}", .{e});
break :syntax syn;
};
self.syntax_no_render = tp.env.get().is("no-syntax");
self.syntax_report_timing = tp.env.get().is("syntax-report-timing");
const ftn = if (self.syntax) |syn| syn.file_type.name else "text";
const fti = if (self.syntax) |syn| syn.file_type.icon else "🖹";
const ftc = if (self.syntax) |syn| syn.file_type.color else 0x000000;
const file_exists = if (self.buffer) |b| b.file_exists else false;
try self.send_editor_open(self.file_path orelse "", file_exists, ftn, fti, ftc);
self.logger.print("file type {s}", .{file_type});
}
pub const set_file_type_meta = .{ .arguments = &.{.string} };
};
pub fn create(allocator: Allocator, parent: Widget) !Widget {

View file

@ -0,0 +1,147 @@
const std = @import("std");
const cbor = @import("cbor");
const tp = @import("thespian");
const syntax = @import("syntax");
const Widget = @import("../../Widget.zig");
const tui = @import("../../tui.zig");
const mainview = @import("../../mainview.zig");
pub const Type = @import("palette.zig").Create(@This());
pub const label = "Select file type";
pub const name = " file type";
pub const description = "file type";
pub const Entry = struct {
label: []const u8,
name: []const u8,
icon: []const u8,
color: u24,
};
pub const Match = struct {
name: []const u8,
score: i32,
matches: []const usize,
};
var previous_file_type: ?[]const u8 = null;
pub fn load_entries(palette: *Type) !usize {
var longest_hint: usize = 0;
var idx: usize = 0;
previous_file_type = blk: {
if (tui.current().mainview.dynamic_cast(mainview)) |mv_| if (mv_.get_editor()) |editor| {
if (editor.syntax) |editor_syntax| break :blk editor_syntax.file_type.name;
};
break :blk null;
};
for (syntax.FileType.file_types) |file_type| {
idx += 1;
(try palette.entries.addOne()).* = .{
.label = file_type.description,
.name = file_type.name,
.icon = file_type.icon,
.color = file_type.color,
};
if (previous_file_type) |file_type_name| if (std.mem.eql(u8, file_type.name, file_type_name)) {
palette.initial_selected = idx;
};
longest_hint = @max(longest_hint, file_type.name.len);
}
return longest_hint;
}
pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !void {
var value = std.ArrayList(u8).init(palette.allocator);
defer value.deinit();
const writer = value.writer();
try cbor.writeValue(writer, entry.label);
try cbor.writeValue(writer, entry.icon);
try cbor.writeValue(writer, entry.color);
try cbor.writeValue(writer, entry.name);
try cbor.writeValue(writer, matches orelse &[_]usize{});
try palette.menu.add_item_with_handler(value.items, select);
palette.items += 1;
}
pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
const style_base = theme.editor_widget;
const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget;
const style_hint = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_label;
button.plane.set_base_style(style_base);
button.plane.erase();
button.plane.home();
button.plane.set_style(style_label);
if (button.active or button.hover or selected) {
_ = button.plane.fill_width(" ", .{}) catch {};
button.plane.home();
}
button.plane.set_style(style_hint);
const pointer = if (selected) "" else " ";
_ = button.plane.print("{s}", .{pointer}) catch {};
var iter = button.opts.label;
var description_: []const u8 = undefined;
var icon: []const u8 = undefined;
var color: u24 = undefined;
if (!(cbor.matchString(&iter, &description_) catch false)) @panic("invalid file_type description");
if (!(cbor.matchString(&iter, &icon) catch false)) @panic("invalid file_type icon");
if (!(cbor.matchInt(u24, &iter, &color) catch false)) @panic("invalid file_type color");
render_file_icon(button, icon, color);
button.plane.set_style(style_label);
_ = button.plane.print(" {s} ", .{description_}) catch {};
var name_: []const u8 = undefined;
if (!(cbor.matchString(&iter, &name_) catch false))
name_ = "";
button.plane.set_style(style_hint);
_ = button.plane.print_aligned_right(0, "{s} ", .{name_}) catch {};
var index: usize = 0;
var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) {
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
render_match_cell(button, 0, index + 4, theme) catch break;
} else break;
}
return false;
}
fn render_match_cell(button: *Type.ButtonState, y: usize, x: usize, theme: *const Widget.Theme) !void {
button.plane.cursor_move_yx(@intCast(y), @intCast(x)) catch return;
var cell = button.plane.cell_init();
_ = button.plane.at_cursor_cell(&cell) catch return;
cell.set_style(theme.editor_match);
_ = button.plane.putc(&cell) catch {};
}
fn render_file_icon(button: *Type.ButtonState, icon: []const u8, color: u24) void {
var cell = button.plane.cell_init();
_ = button.plane.at_cursor_cell(&cell) catch return;
if (!(color == 0xFFFFFF or color == 0x000000 or color == 0x000001)) {
cell.set_fg_rgb(@intCast(color)) catch {};
}
_ = button.plane.cell_load(&cell, icon) catch {};
_ = button.plane.putc(&cell) catch {};
button.plane.cursor_move_rel(0, 1) catch {};
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
var description_: []const u8 = undefined;
var icon: []const u8 = undefined;
var color: u24 = undefined;
var name_: []const u8 = undefined;
var iter = button.opts.label;
if (!(cbor.matchString(&iter, &description_) catch false)) return;
if (!(cbor.matchString(&iter, &icon) catch false)) return;
if (!(cbor.matchInt(u24, &iter, &color) catch false)) return;
if (!(cbor.matchString(&iter, &name_) catch false)) return;
if (previous_file_type) |prev| if (std.mem.eql(u8, prev, name_))
return;
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err("file_type_palette", e);
tp.self_pid().send(.{ "cmd", "set_file_type", .{name_} }) catch |e| menu.*.opts.ctx.logger.err("file_type_palette", e);
}

View file

@ -54,7 +54,7 @@ pub fn Create(options: type) type {
.modal = try ModalBackground.create(*Self, allocator, tui.current().mainview, .{ .ctx = self }),
.menu = try Menu.create(*Self, allocator, tui.current().mainview, .{
.ctx = self,
.on_render = on_render_menu,
.on_render = if (@hasDecl(options, "on_render_menu")) options.on_render_menu else on_render_menu,
.on_resize = on_resize_menu,
.on_scroll = EventHandler.bind(self, Self.on_scroll),
.on_click4 = mouse_click_button4,

View file

@ -759,6 +759,11 @@ const cmds = struct {
}
pub const change_theme_meta = .{ .description = "Select color theme" };
pub fn change_file_type(self: *Self, _: Ctx) Result {
return self.enter_overlay_mode(@import("mode/overlay/file_type_palette.zig").Type);
}
pub const change_file_type_meta = .{ .description = "Change file type" };
pub fn exit_overlay_mode(self: *Self, _: Ctx) Result {
if (self.input_mode_outer == null) return;
if (self.input_mode) |*mode| mode.deinit();