mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-29 06:28:37 +00:00
Close other tabs feature on Mac.
Supporting command line, file menu and keybindings. Default mac shortcut of `super + alt + o` (other) Not able to test on Linux so excluding `close_other_tabs` from `gtk` for now make a default short cut for close other tabs
This commit is contained in:

committed by
Mitchell Hashimoto

parent
13425b4881
commit
c26323d697
@@ -709,6 +709,7 @@ typedef enum {
|
|||||||
GHOSTTY_ACTION_NEW_WINDOW,
|
GHOSTTY_ACTION_NEW_WINDOW,
|
||||||
GHOSTTY_ACTION_NEW_TAB,
|
GHOSTTY_ACTION_NEW_TAB,
|
||||||
GHOSTTY_ACTION_CLOSE_TAB,
|
GHOSTTY_ACTION_CLOSE_TAB,
|
||||||
|
GHOSTTY_ACTION_CLOSE_OTHER_TABS,
|
||||||
GHOSTTY_ACTION_NEW_SPLIT,
|
GHOSTTY_ACTION_NEW_SPLIT,
|
||||||
GHOSTTY_ACTION_CLOSE_ALL_WINDOWS,
|
GHOSTTY_ACTION_CLOSE_ALL_WINDOWS,
|
||||||
GHOSTTY_ACTION_TOGGLE_MAXIMIZE,
|
GHOSTTY_ACTION_TOGGLE_MAXIMIZE,
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24123.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="macosx"/>
|
<deployment identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24093.7"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24123.1"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||||
|
@@ -95,6 +95,11 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||||||
selector: #selector(onCloseTab),
|
selector: #selector(onCloseTab),
|
||||||
name: .ghosttyCloseTab,
|
name: .ghosttyCloseTab,
|
||||||
object: nil)
|
object: nil)
|
||||||
|
center.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(onCloseOtherTabs),
|
||||||
|
name: .ghosttyCloseOtherTabs,
|
||||||
|
object: nil)
|
||||||
center.addObserver(
|
center.addObserver(
|
||||||
self,
|
self,
|
||||||
selector: #selector(onResetWindowSize),
|
selector: #selector(onResetWindowSize),
|
||||||
@@ -559,7 +564,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||||||
closeWindow(nil)
|
closeWindow(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func closeTabImmediately() {
|
private func closeTabImmediately(registerRedo: Bool = true) {
|
||||||
guard let window = window else { return }
|
guard let window = window else { return }
|
||||||
guard let tabGroup = window.tabGroup,
|
guard let tabGroup = window.tabGroup,
|
||||||
tabGroup.windows.count > 1 else {
|
tabGroup.windows.count > 1 else {
|
||||||
@@ -577,7 +582,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||||||
) { ghostty in
|
) { ghostty in
|
||||||
let newController = TerminalController(ghostty, with: undoState)
|
let newController = TerminalController(ghostty, with: undoState)
|
||||||
|
|
||||||
// Register redo action
|
if registerRedo {
|
||||||
undoManager.registerUndo(
|
undoManager.registerUndo(
|
||||||
withTarget: newController,
|
withTarget: newController,
|
||||||
expiresAfter: newController.undoExpiration
|
expiresAfter: newController.undoExpiration
|
||||||
@@ -586,10 +591,60 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.close()
|
window.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func closeOtherTabsImmediately() {
|
||||||
|
guard let window = window else { return }
|
||||||
|
guard let tabGroup = window.tabGroup else { return }
|
||||||
|
guard tabGroup.windows.count > 1 else { return }
|
||||||
|
|
||||||
|
// Start an undo grouping
|
||||||
|
if let undoManager {
|
||||||
|
undoManager.beginUndoGrouping()
|
||||||
|
}
|
||||||
|
defer {
|
||||||
|
undoManager?.endUndoGrouping()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through all tabs except the current one.
|
||||||
|
for window in tabGroup.windows where window != self.window {
|
||||||
|
// We ignore any non-terminal tabs. They don't currently exist and we can't
|
||||||
|
// properly undo them anyways so I'd rather ignore them and get a bug report
|
||||||
|
// later if and when we introduce non-terminal tabs.
|
||||||
|
if let controller = window.windowController as? TerminalController {
|
||||||
|
// We must not register a redo, because it messes with our own redo
|
||||||
|
// that we register later.
|
||||||
|
controller.closeTabImmediately(registerRedo: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let undoManager {
|
||||||
|
undoManager.setActionName("Close Other Tabs")
|
||||||
|
|
||||||
|
// We need to register an undo that refocuses this window. Otherwise, the
|
||||||
|
// undo operation above for each tab will steal focus.
|
||||||
|
undoManager.registerUndo(
|
||||||
|
withTarget: self,
|
||||||
|
expiresAfter: undoExpiration
|
||||||
|
) { target in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
target.window?.makeKeyAndOrderFront(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register redo action
|
||||||
|
undoManager.registerUndo(
|
||||||
|
withTarget: target,
|
||||||
|
expiresAfter: target.undoExpiration
|
||||||
|
) { target in
|
||||||
|
target.closeOtherTabsImmediately()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Closes the current window (including any other tabs) immediately and without
|
/// Closes the current window (including any other tabs) immediately and without
|
||||||
/// confirmation. This will setup proper undo state so the action can be undone.
|
/// confirmation. This will setup proper undo state so the action can be undone.
|
||||||
private func closeWindowImmediately() {
|
private func closeWindowImmediately() {
|
||||||
@@ -1023,6 +1078,38 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBAction func closeOtherTabs(_ sender: Any?) {
|
||||||
|
guard let window = window else { return }
|
||||||
|
guard let tabGroup = window.tabGroup else { return }
|
||||||
|
|
||||||
|
// If we only have one window then we have no other tabs to close
|
||||||
|
guard tabGroup.windows.count > 1 else { return }
|
||||||
|
|
||||||
|
// Check if we have to confirm close.
|
||||||
|
guard tabGroup.windows.contains(where: { window in
|
||||||
|
// Ignore ourself
|
||||||
|
if window == self.window { return false }
|
||||||
|
|
||||||
|
// Ignore non-terminals
|
||||||
|
guard let controller = window.windowController as? TerminalController else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any surfaces require confirmation
|
||||||
|
return controller.surfaceTree.contains(where: { $0.needsConfirmQuit })
|
||||||
|
}) else {
|
||||||
|
self.closeOtherTabsImmediately()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmClose(
|
||||||
|
messageText: "Close Other Tabs?",
|
||||||
|
informativeText: "At least one other tab still has a running process. If you close the tab the process will be killed."
|
||||||
|
) {
|
||||||
|
self.closeOtherTabsImmediately()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@IBAction func returnToDefaultSize(_ sender: Any?) {
|
@IBAction func returnToDefaultSize(_ sender: Any?) {
|
||||||
guard let defaultSize else { return }
|
guard let defaultSize else { return }
|
||||||
window?.setFrame(defaultSize, display: true)
|
window?.setFrame(defaultSize, display: true)
|
||||||
@@ -1206,6 +1293,12 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||||||
closeTab(self)
|
closeTab(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func onCloseOtherTabs(notification: SwiftUI.Notification) {
|
||||||
|
guard let target = notification.object as? Ghostty.SurfaceView else { return }
|
||||||
|
guard surfaceTree.contains(target) else { return }
|
||||||
|
closeOtherTabs(self)
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func onCloseWindow(notification: SwiftUI.Notification) {
|
@objc private func onCloseWindow(notification: SwiftUI.Notification) {
|
||||||
guard let target = notification.object as? Ghostty.SurfaceView else { return }
|
guard let target = notification.object as? Ghostty.SurfaceView else { return }
|
||||||
guard surfaceTree.contains(target) else { return }
|
guard surfaceTree.contains(target) else { return }
|
||||||
|
@@ -457,6 +457,9 @@ extension Ghostty {
|
|||||||
case GHOSTTY_ACTION_CLOSE_TAB:
|
case GHOSTTY_ACTION_CLOSE_TAB:
|
||||||
closeTab(app, target: target)
|
closeTab(app, target: target)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_CLOSE_OTHER_TABS:
|
||||||
|
closeOtherTabs(app, target: target)
|
||||||
|
|
||||||
case GHOSTTY_ACTION_CLOSE_WINDOW:
|
case GHOSTTY_ACTION_CLOSE_WINDOW:
|
||||||
closeWindow(app, target: target)
|
closeWindow(app, target: target)
|
||||||
|
|
||||||
@@ -781,7 +784,7 @@ extension Ghostty {
|
|||||||
private static func closeTab(_ app: ghostty_app_t, target: ghostty_target_s) {
|
private static func closeTab(_ app: ghostty_app_t, target: ghostty_target_s) {
|
||||||
switch (target.tag) {
|
switch (target.tag) {
|
||||||
case GHOSTTY_TARGET_APP:
|
case GHOSTTY_TARGET_APP:
|
||||||
Ghostty.logger.warning("close tab does nothing with an app target")
|
Ghostty.logger.warning("close tabs does nothing with an app target")
|
||||||
return
|
return
|
||||||
|
|
||||||
case GHOSTTY_TARGET_SURFACE:
|
case GHOSTTY_TARGET_SURFACE:
|
||||||
@@ -799,6 +802,27 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func closeOtherTabs(_ app: ghostty_app_t, target: ghostty_target_s) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("close other tabs does nothing with an app target")
|
||||||
|
return
|
||||||
|
|
||||||
|
case GHOSTTY_TARGET_SURFACE:
|
||||||
|
guard let surface = target.target.surface else { return }
|
||||||
|
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||||
|
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: .ghosttyCloseOtherTabs,
|
||||||
|
object: surfaceView
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static func closeWindow(_ app: ghostty_app_t, target: ghostty_target_s) {
|
private static func closeWindow(_ app: ghostty_app_t, target: ghostty_target_s) {
|
||||||
switch (target.tag) {
|
switch (target.tag) {
|
||||||
case GHOSTTY_TARGET_APP:
|
case GHOSTTY_TARGET_APP:
|
||||||
|
@@ -329,6 +329,9 @@ extension Notification.Name {
|
|||||||
/// Close tab
|
/// Close tab
|
||||||
static let ghosttyCloseTab = Notification.Name("com.mitchellh.ghostty.closeTab")
|
static let ghosttyCloseTab = Notification.Name("com.mitchellh.ghostty.closeTab")
|
||||||
|
|
||||||
|
/// Close other tabs
|
||||||
|
static let ghosttyCloseOtherTabs = Notification.Name("com.mitchellh.ghostty.closeOtherTabs")
|
||||||
|
|
||||||
/// Close window
|
/// Close window
|
||||||
static let ghosttyCloseWindow = Notification.Name("com.mitchellh.ghostty.closeWindow")
|
static let ghosttyCloseWindow = Notification.Name("com.mitchellh.ghostty.closeWindow")
|
||||||
|
|
||||||
|
@@ -4840,6 +4840,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
.close_other_tabs => return try self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.close_other_tabs,
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
|
||||||
.select_all => {
|
.select_all => {
|
||||||
const sel = self.io.terminal.screen.selectAll();
|
const sel = self.io.terminal.screen.selectAll();
|
||||||
if (sel) |s| {
|
if (sel) |s| {
|
||||||
|
@@ -86,6 +86,10 @@ pub const Action = union(Key) {
|
|||||||
/// Closes the tab belonging to the currently focused split.
|
/// Closes the tab belonging to the currently focused split.
|
||||||
close_tab,
|
close_tab,
|
||||||
|
|
||||||
|
/// Closes all tabs in the current window other than the currently
|
||||||
|
/// focused tab.
|
||||||
|
close_other_tabs,
|
||||||
|
|
||||||
/// Create a new split. The value determines the location of the split
|
/// Create a new split. The value determines the location of the split
|
||||||
/// relative to the target.
|
/// relative to the target.
|
||||||
new_split: SplitDirection,
|
new_split: SplitDirection,
|
||||||
@@ -300,6 +304,7 @@ pub const Action = union(Key) {
|
|||||||
new_window,
|
new_window,
|
||||||
new_tab,
|
new_tab,
|
||||||
close_tab,
|
close_tab,
|
||||||
|
close_other_tabs,
|
||||||
new_split,
|
new_split,
|
||||||
close_all_windows,
|
close_all_windows,
|
||||||
toggle_maximize,
|
toggle_maximize,
|
||||||
|
@@ -625,6 +625,7 @@ pub const Application = extern struct {
|
|||||||
// Unimplemented
|
// Unimplemented
|
||||||
.secure_input,
|
.secure_input,
|
||||||
.close_all_windows,
|
.close_all_windows,
|
||||||
|
.close_other_tabs,
|
||||||
.float_window,
|
.float_window,
|
||||||
.toggle_visibility,
|
.toggle_visibility,
|
||||||
.cell_size,
|
.cell_size,
|
||||||
|
@@ -156,6 +156,7 @@ pub const CommandPalette = extern struct {
|
|||||||
// for GTK.
|
// for GTK.
|
||||||
switch (command.action) {
|
switch (command.action) {
|
||||||
.close_all_windows,
|
.close_all_windows,
|
||||||
|
.close_other_tabs,
|
||||||
.toggle_secure_input,
|
.toggle_secure_input,
|
||||||
.check_for_updates,
|
.check_for_updates,
|
||||||
.redo,
|
.redo,
|
||||||
|
@@ -528,6 +528,7 @@ pub fn performAction(
|
|||||||
|
|
||||||
// Unimplemented
|
// Unimplemented
|
||||||
.close_all_windows,
|
.close_all_windows,
|
||||||
|
.close_other_tabs,
|
||||||
.float_window,
|
.float_window,
|
||||||
.toggle_visibility,
|
.toggle_visibility,
|
||||||
.cell_size,
|
.cell_size,
|
||||||
|
@@ -108,6 +108,7 @@ pub fn updateConfig(self: *CommandPalette, config: *const configpkg.Config) !voi
|
|||||||
// or don't make sense for GTK
|
// or don't make sense for GTK
|
||||||
switch (command.action) {
|
switch (command.action) {
|
||||||
.close_all_windows,
|
.close_all_windows,
|
||||||
|
.close_other_tabs,
|
||||||
.toggle_secure_input,
|
.toggle_secure_input,
|
||||||
.check_for_updates,
|
.check_for_updates,
|
||||||
.redo,
|
.redo,
|
||||||
|
@@ -558,6 +558,12 @@ pub const Action = union(enum) {
|
|||||||
/// of the `confirm-close-surface` configuration setting.
|
/// of the `confirm-close-surface` configuration setting.
|
||||||
close_tab,
|
close_tab,
|
||||||
|
|
||||||
|
/// Close all tabs other than the currently focused one within the same
|
||||||
|
/// window.
|
||||||
|
///
|
||||||
|
/// Only available on macOS currently.
|
||||||
|
close_other_tabs,
|
||||||
|
|
||||||
/// Close the current window and all tabs and splits therein.
|
/// Close the current window and all tabs and splits therein.
|
||||||
///
|
///
|
||||||
/// This might trigger a close confirmation popup, depending on the value
|
/// This might trigger a close confirmation popup, depending on the value
|
||||||
@@ -1052,6 +1058,7 @@ pub const Action = union(enum) {
|
|||||||
.write_selection_file,
|
.write_selection_file,
|
||||||
.close_surface,
|
.close_surface,
|
||||||
.close_tab,
|
.close_tab,
|
||||||
|
.close_other_tabs,
|
||||||
.close_window,
|
.close_window,
|
||||||
.toggle_maximize,
|
.toggle_maximize,
|
||||||
.toggle_fullscreen,
|
.toggle_fullscreen,
|
||||||
|
@@ -375,6 +375,12 @@ fn actionCommands(action: Action.Key) []const Command {
|
|||||||
.description = "Show the on-screen keyboard if present.",
|
.description = "Show the on-screen keyboard if present.",
|
||||||
}},
|
}},
|
||||||
|
|
||||||
|
.close_other_tabs => comptime &.{.{
|
||||||
|
.action = .close_other_tabs,
|
||||||
|
.title = "Close Other Tabs",
|
||||||
|
.description = "Close all tabs in this window except the current one.",
|
||||||
|
}},
|
||||||
|
|
||||||
.open_config => comptime &.{.{
|
.open_config => comptime &.{.{
|
||||||
.action = .open_config,
|
.action = .open_config,
|
||||||
.title = "Open Config",
|
.title = "Open Config",
|
||||||
|
Reference in New Issue
Block a user