mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Merge branch 'main' into push-rrywsxrqksul
This commit is contained in:
@ -59,6 +59,11 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
selector: #selector(ghosttyConfigDidChange(_:)),
|
selector: #selector(ghosttyConfigDidChange(_:)),
|
||||||
name: .ghosttyConfigDidChange,
|
name: .ghosttyConfigDidChange,
|
||||||
object: nil)
|
object: nil)
|
||||||
|
center.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(onNewTab),
|
||||||
|
name: Ghostty.Notification.ghosttyNewTab,
|
||||||
|
object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -437,6 +442,16 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func showNoNewTabAlert() {
|
||||||
|
guard let window else { return }
|
||||||
|
let alert = NSAlert()
|
||||||
|
alert.messageText = "Cannot Create New Tab"
|
||||||
|
alert.informativeText = "Tabs aren't supported in the Quick Terminal."
|
||||||
|
alert.addButton(withTitle: "OK")
|
||||||
|
alert.alertStyle = .warning
|
||||||
|
alert.beginSheetModal(for: window)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: First Responder
|
// MARK: First Responder
|
||||||
|
|
||||||
@IBAction override func closeWindow(_ sender: Any) {
|
@IBAction override func closeWindow(_ sender: Any) {
|
||||||
@ -445,13 +460,7 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func newTab(_ sender: Any?) {
|
@IBAction func newTab(_ sender: Any?) {
|
||||||
guard let window else { return }
|
showNoNewTabAlert()
|
||||||
let alert = NSAlert()
|
|
||||||
alert.messageText = "Cannot Create New Tab"
|
|
||||||
alert.informativeText = "Tabs aren't supported in the Quick Terminal."
|
|
||||||
alert.addButton(withTitle: "OK")
|
|
||||||
alert.alertStyle = .warning
|
|
||||||
alert.beginSheetModal(for: window)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func toggleGhosttyFullScreen(_ sender: Any) {
|
@IBAction func toggleGhosttyFullScreen(_ sender: Any) {
|
||||||
@ -492,6 +501,14 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
syncAppearance()
|
syncAppearance()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func onNewTab(notification: SwiftUI.Notification) {
|
||||||
|
guard let surfaceView = notification.object as? Ghostty.SurfaceView else { return }
|
||||||
|
guard let window = surfaceView.window else { return }
|
||||||
|
guard window.windowController is QuickTerminalController else { return }
|
||||||
|
// Tabs aren't supported with Quick Terminals or derivatives
|
||||||
|
showNoNewTabAlert()
|
||||||
|
}
|
||||||
|
|
||||||
private struct DerivedConfig {
|
private struct DerivedConfig {
|
||||||
let quickTerminalScreen: QuickTerminalScreen
|
let quickTerminalScreen: QuickTerminalScreen
|
||||||
let quickTerminalAnimationDuration: Double
|
let quickTerminalAnimationDuration: Double
|
||||||
|
@ -125,6 +125,9 @@ class TerminalManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func newTab(to parent: NSWindow, withBaseConfig base: Ghostty.SurfaceConfiguration?) {
|
private func newTab(to parent: NSWindow, withBaseConfig base: Ghostty.SurfaceConfiguration?) {
|
||||||
|
// Making sure that we're dealing with a TerminalController
|
||||||
|
guard parent.windowController is TerminalController else { return }
|
||||||
|
|
||||||
// If our parent is in non-native fullscreen, then new tabs do not work.
|
// If our parent is in non-native fullscreen, then new tabs do not work.
|
||||||
// See: https://github.com/mitchellh/ghostty/issues/392
|
// See: https://github.com/mitchellh/ghostty/issues/392
|
||||||
if let controller = parent.windowController as? TerminalController,
|
if let controller = parent.windowController as? TerminalController,
|
||||||
|
@ -7,13 +7,15 @@ const Window = @This();
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const gtk = @import("gtk");
|
|
||||||
const gobject = @import("gobject");
|
|
||||||
|
|
||||||
const build_config = @import("../../build_config.zig");
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
const gio = @import("gio");
|
||||||
|
const glib = @import("glib");
|
||||||
|
const gobject = @import("gobject");
|
||||||
|
const gtk = @import("gtk");
|
||||||
|
|
||||||
|
const build_config = @import("../../build_config.zig");
|
||||||
const configpkg = @import("../../config.zig");
|
const configpkg = @import("../../config.zig");
|
||||||
const font = @import("../../font/main.zig");
|
const font = @import("../../font/main.zig");
|
||||||
const input = @import("../../input.zig");
|
const input = @import("../../input.zig");
|
||||||
@ -496,36 +498,38 @@ fn toggleCssClass(
|
|||||||
/// menus and such. The menu is defined in App.zig but the action is defined
|
/// menus and such. The menu is defined in App.zig but the action is defined
|
||||||
/// here. The string name binds them.
|
/// here. The string name binds them.
|
||||||
fn initActions(self: *Window) void {
|
fn initActions(self: *Window) void {
|
||||||
|
// FIXME: when rest of file is converted to gobject
|
||||||
|
const window: *gtk.ApplicationWindow = @ptrCast(@alignCast(self.window));
|
||||||
|
const action_map = window.as(gio.ActionMap);
|
||||||
const actions = .{
|
const actions = .{
|
||||||
.{ "about", >kActionAbout },
|
.{ "about", gtkActionAbout },
|
||||||
.{ "close", >kActionClose },
|
.{ "close", gtkActionClose },
|
||||||
.{ "new-window", >kActionNewWindow },
|
.{ "new-window", gtkActionNewWindow },
|
||||||
.{ "new-tab", >kActionNewTab },
|
.{ "new-tab", gtkActionNewTab },
|
||||||
.{ "close-tab", >kActionCloseTab },
|
.{ "close-tab", gtkActionCloseTab },
|
||||||
.{ "split-right", >kActionSplitRight },
|
.{ "split-right", gtkActionSplitRight },
|
||||||
.{ "split-down", >kActionSplitDown },
|
.{ "split-down", gtkActionSplitDown },
|
||||||
.{ "split-left", >kActionSplitLeft },
|
.{ "split-left", gtkActionSplitLeft },
|
||||||
.{ "split-up", >kActionSplitUp },
|
.{ "split-up", gtkActionSplitUp },
|
||||||
.{ "toggle-inspector", >kActionToggleInspector },
|
.{ "toggle-inspector", gtkActionToggleInspector },
|
||||||
.{ "copy", >kActionCopy },
|
.{ "copy", gtkActionCopy },
|
||||||
.{ "paste", >kActionPaste },
|
.{ "paste", gtkActionPaste },
|
||||||
.{ "reset", >kActionReset },
|
.{ "reset", gtkActionReset },
|
||||||
.{ "clear", >kActionClear },
|
.{ "clear", gtkActionClear },
|
||||||
.{ "prompt-title", >kActionPromptTitle },
|
.{ "prompt-title", gtkActionPromptTitle },
|
||||||
};
|
};
|
||||||
|
|
||||||
inline for (actions) |entry| {
|
inline for (actions) |entry| {
|
||||||
const action = c.g_simple_action_new(entry[0], null);
|
const action = gio.SimpleAction.new(entry[0], null);
|
||||||
defer c.g_object_unref(action);
|
defer action.unref();
|
||||||
_ = c.g_signal_connect_data(
|
_ = gio.SimpleAction.signals.activate.connect(
|
||||||
action,
|
action,
|
||||||
"activate",
|
*Window,
|
||||||
c.G_CALLBACK(entry[1]),
|
entry[1],
|
||||||
self,
|
self,
|
||||||
null,
|
.{},
|
||||||
c.G_CONNECT_DEFAULT,
|
|
||||||
);
|
);
|
||||||
c.g_action_map_add_action(@ptrCast(self.window), @ptrCast(action));
|
action_map.addAction(action.as(gio.Action));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -900,12 +904,10 @@ fn gtkKeyPressed(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionAbout(
|
fn gtkActionAbout(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
|
|
||||||
const name = "Ghostty";
|
const name = "Ghostty";
|
||||||
const icon = "com.mitchellh.ghostty";
|
const icon = "com.mitchellh.ghostty";
|
||||||
const website = "https://ghostty.org";
|
const website = "https://ghostty.org";
|
||||||
@ -946,20 +948,18 @@ fn gtkActionAbout(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionClose(
|
fn gtkActionClose(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
c.gtk_window_destroy(self.window);
|
c.gtk_window_destroy(self.window);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionNewWindow(
|
fn gtkActionNewWindow(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
const surface = self.actionSurface() orelse return;
|
const surface = self.actionSurface() orelse return;
|
||||||
_ = surface.performBindingAction(.{ .new_window = {} }) catch |err| {
|
_ = surface.performBindingAction(.{ .new_window = {} }) catch |err| {
|
||||||
log.warn("error performing binding action error={}", .{err});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
@ -968,20 +968,19 @@ fn gtkActionNewWindow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionNewTab(
|
fn gtkActionNewTab(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
// We can use undefined because the button is not used.
|
// We can use undefined because the button is not used.
|
||||||
gtkTabNewClick(undefined, ud);
|
gtkTabNewClick(undefined, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionCloseTab(
|
fn gtkActionCloseTab(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
const surface = self.actionSurface() orelse return;
|
const surface = self.actionSurface() orelse return;
|
||||||
_ = surface.performBindingAction(.{ .close_tab = {} }) catch |err| {
|
_ = surface.performBindingAction(.{ .close_tab = {} }) catch |err| {
|
||||||
log.warn("error performing binding action error={}", .{err});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
@ -990,11 +989,10 @@ fn gtkActionCloseTab(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionSplitRight(
|
fn gtkActionSplitRight(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
const surface = self.actionSurface() orelse return;
|
const surface = self.actionSurface() orelse return;
|
||||||
_ = surface.performBindingAction(.{ .new_split = .right }) catch |err| {
|
_ = surface.performBindingAction(.{ .new_split = .right }) catch |err| {
|
||||||
log.warn("error performing binding action error={}", .{err});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
@ -1003,11 +1001,10 @@ fn gtkActionSplitRight(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionSplitDown(
|
fn gtkActionSplitDown(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
const surface = self.actionSurface() orelse return;
|
const surface = self.actionSurface() orelse return;
|
||||||
_ = surface.performBindingAction(.{ .new_split = .down }) catch |err| {
|
_ = surface.performBindingAction(.{ .new_split = .down }) catch |err| {
|
||||||
log.warn("error performing binding action error={}", .{err});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
@ -1016,11 +1013,10 @@ fn gtkActionSplitDown(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionSplitLeft(
|
fn gtkActionSplitLeft(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
const surface = self.actionSurface() orelse return;
|
const surface = self.actionSurface() orelse return;
|
||||||
_ = surface.performBindingAction(.{ .new_split = .left }) catch |err| {
|
_ = surface.performBindingAction(.{ .new_split = .left }) catch |err| {
|
||||||
log.warn("error performing binding action error={}", .{err});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
@ -1029,11 +1025,10 @@ fn gtkActionSplitLeft(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionSplitUp(
|
fn gtkActionSplitUp(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
const surface = self.actionSurface() orelse return;
|
const surface = self.actionSurface() orelse return;
|
||||||
_ = surface.performBindingAction(.{ .new_split = .up }) catch |err| {
|
_ = surface.performBindingAction(.{ .new_split = .up }) catch |err| {
|
||||||
log.warn("error performing binding action error={}", .{err});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
@ -1042,11 +1037,10 @@ fn gtkActionSplitUp(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionToggleInspector(
|
fn gtkActionToggleInspector(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
const surface = self.actionSurface() orelse return;
|
const surface = self.actionSurface() orelse return;
|
||||||
_ = surface.performBindingAction(.{ .inspector = .toggle }) catch |err| {
|
_ = surface.performBindingAction(.{ .inspector = .toggle }) catch |err| {
|
||||||
log.warn("error performing binding action error={}", .{err});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
@ -1055,11 +1049,10 @@ fn gtkActionToggleInspector(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionCopy(
|
fn gtkActionCopy(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
const surface = self.actionSurface() orelse return;
|
const surface = self.actionSurface() orelse return;
|
||||||
_ = surface.performBindingAction(.{ .copy_to_clipboard = {} }) catch |err| {
|
_ = surface.performBindingAction(.{ .copy_to_clipboard = {} }) catch |err| {
|
||||||
log.warn("error performing binding action error={}", .{err});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
@ -1068,11 +1061,10 @@ fn gtkActionCopy(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionPaste(
|
fn gtkActionPaste(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
const surface = self.actionSurface() orelse return;
|
const surface = self.actionSurface() orelse return;
|
||||||
_ = surface.performBindingAction(.{ .paste_from_clipboard = {} }) catch |err| {
|
_ = surface.performBindingAction(.{ .paste_from_clipboard = {} }) catch |err| {
|
||||||
log.warn("error performing binding action error={}", .{err});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
@ -1081,11 +1073,10 @@ fn gtkActionPaste(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionReset(
|
fn gtkActionReset(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
const surface = self.actionSurface() orelse return;
|
const surface = self.actionSurface() orelse return;
|
||||||
_ = surface.performBindingAction(.{ .reset = {} }) catch |err| {
|
_ = surface.performBindingAction(.{ .reset = {} }) catch |err| {
|
||||||
log.warn("error performing binding action error={}", .{err});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
@ -1094,11 +1085,10 @@ fn gtkActionReset(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionClear(
|
fn gtkActionClear(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
const surface = self.actionSurface() orelse return;
|
const surface = self.actionSurface() orelse return;
|
||||||
_ = surface.performBindingAction(.{ .clear_screen = {} }) catch |err| {
|
_ = surface.performBindingAction(.{ .clear_screen = {} }) catch |err| {
|
||||||
log.warn("error performing binding action error={}", .{err});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
@ -1107,11 +1097,10 @@ fn gtkActionClear(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionPromptTitle(
|
fn gtkActionPromptTitle(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
const surface = self.actionSurface() orelse return;
|
const surface = self.actionSurface() orelse return;
|
||||||
_ = surface.performBindingAction(.{ .prompt_surface_title = {} }) catch |err| {
|
_ = surface.performBindingAction(.{ .prompt_surface_title = {} }) catch |err| {
|
||||||
log.warn("error performing binding action error={}", .{err});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
|
@ -47,11 +47,23 @@ pub fn main() !void {
|
|||||||
alloc,
|
alloc,
|
||||||
);
|
);
|
||||||
|
|
||||||
const term = try compiler.spawnAndWait();
|
const term = compiler.spawnAndWait() catch |err| switch (err) {
|
||||||
|
error.FileNotFound => {
|
||||||
|
std.log.err(
|
||||||
|
\\`blueprint-compiler` not found.
|
||||||
|
\\
|
||||||
|
\\Ghostty requires `blueprint-compiler` as a build-time dependency starting from version 1.2.
|
||||||
|
\\Please install it, ensure that it is available on your PATH, and then retry building Ghostty.
|
||||||
|
, .{});
|
||||||
|
std.posix.exit(1);
|
||||||
|
},
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
switch (term) {
|
switch (term) {
|
||||||
.Exited => |rc| {
|
.Exited => |rc| {
|
||||||
if (rc != 0) std.posix.exit(1);
|
if (rc != 0) std.process.exit(1);
|
||||||
},
|
},
|
||||||
else => std.posix.exit(1),
|
else => std.process.exit(1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2060,7 +2060,9 @@ keybind: Keybinds = .{},
|
|||||||
/// * `plastic` - A glossy, dark plastic frame.
|
/// * `plastic` - A glossy, dark plastic frame.
|
||||||
/// * `chrome` - A shiny chrome frame.
|
/// * `chrome` - A shiny chrome frame.
|
||||||
///
|
///
|
||||||
/// This only has an effect when `macos-icon` is set to `custom-style`.
|
/// Note: This configuration is required when `macos-icon` is set to
|
||||||
|
/// `custom-style`.
|
||||||
|
///
|
||||||
@"macos-icon-frame": MacAppIconFrame = .aluminum,
|
@"macos-icon-frame": MacAppIconFrame = .aluminum,
|
||||||
|
|
||||||
/// The color of the ghost in the macOS app icon.
|
/// The color of the ghost in the macOS app icon.
|
||||||
@ -2068,8 +2070,6 @@ keybind: Keybinds = .{},
|
|||||||
/// Note: This configuration is required when `macos-icon` is set to
|
/// Note: This configuration is required when `macos-icon` is set to
|
||||||
/// `custom-style`.
|
/// `custom-style`.
|
||||||
///
|
///
|
||||||
/// This only has an effect when `macos-icon` is set to `custom-style`.
|
|
||||||
///
|
|
||||||
/// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color.
|
/// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color.
|
||||||
@"macos-icon-ghost-color": ?Color = null,
|
@"macos-icon-ghost-color": ?Color = null,
|
||||||
|
|
||||||
@ -2084,7 +2084,6 @@ keybind: Keybinds = .{},
|
|||||||
/// Note: This configuration is required when `macos-icon` is set to
|
/// Note: This configuration is required when `macos-icon` is set to
|
||||||
/// `custom-style`.
|
/// `custom-style`.
|
||||||
///
|
///
|
||||||
/// This only has an effect when `macos-icon` is set to `custom-style`.
|
|
||||||
@"macos-icon-screen-color": ?ColorList = null,
|
@"macos-icon-screen-color": ?ColorList = null,
|
||||||
|
|
||||||
/// Put every surface (tab, split, window) into a dedicated Linux cgroup.
|
/// Put every surface (tab, split, window) into a dedicated Linux cgroup.
|
||||||
|
@ -801,6 +801,13 @@ pub const Delete = union(enum) {
|
|||||||
z: i32 = 0, // z
|
z: i32 = 0, // z
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// r/R
|
||||||
|
range: struct {
|
||||||
|
delete: bool = false, // uppercase
|
||||||
|
first: u32 = 0, // x
|
||||||
|
last: u32 = 0, // y
|
||||||
|
},
|
||||||
|
|
||||||
// x/X
|
// x/X
|
||||||
column: struct {
|
column: struct {
|
||||||
delete: bool = false, // uppercase
|
delete: bool = false, // uppercase
|
||||||
@ -885,6 +892,19 @@ pub const Delete = union(enum) {
|
|||||||
break :blk result;
|
break :blk result;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'r', 'R' => blk: {
|
||||||
|
const x = kv.get('x') orelse return error.InvalidFormat;
|
||||||
|
const y = kv.get('y') orelse return error.InvalidFormat;
|
||||||
|
if (x > y) return error.InvalidFormat;
|
||||||
|
break :blk .{
|
||||||
|
.range = .{
|
||||||
|
.delete = what == 'R',
|
||||||
|
.first = x,
|
||||||
|
.last = y,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
'x', 'X' => blk: {
|
'x', 'X' => blk: {
|
||||||
var result: Delete = .{ .column = .{ .delete = what == 'X' } };
|
var result: Delete = .{ .column = .{ .delete = what == 'X' } };
|
||||||
if (kv.get('x')) |v| {
|
if (kv.get('x')) |v| {
|
||||||
@ -1197,3 +1217,76 @@ test "response: encode with image ID and number" {
|
|||||||
try r.encode(fbs.writer());
|
try r.encode(fbs.writer());
|
||||||
try testing.expectEqualStrings("\x1b_Gi=12,I=4;OK\x1b\\", fbs.getWritten());
|
try testing.expectEqualStrings("\x1b_Gi=12,I=4;OK\x1b\\", fbs.getWritten());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "delete range command 1" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var p = Parser.init(alloc);
|
||||||
|
defer p.deinit();
|
||||||
|
|
||||||
|
const input = "a=d,d=r,x=3,y=4";
|
||||||
|
for (input) |c| try p.feed(c);
|
||||||
|
const command = try p.complete();
|
||||||
|
defer command.deinit(alloc);
|
||||||
|
|
||||||
|
try testing.expect(command.control == .delete);
|
||||||
|
const v = command.control.delete;
|
||||||
|
try testing.expect(v == .range);
|
||||||
|
const range = v.range;
|
||||||
|
try testing.expect(!range.delete);
|
||||||
|
try testing.expectEqual(@as(u32, 3), range.first);
|
||||||
|
try testing.expectEqual(@as(u32, 4), range.last);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "delete range command 2" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var p = Parser.init(alloc);
|
||||||
|
defer p.deinit();
|
||||||
|
|
||||||
|
const input = "a=d,d=R,x=5,y=11";
|
||||||
|
for (input) |c| try p.feed(c);
|
||||||
|
const command = try p.complete();
|
||||||
|
defer command.deinit(alloc);
|
||||||
|
|
||||||
|
try testing.expect(command.control == .delete);
|
||||||
|
const v = command.control.delete;
|
||||||
|
try testing.expect(v == .range);
|
||||||
|
const range = v.range;
|
||||||
|
try testing.expect(range.delete);
|
||||||
|
try testing.expectEqual(@as(u32, 5), range.first);
|
||||||
|
try testing.expectEqual(@as(u32, 11), range.last);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "delete range command 3" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var p = Parser.init(alloc);
|
||||||
|
defer p.deinit();
|
||||||
|
|
||||||
|
const input = "a=d,d=R,x=5,y=4";
|
||||||
|
for (input) |c| try p.feed(c);
|
||||||
|
try testing.expectError(error.InvalidFormat, p.complete());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "delete range command 4" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var p = Parser.init(alloc);
|
||||||
|
defer p.deinit();
|
||||||
|
|
||||||
|
const input = "a=d,d=R,x=5";
|
||||||
|
for (input) |c| try p.feed(c);
|
||||||
|
try testing.expectError(error.InvalidFormat, p.complete());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "delete range command 5" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var p = Parser.init(alloc);
|
||||||
|
defer p.deinit();
|
||||||
|
|
||||||
|
const input = "a=d,d=R,y=5";
|
||||||
|
for (input) |c| try p.feed(c);
|
||||||
|
try testing.expectError(error.InvalidFormat, p.complete());
|
||||||
|
}
|
||||||
|
@ -397,6 +397,30 @@ pub const ImageStorage = struct {
|
|||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.range => |v| range: {
|
||||||
|
if (v.first <= 0 or v.last <= 0) {
|
||||||
|
log.warn("delete range values must be greater than zero", .{});
|
||||||
|
break :range;
|
||||||
|
}
|
||||||
|
if (v.first > v.last) {
|
||||||
|
log.warn("delete range 'x' ({}) must be less than or equal to 'y' ({})", .{ v.first, v.last });
|
||||||
|
break :range;
|
||||||
|
}
|
||||||
|
|
||||||
|
var it = self.placements.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
if (entry.key_ptr.image_id >= v.first or entry.key_ptr.image_id <= v.last) {
|
||||||
|
const image_id = entry.key_ptr.image_id;
|
||||||
|
entry.value_ptr.deinit(&t.screen);
|
||||||
|
self.placements.removeByPtr(entry.key_ptr);
|
||||||
|
if (v.delete) self.deleteIfUnused(alloc, image_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark dirty to force redraw
|
||||||
|
self.dirty = true;
|
||||||
|
},
|
||||||
|
|
||||||
// We don't support animation frames yet so they are successfully
|
// We don't support animation frames yet so they are successfully
|
||||||
// deleted!
|
// deleted!
|
||||||
.animation_frames => {},
|
.animation_frames => {},
|
||||||
@ -1111,3 +1135,103 @@ test "storage: delete by row 1x1" {
|
|||||||
.placement_id = .{ .tag = .external, .id = 3 },
|
.placement_id = .{ .tag = .external, .id = 3 },
|
||||||
}) != null);
|
}) != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "storage: delete images by range 1" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
|
||||||
|
defer t.deinit(alloc);
|
||||||
|
const tracked = t.screen.pages.countTrackedPins();
|
||||||
|
|
||||||
|
var s: ImageStorage = .{};
|
||||||
|
defer s.deinit(alloc, &t.screen);
|
||||||
|
try s.addImage(alloc, .{ .id = 1 });
|
||||||
|
try s.addImage(alloc, .{ .id = 2 });
|
||||||
|
try s.addImage(alloc, .{ .id = 3 });
|
||||||
|
try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try s.addPlacement(alloc, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try testing.expectEqual(@as(usize, 3), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 2), s.placements.count());
|
||||||
|
|
||||||
|
s.dirty = false;
|
||||||
|
s.delete(alloc, &t, .{ .range = .{ .delete = false, .first = 1, .last = 2 } });
|
||||||
|
try testing.expect(s.dirty);
|
||||||
|
try testing.expectEqual(@as(usize, 3), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 0), s.placements.count());
|
||||||
|
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "storage: delete images by range 2" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
|
||||||
|
defer t.deinit(alloc);
|
||||||
|
const tracked = t.screen.pages.countTrackedPins();
|
||||||
|
|
||||||
|
var s: ImageStorage = .{};
|
||||||
|
defer s.deinit(alloc, &t.screen);
|
||||||
|
try s.addImage(alloc, .{ .id = 1 });
|
||||||
|
try s.addImage(alloc, .{ .id = 2 });
|
||||||
|
try s.addImage(alloc, .{ .id = 3 });
|
||||||
|
try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try s.addPlacement(alloc, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try testing.expectEqual(@as(usize, 3), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 2), s.placements.count());
|
||||||
|
|
||||||
|
s.dirty = false;
|
||||||
|
s.delete(alloc, &t, .{ .range = .{ .delete = true, .first = 1, .last = 2 } });
|
||||||
|
try testing.expect(s.dirty);
|
||||||
|
try testing.expectEqual(@as(usize, 1), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 0), s.placements.count());
|
||||||
|
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "storage: delete images by range 3" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
|
||||||
|
defer t.deinit(alloc);
|
||||||
|
const tracked = t.screen.pages.countTrackedPins();
|
||||||
|
|
||||||
|
var s: ImageStorage = .{};
|
||||||
|
defer s.deinit(alloc, &t.screen);
|
||||||
|
try s.addImage(alloc, .{ .id = 1 });
|
||||||
|
try s.addImage(alloc, .{ .id = 2 });
|
||||||
|
try s.addImage(alloc, .{ .id = 3 });
|
||||||
|
try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try s.addPlacement(alloc, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try testing.expectEqual(@as(usize, 3), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 2), s.placements.count());
|
||||||
|
|
||||||
|
s.dirty = false;
|
||||||
|
s.delete(alloc, &t, .{ .range = .{ .delete = false, .first = 1, .last = 1 } });
|
||||||
|
try testing.expect(s.dirty);
|
||||||
|
try testing.expectEqual(@as(usize, 3), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 0), s.placements.count());
|
||||||
|
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "storage: delete images by range 4" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
|
||||||
|
defer t.deinit(alloc);
|
||||||
|
const tracked = t.screen.pages.countTrackedPins();
|
||||||
|
|
||||||
|
var s: ImageStorage = .{};
|
||||||
|
defer s.deinit(alloc, &t.screen);
|
||||||
|
try s.addImage(alloc, .{ .id = 1 });
|
||||||
|
try s.addImage(alloc, .{ .id = 2 });
|
||||||
|
try s.addImage(alloc, .{ .id = 3 });
|
||||||
|
try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try s.addPlacement(alloc, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try testing.expectEqual(@as(usize, 3), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 2), s.placements.count());
|
||||||
|
|
||||||
|
s.dirty = false;
|
||||||
|
s.delete(alloc, &t, .{ .range = .{ .delete = true, .first = 1, .last = 1 } });
|
||||||
|
try testing.expect(s.dirty);
|
||||||
|
try testing.expectEqual(@as(usize, 1), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 0), s.placements.count());
|
||||||
|
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user