feat: add palette for changing the current file type
This commit is contained in:
parent
c3021de372
commit
9fba9eba3c
5 changed files with 187 additions and 1 deletions
|
@ -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"],
|
||||
|
|
|
@ -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 {
|
||||
|
|
147
src/tui/mode/overlay/file_type_palette.zig
Normal file
147
src/tui/mode/overlay/file_type_palette.zig
Normal 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);
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Reference in a new issue