Merge branch 'master' into helix-mode-selections
This commit is contained in:
commit
80c8795c3b
17 changed files with 429 additions and 134 deletions
|
@ -242,6 +242,7 @@ pub const Editor = struct {
|
|||
|
||||
file_path: ?[]const u8,
|
||||
buffer: ?*Buffer,
|
||||
buffer_manager: *Buffer.Manager,
|
||||
lsp_version: usize = 1,
|
||||
pause_undo: bool = false,
|
||||
|
||||
|
@ -382,7 +383,7 @@ pub const Editor = struct {
|
|||
self.clamp();
|
||||
}
|
||||
|
||||
fn init(self: *Self, allocator: Allocator, n: Plane) void {
|
||||
fn init(self: *Self, allocator: Allocator, n: Plane, buffer_manager: *Buffer.Manager) void {
|
||||
const logger = log.logger("editor");
|
||||
const frame_rate = tp.env.get().num("frame-rate");
|
||||
const indent_size = tui.current().config.indent_size;
|
||||
|
@ -396,6 +397,7 @@ pub const Editor = struct {
|
|||
.logger = logger,
|
||||
.file_path = null,
|
||||
.buffer = null,
|
||||
.buffer_manager = buffer_manager,
|
||||
.handlers = EventHandler.List.init(allocator),
|
||||
.animation_lag = get_animation_max_lag(),
|
||||
.animation_frame_rate = frame_rate,
|
||||
|
@ -417,7 +419,7 @@ pub const Editor = struct {
|
|||
self.matches.deinit();
|
||||
self.handlers.deinit();
|
||||
self.logger.deinit();
|
||||
if (self.buffer) |p| p.deinit();
|
||||
if (self.buffer) |p| self.buffer_manager.retire(p);
|
||||
if (self.case_data) |cd| cd.deinit();
|
||||
}
|
||||
|
||||
|
@ -483,28 +485,16 @@ pub const Editor = struct {
|
|||
self.view.cols = pos.w;
|
||||
}
|
||||
|
||||
pub fn is_dirty(self: *Self) bool {
|
||||
const b = self.buffer orelse return false;
|
||||
return b.is_dirty();
|
||||
}
|
||||
|
||||
fn open(self: *Self, file_path: []const u8) !void {
|
||||
var new_buf = try Buffer.create(self.allocator);
|
||||
errdefer new_buf.deinit();
|
||||
try new_buf.load_from_file_and_update(file_path);
|
||||
return self.open_buffer(file_path, new_buf);
|
||||
return self.open_buffer(file_path, try self.buffer_manager.open_file(file_path));
|
||||
}
|
||||
|
||||
fn open_scratch(self: *Self, file_path: []const u8, content: []const u8) !void {
|
||||
var new_buf = try Buffer.create(self.allocator);
|
||||
errdefer new_buf.deinit();
|
||||
try new_buf.load_from_string_and_update(file_path, content);
|
||||
new_buf.file_exists = true;
|
||||
return self.open_buffer(file_path, new_buf);
|
||||
return self.open_buffer(file_path, try self.buffer_manager.open_scratch(file_path, content));
|
||||
}
|
||||
|
||||
fn open_buffer(self: *Self, file_path: []const u8, new_buf: *Buffer) !void {
|
||||
errdefer new_buf.deinit();
|
||||
errdefer self.buffer_manager.retire(new_buf);
|
||||
self.cancel_all_selections();
|
||||
self.get_primary().reset();
|
||||
self.file_path = try self.allocator.dupe(u8, file_path);
|
||||
|
@ -543,17 +533,7 @@ pub const Editor = struct {
|
|||
}
|
||||
|
||||
fn close(self: *Self) !void {
|
||||
return self.close_internal(false);
|
||||
}
|
||||
|
||||
fn close_dirty(self: *Self) !void {
|
||||
return self.close_internal(true);
|
||||
}
|
||||
|
||||
fn close_internal(self: *Self, allow_dirty_close: bool) !void {
|
||||
const b = self.buffer orelse return error.Stop;
|
||||
if (!allow_dirty_close and b.is_dirty()) return tp.exit("unsaved changes");
|
||||
if (self.buffer) |b_mut| b_mut.deinit();
|
||||
if (self.buffer) |b_mut| self.buffer_manager.retire(b_mut);
|
||||
self.buffer = null;
|
||||
self.plane.erase();
|
||||
self.plane.home();
|
||||
|
@ -3805,7 +3785,8 @@ pub const Editor = struct {
|
|||
|
||||
pub fn close_file_without_saving(self: *Self, _: Context) Result {
|
||||
self.cancel_all_selections();
|
||||
try self.close_dirty();
|
||||
if (self.buffer) |buffer| buffer.reset_to_last_saved();
|
||||
try self.close();
|
||||
}
|
||||
pub const close_file_without_saving_meta = .{ .description = "Close file without saving" };
|
||||
|
||||
|
@ -4794,8 +4775,8 @@ pub const Editor = struct {
|
|||
pub const set_file_type_meta = .{ .arguments = &.{.string} };
|
||||
};
|
||||
|
||||
pub fn create(allocator: Allocator, parent: Widget) !Widget {
|
||||
return EditorWidget.create(allocator, parent);
|
||||
pub fn create(allocator: Allocator, parent: Widget, buffer_manager: *Buffer.Manager) !Widget {
|
||||
return EditorWidget.create(allocator, parent, buffer_manager);
|
||||
}
|
||||
|
||||
pub const EditorWidget = struct {
|
||||
|
@ -4817,10 +4798,10 @@ pub const EditorWidget = struct {
|
|||
const Self = @This();
|
||||
const Commands = command.Collection(Editor);
|
||||
|
||||
fn create(allocator: Allocator, parent: Widget) !Widget {
|
||||
fn create(allocator: Allocator, parent: Widget, buffer_manager: *Buffer.Manager) !Widget {
|
||||
const container = try WidgetList.createH(allocator, parent, "editor.container", .dynamic);
|
||||
const self: *Self = try allocator.create(Self);
|
||||
try self.init(allocator, container.widget());
|
||||
try self.init(allocator, container.widget(), buffer_manager);
|
||||
try self.commands.init(&self.editor);
|
||||
const editorWidget = Widget.to(self);
|
||||
try container.add(try editor_gutter.create(allocator, container.widget(), editorWidget, &self.editor));
|
||||
|
@ -4829,7 +4810,7 @@ pub const EditorWidget = struct {
|
|||
return container.widget();
|
||||
}
|
||||
|
||||
fn init(self: *Self, allocator: Allocator, parent: Widget) !void {
|
||||
fn init(self: *Self, allocator: Allocator, parent: Widget, buffer_manager: *Buffer.Manager) !void {
|
||||
var n = try Plane.init(&(Widget.Box{}).opts("editor"), parent.plane.*);
|
||||
errdefer n.deinit();
|
||||
|
||||
|
@ -4838,7 +4819,7 @@ pub const EditorWidget = struct {
|
|||
.plane = n,
|
||||
.editor = undefined,
|
||||
};
|
||||
self.editor.init(allocator, n);
|
||||
self.editor.init(allocator, n, buffer_manager);
|
||||
errdefer self.editor.deinit();
|
||||
try self.editor.push_cursor();
|
||||
}
|
||||
|
|
|
@ -245,6 +245,12 @@ const cmds = struct {
|
|||
const Ctx = command.Context;
|
||||
const Result = command.Result;
|
||||
|
||||
pub fn save_all(_: *Self, _: Ctx) Result {
|
||||
if (tui.get_buffer_manager()) |bm|
|
||||
bm.save_all() catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||
}
|
||||
pub const save_all_meta = .{ .description = "Save all changed files" };
|
||||
|
||||
pub fn home_menu_down(self: *Self, _: Ctx) Result {
|
||||
self.menu.select_down();
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ const build_options = @import("build_options");
|
|||
const Plane = @import("renderer").Plane;
|
||||
const input = @import("input");
|
||||
const command = @import("command");
|
||||
const BufferManager = @import("Buffer").Manager;
|
||||
|
||||
const tui = @import("tui.zig");
|
||||
const Box = @import("Box.zig");
|
||||
|
@ -47,6 +48,7 @@ active_view: ?usize = 0,
|
|||
panels: ?*WidgetList = null,
|
||||
last_match_text: ?[]const u8 = null,
|
||||
location_history: location_history,
|
||||
buffer_manager: BufferManager,
|
||||
file_stack: std.ArrayList([]const u8),
|
||||
find_in_files_state: enum { init, adding, done } = .done,
|
||||
file_list_type: FileListType = .find_in_files,
|
||||
|
@ -70,6 +72,7 @@ pub fn create(allocator: std.mem.Allocator) !Widget {
|
|||
.file_stack = std.ArrayList([]const u8).init(allocator),
|
||||
.views = undefined,
|
||||
.views_widget = undefined,
|
||||
.buffer_manager = BufferManager.init(allocator),
|
||||
};
|
||||
try self.commands.init(self);
|
||||
const w = Widget.to(self);
|
||||
|
@ -104,6 +107,7 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
|
|||
self.commands.deinit();
|
||||
self.widgets.deinit(allocator);
|
||||
self.floating_views.deinit();
|
||||
self.buffer_manager.deinit();
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
|
@ -237,15 +241,8 @@ fn toggle_view(self: *Self, view: anytype) !void {
|
|||
}
|
||||
|
||||
fn check_all_not_dirty(self: *const Self) command.Result {
|
||||
for (self.editors.items) |editor|
|
||||
if (editor.is_dirty())
|
||||
return tp.exit("unsaved changes");
|
||||
}
|
||||
|
||||
fn check_active_not_dirty(self: *const Self) command.Result {
|
||||
if (self.active_editor) |idx|
|
||||
if (self.editors.items[idx].is_dirty())
|
||||
return tp.exit("unsaved changes");
|
||||
if (self.buffer_manager.is_dirty())
|
||||
return tp.exit("unsaved changes");
|
||||
}
|
||||
|
||||
const cmds = struct {
|
||||
|
@ -296,6 +293,8 @@ const cmds = struct {
|
|||
self.clear_find_in_files_results(.diagnostics);
|
||||
if (self.file_list_type == .diagnostics and self.is_panel_view_showing(filelist_view))
|
||||
try self.toggle_panel_view(filelist_view, false);
|
||||
self.buffer_manager.deinit();
|
||||
self.buffer_manager = BufferManager.init(self.allocator);
|
||||
try project_manager.open(project_dir);
|
||||
const project = tp.env.get().str("project");
|
||||
tui.current().rdr.set_terminal_working_directory(project);
|
||||
|
@ -352,7 +351,6 @@ const cmds = struct {
|
|||
|
||||
if (!same_file) {
|
||||
if (self.get_active_editor()) |editor| {
|
||||
try self.check_active_not_dirty();
|
||||
editor.send_editor_jump_source() catch {};
|
||||
}
|
||||
try self.create_editor();
|
||||
|
@ -395,7 +393,6 @@ const cmds = struct {
|
|||
pub const open_gui_config_meta = .{ .description = "Edit gui configuration file" };
|
||||
|
||||
pub fn create_scratch_buffer(self: *Self, ctx: Ctx) Result {
|
||||
try self.check_all_not_dirty();
|
||||
tui.reset_drag_context();
|
||||
try self.create_editor();
|
||||
try command.executeName("open_scratch_buffer", ctx);
|
||||
|
@ -863,7 +860,7 @@ fn create_editor(self: *Self) !void {
|
|||
if (self.get_active_file_path()) |file_path| self.push_file_stack(file_path) catch {};
|
||||
try self.delete_active_view();
|
||||
command.executeName("enter_mode_default", .{}) catch {};
|
||||
var editor_widget = try ed.create(self.allocator, Widget.to(self));
|
||||
var editor_widget = try ed.create(self.allocator, Widget.to(self), &self.buffer_manager);
|
||||
errdefer editor_widget.deinit(self.allocator);
|
||||
const editor = editor_widget.get("editor") orelse @panic("mainview editor not found");
|
||||
if (self.top_bar) |bar| editor.subscribe(EventHandler.to_unowned(bar)) catch @panic("subscribe unsupported");
|
||||
|
|
|
@ -11,7 +11,6 @@ pub const create = Type.create;
|
|||
|
||||
pub fn load_entries(self: *Type) error{ Exit, OutOfMemory }!void {
|
||||
const editor = tui.get_active_editor() orelse return;
|
||||
if (editor.is_dirty()) return tp.exit("unsaved changes");
|
||||
if (editor.file_path) |old_path|
|
||||
if (std.mem.lastIndexOf(u8, old_path, "/")) |pos|
|
||||
try self.file_path.appendSlice(old_path[0 .. pos + 1]);
|
||||
|
|
49
src/tui/mode/overlay/buffer_palette.zig
Normal file
49
src/tui/mode/overlay/buffer_palette.zig
Normal file
|
@ -0,0 +1,49 @@
|
|||
const std = @import("std");
|
||||
const cbor = @import("cbor");
|
||||
const tp = @import("thespian");
|
||||
const root = @import("root");
|
||||
const command = @import("command");
|
||||
|
||||
const tui = @import("../../tui.zig");
|
||||
pub const Type = @import("palette.zig").Create(@This());
|
||||
|
||||
pub const label = "Switch buffers";
|
||||
pub const name = " buffer";
|
||||
pub const description = "buffer";
|
||||
const dirty_indicator = "";
|
||||
|
||||
pub const Entry = struct {
|
||||
label: []const u8,
|
||||
hint: []const u8,
|
||||
};
|
||||
|
||||
pub fn load_entries(palette: *Type) !usize {
|
||||
const buffer_manager = tui.get_buffer_manager() orelse return 0;
|
||||
const buffers = try buffer_manager.list_most_recently_used(palette.allocator);
|
||||
defer palette.allocator.free(buffers);
|
||||
for (buffers) |buffer| {
|
||||
const hint = if (buffer.is_dirty()) dirty_indicator else "";
|
||||
(try palette.entries.addOne()).* = .{ .label = buffer.file_path, .hint = hint };
|
||||
}
|
||||
return if (palette.entries.items.len == 0) label.len else 2;
|
||||
}
|
||||
|
||||
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.hint);
|
||||
try cbor.writeValue(writer, matches orelse &[_]usize{});
|
||||
try palette.menu.add_item_with_handler(value.items, select);
|
||||
palette.items += 1;
|
||||
}
|
||||
|
||||
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
|
||||
var file_path: []const u8 = undefined;
|
||||
var iter = button.opts.label;
|
||||
if (!(cbor.matchString(&iter, &file_path) catch false)) return;
|
||||
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err("navigate", e);
|
||||
tp.self_pid().send(.{ "cmd", "navigate", .{} }) catch |e| menu.*.opts.ctx.logger.err("navigate", e);
|
||||
tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_path } }) catch |e| menu.*.opts.ctx.logger.err("navigate", e);
|
||||
}
|
|
@ -10,6 +10,7 @@ const keybind = @import("keybind");
|
|||
const project_manager = @import("project_manager");
|
||||
const command = @import("command");
|
||||
const EventHandler = @import("EventHandler");
|
||||
const BufferManager = @import("Buffer").Manager;
|
||||
|
||||
const tui = @import("../../tui.zig");
|
||||
const MessageFilter = @import("../../MessageFilter.zig");
|
||||
|
@ -34,6 +35,7 @@ need_reset: bool = false,
|
|||
need_select_first: bool = true,
|
||||
longest: usize = 0,
|
||||
commands: Commands = undefined,
|
||||
buffer_manager: ?*BufferManager,
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator) !tui.Mode {
|
||||
const mv = tui.current().mainview.dynamic_cast(mainview) orelse return error.NotFound;
|
||||
|
@ -51,6 +53,7 @@ pub fn create(allocator: std.mem.Allocator) !tui.Mode {
|
|||
.ctx = self,
|
||||
.label = "Search files by name",
|
||||
}))).dynamic_cast(InputBox.State(*Self)) orelse unreachable,
|
||||
.buffer_manager = tui.get_buffer_manager(),
|
||||
};
|
||||
try self.commands.init(self);
|
||||
try tui.current().message_filters.add(MessageFilter.bind(self, receive_project_manager));
|
||||
|
@ -93,7 +96,7 @@ inline fn max_menu_width() usize {
|
|||
return @max(15, width - (width / 5));
|
||||
}
|
||||
|
||||
fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool {
|
||||
fn on_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), 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_keybind = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_base;
|
||||
|
@ -110,7 +113,8 @@ fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *c
|
|||
if (!(cbor.matchString(&iter, &file_path) catch false))
|
||||
file_path = "#ERROR#";
|
||||
button.plane.set_style(style_keybind);
|
||||
const pointer = if (selected) "⏵" else " ";
|
||||
const dirty = if (self.buffer_manager) |bm| if (bm.is_buffer_dirty(file_path)) "" else " " else " ";
|
||||
const pointer = if (selected) "⏵" else dirty;
|
||||
_ = button.plane.print("{s}", .{pointer}) catch {};
|
||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
var removed_prefix: usize = 0;
|
||||
|
|
|
@ -56,6 +56,7 @@ mouse_idle_timer: ?tp.Cancellable = null,
|
|||
default_cursor: keybind.CursorShape = .default,
|
||||
fontface: []const u8 = "",
|
||||
fontfaces: ?std.ArrayList([]const u8) = null,
|
||||
enable_mouse_idle_timer: bool = false,
|
||||
|
||||
const keepalive = std.time.us_per_day * 365; // one year
|
||||
const idle_frames = 0;
|
||||
|
@ -222,6 +223,7 @@ fn listen_sigwinch(self: *Self) tp.result {
|
|||
}
|
||||
|
||||
fn update_mouse_idle_timer(self: *Self) void {
|
||||
if (!self.enable_mouse_idle_timer) return;
|
||||
const delay = std.time.us_per_ms * @as(u64, mouse_idle_time_milliseconds);
|
||||
if (self.mouse_idle_timer) |*t| {
|
||||
t.cancel() catch {};
|
||||
|
@ -309,6 +311,8 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
|
|||
|
||||
if (try m.match(.{"resize"})) {
|
||||
self.resize();
|
||||
const box = self.screen();
|
||||
message("{d}x{d}", .{ box.w, box.h });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -811,6 +815,11 @@ const cmds = struct {
|
|||
}
|
||||
pub const open_recent_project_meta = .{ .description = "Open recent project" };
|
||||
|
||||
pub fn switch_buffers(self: *Self, _: Ctx) Result {
|
||||
return self.enter_overlay_mode(@import("mode/overlay/buffer_palette.zig").Type);
|
||||
}
|
||||
pub const switch_buffers_meta = .{ .description = "Switch buffers" };
|
||||
|
||||
pub fn change_theme(self: *Self, _: Ctx) Result {
|
||||
return self.enter_overlay_mode(@import("mode/overlay/theme_palette.zig").Type);
|
||||
}
|
||||
|
@ -987,6 +996,10 @@ pub fn get_active_selection(allocator: std.mem.Allocator) ?[]u8 {
|
|||
return editor.get_selection(sel, allocator) catch null;
|
||||
}
|
||||
|
||||
pub fn get_buffer_manager() ?*@import("Buffer").Manager {
|
||||
return if (current().mainview.dynamic_cast(mainview)) |mv_| &mv_.buffer_manager else null;
|
||||
}
|
||||
|
||||
fn context_check() void {
|
||||
if (instance_ == null) @panic("tui call out of context");
|
||||
}
|
||||
|
@ -1176,3 +1189,8 @@ pub fn is_cursor_beam(self: *Self) bool {
|
|||
pub fn get_selection_style(self: *Self) @import("Buffer").Selection.Style {
|
||||
return if (self.input_mode) |mode| mode.selection_style else .normal;
|
||||
}
|
||||
|
||||
pub fn message(comptime fmt: anytype, args: anytype) void {
|
||||
var buf: [256]u8 = undefined;
|
||||
tp.self_pid().send(.{ "message", std.fmt.bufPrint(&buf, fmt, args) catch @panic("too large") }) catch {};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue