mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-10-02 16:08:39 +00:00
add undo/redo keybindings, default them on macOS
This commit is contained in:
@@ -673,6 +673,8 @@ typedef enum {
|
||||
GHOSTTY_ACTION_CONFIG_CHANGE,
|
||||
GHOSTTY_ACTION_CLOSE_WINDOW,
|
||||
GHOSTTY_ACTION_RING_BELL,
|
||||
GHOSTTY_ACTION_UNDO,
|
||||
GHOSTTY_ACTION_REDO,
|
||||
GHOSTTY_ACTION_CHECK_FOR_UPDATES
|
||||
} ghostty_action_tag_e;
|
||||
|
||||
|
@@ -398,11 +398,8 @@ class AppDelegate: NSObject,
|
||||
syncMenuShortcut(config, action: "new_split:down", menuItem: self.menuSplitDown)
|
||||
syncMenuShortcut(config, action: "new_split:up", menuItem: self.menuSplitUp)
|
||||
|
||||
// TODO: sync
|
||||
menuUndo?.keyEquivalent = "z"
|
||||
menuUndo?.keyEquivalentModifierMask = [.command]
|
||||
menuRedo?.keyEquivalent = "z"
|
||||
menuRedo?.keyEquivalentModifierMask = [.command, .shift]
|
||||
syncMenuShortcut(config, action: "undo", menuItem: self.menuUndo)
|
||||
syncMenuShortcut(config, action: "redo", menuItem: self.menuRedo)
|
||||
syncMenuShortcut(config, action: "copy_to_clipboard", menuItem: self.menuCopy)
|
||||
syncMenuShortcut(config, action: "paste_from_clipboard", menuItem: self.menuPaste)
|
||||
syncMenuShortcut(config, action: "paste_from_selection", menuItem: self.menuPasteSelection)
|
||||
|
@@ -428,8 +428,7 @@ class TerminalController: BaseTerminalController {
|
||||
undoManager.registerUndo(
|
||||
withTarget: newController,
|
||||
expiresAfter: newController.undoExpiration) { target in
|
||||
// For redo, we close the tab again
|
||||
target.closeTabImmediately()
|
||||
target.closeTab(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -459,8 +458,7 @@ class TerminalController: BaseTerminalController {
|
||||
undoManager.registerUndo(
|
||||
withTarget: newController,
|
||||
expiresAfter: newController.undoExpiration) { target in
|
||||
// For redo, we close the window again
|
||||
target.closeWindowImmediately()
|
||||
target.closeWindow(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -553,6 +553,12 @@ extension Ghostty {
|
||||
case GHOSTTY_ACTION_CHECK_FOR_UPDATES:
|
||||
checkForUpdates(app)
|
||||
|
||||
case GHOSTTY_ACTION_UNDO:
|
||||
return undo(app, target: target)
|
||||
|
||||
case GHOSTTY_ACTION_REDO:
|
||||
return redo(app, target: target)
|
||||
|
||||
case GHOSTTY_ACTION_CLOSE_ALL_WINDOWS:
|
||||
fallthrough
|
||||
case GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW:
|
||||
@@ -599,6 +605,48 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
private static func undo(_ app: ghostty_app_t, target: ghostty_target_s) -> Bool {
|
||||
let undoManager: UndoManager?
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
undoManager = (NSApp.delegate as? AppDelegate)?.undoManager
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return false }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return false }
|
||||
undoManager = surfaceView.undoManager
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
return false
|
||||
}
|
||||
|
||||
guard let undoManager, undoManager.canUndo else { return false }
|
||||
undoManager.undo()
|
||||
return true
|
||||
}
|
||||
|
||||
private static func redo(_ app: ghostty_app_t, target: ghostty_target_s) -> Bool {
|
||||
let undoManager: UndoManager?
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
undoManager = (NSApp.delegate as? AppDelegate)?.undoManager
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return false }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return false }
|
||||
undoManager = surfaceView.undoManager
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
return false
|
||||
}
|
||||
|
||||
guard let undoManager, undoManager.canRedo else { return false }
|
||||
undoManager.redo()
|
||||
return true
|
||||
}
|
||||
|
||||
private static func newWindow(_ app: ghostty_app_t, target: ghostty_target_s) {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
|
@@ -4337,6 +4337,18 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
{},
|
||||
),
|
||||
|
||||
.undo => return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.undo,
|
||||
{},
|
||||
),
|
||||
|
||||
.redo => return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.redo,
|
||||
{},
|
||||
),
|
||||
|
||||
.select_all => {
|
||||
const sel = self.io.terminal.screen.selectAll();
|
||||
if (sel) |s| {
|
||||
|
@@ -258,6 +258,13 @@ pub const Action = union(Key) {
|
||||
/// it needs to ring the bell. This is usually a sound or visual effect.
|
||||
ring_bell,
|
||||
|
||||
/// Undo the last action. See the "undo" keybinding for more
|
||||
/// details on what can and cannot be undone.
|
||||
undo,
|
||||
|
||||
/// Redo the last undone action.
|
||||
redo,
|
||||
|
||||
check_for_updates,
|
||||
|
||||
/// Sync with: ghostty_action_tag_e
|
||||
@@ -307,6 +314,8 @@ pub const Action = union(Key) {
|
||||
config_change,
|
||||
close_window,
|
||||
ring_bell,
|
||||
undo,
|
||||
redo,
|
||||
check_for_updates,
|
||||
};
|
||||
|
||||
|
@@ -26,7 +26,7 @@ pub fn genConfig(writer: anytype, cli: bool) !void {
|
||||
\\
|
||||
);
|
||||
|
||||
@setEvalBranchQuota(3000);
|
||||
@setEvalBranchQuota(5000);
|
||||
inline for (@typeInfo(Config).@"struct".fields) |field| {
|
||||
if (field.name[0] == '_') continue;
|
||||
|
||||
@@ -94,6 +94,7 @@ pub fn genKeybindActions(writer: anytype) !void {
|
||||
const info = @typeInfo(KeybindAction);
|
||||
std.debug.assert(info == .@"union");
|
||||
|
||||
@setEvalBranchQuota(5000);
|
||||
inline for (info.@"union".fields) |field| {
|
||||
if (field.name[0] == '_') continue;
|
||||
|
||||
|
@@ -4898,6 +4898,18 @@ pub const Keybinds = struct {
|
||||
.{ .key = .{ .unicode = 'q' }, .mods = .{ .super = true } },
|
||||
.{ .quit = {} },
|
||||
);
|
||||
try self.set.putFlags(
|
||||
alloc,
|
||||
.{ .key = .{ .unicode = 'z' }, .mods = .{ .super = true } },
|
||||
.{ .undo = {} },
|
||||
.{ .performable = true },
|
||||
);
|
||||
try self.set.putFlags(
|
||||
alloc,
|
||||
.{ .key = .{ .unicode = 'z' }, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .redo = {} },
|
||||
.{ .performable = true },
|
||||
);
|
||||
try self.set.putFlags(
|
||||
alloc,
|
||||
.{ .key = .{ .unicode = 'k' }, .mods = .{ .super = true } },
|
||||
|
@@ -655,6 +655,35 @@ pub const Action = union(enum) {
|
||||
/// Only implemented on macOS.
|
||||
check_for_updates,
|
||||
|
||||
/// Undo the last undoable action for the focused surface or terminal,
|
||||
/// if possible. This can undo actions such as closing tabs or
|
||||
/// windows.
|
||||
///
|
||||
/// Not every action in Ghostty can be undone or redone. The list
|
||||
/// of actions support undo/redo is currently limited to:
|
||||
///
|
||||
/// - New window, close window
|
||||
/// - New tab, close tab
|
||||
/// - New split, close split
|
||||
///
|
||||
/// All actions are only undoable/redoable for a limited time.
|
||||
/// For example, restoring a closed split can only be done for
|
||||
/// some number of seconds since the split was closed. The exact
|
||||
/// amount is configured with `TODO`.
|
||||
///
|
||||
/// The undo/redo actions being limited ensures that there is
|
||||
/// bounded memory usage over time, closed surfaces don't continue running
|
||||
/// in the background indefinitely, and the keybinds become available
|
||||
/// for terminal applications to use.
|
||||
///
|
||||
/// Only implemented on macOS.
|
||||
undo,
|
||||
|
||||
/// Redo the last undoable action for the focused surface or terminal,
|
||||
/// if possible. See "undo" for more details on what can and cannot
|
||||
/// be undone or redone.
|
||||
redo,
|
||||
|
||||
/// Quit Ghostty.
|
||||
quit,
|
||||
|
||||
@@ -991,6 +1020,8 @@ pub const Action = union(enum) {
|
||||
.toggle_secure_input,
|
||||
.toggle_command_palette,
|
||||
.reset_window_size,
|
||||
.undo,
|
||||
.redo,
|
||||
.crash,
|
||||
=> .surface,
|
||||
|
||||
|
@@ -409,6 +409,18 @@ fn actionCommands(action: Action.Key) []const Command {
|
||||
.description = "Check for updates to the application.",
|
||||
}},
|
||||
|
||||
.undo => comptime &.{.{
|
||||
.action = .undo,
|
||||
.title = "Undo",
|
||||
.description = "Undo the last action.",
|
||||
}},
|
||||
|
||||
.redo => comptime &.{.{
|
||||
.action = .redo,
|
||||
.title = "Redo",
|
||||
.description = "Redo the last undone action.",
|
||||
}},
|
||||
|
||||
.quit => comptime &.{.{
|
||||
.action = .quit,
|
||||
.title = "Quit",
|
||||
|
Reference in New Issue
Block a user