Skip to content

Commit 19ffef9

Browse files
committed
🐛 fix(tab-groups): address review feedback from gnachman
1 parent 2f21b6d commit 19ffef9

4 files changed

Lines changed: 49 additions & 27 deletions

File tree

sources/PseudoTerminal+Private.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ extern NSString *const TERMINAL_ARRANGEMENT_SIZE_LOCKED;
126126
// the right type.
127127
@property (nonatomic, readonly) __unsafe_unretained iTermRootTerminalView *contentView;
128128

129+
@property (nonatomic, readonly) NSMutableArray *mutableTabGroups;
130+
129131
- (void)returnTabBarToContentView;
130132
- (void)updateForTransparency:(NSWindow<PTYWindow> *)window;
131133
- (void)updateVariables;

sources/PseudoTerminal+TabGroups.swift

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,6 @@ extension PseudoTerminal {
88
return mutableTabGroups as! [iTermTabGroup]
99
}
1010

11-
private var mutableTabGroups: NSMutableArray {
12-
if let existing = objc_getAssociatedObject(self, &AssociatedKeys.tabGroups) as? NSMutableArray {
13-
return existing
14-
}
15-
let array = NSMutableArray()
16-
objc_setAssociatedObject(self, &AssociatedKeys.tabGroups, array, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
17-
return array
18-
}
19-
2011
@objc @discardableResult
2112
func createTabGroup(name: String, color: NSColor, forTab tab: PTYTab) -> iTermTabGroup {
2213
let group = iTermTabGroup(name: name, color: color)
@@ -45,13 +36,31 @@ extension PseudoTerminal {
4536

4637
@objc func deleteTabGroup(_ group: iTermTabGroup) {
4738
if group.isCollapsed {
48-
toggleCollapseGroup(group)
39+
unstashItems(for: group)
4940
}
5041
group.memberTabIDs.removeAll()
5142
mutableTabGroups.remove(group)
5243
updateTabGroupDecorations()
5344
}
5445

46+
private func unstashItems(for group: iTermTabGroup) {
47+
guard let representativeID = group.memberTabIDs.first,
48+
let representativeTab = tabs()?.first(where: { Int($0.uniqueId) == representativeID }),
49+
let representativeItem = representativeTab.tabViewItem else { return }
50+
51+
let tabView = contentView.tabView
52+
let baseIndex = tabView.indexOfTabViewItem(representativeItem)
53+
guard baseIndex != NSNotFound else { return }
54+
55+
for (offset, item) in group.stashedTabViewItems.enumerated() {
56+
let targetIndex = baseIndex + 1 + offset
57+
let safeIndex = min(targetIndex, tabView.numberOfTabViewItems)
58+
tabView.insertTabViewItem(item, at: safeIndex)
59+
}
60+
group.stashedTabViewItems.removeAll()
61+
group.isCollapsed = false
62+
}
63+
5564
@objc func toggleCollapseGroup(_ group: iTermTabGroup) {
5665
if group.isCollapsed {
5766
expandGroup(group)
@@ -104,18 +113,20 @@ extension PseudoTerminal {
104113
}
105114

106115
@objc func encodeTabGroupsForArrangement() -> [[String: Any]] {
107-
return tabGroups.map { $0.toDictionary() }
116+
let allTabs = tabs() ?? []
117+
return tabGroups.map { $0.toDictionary(allTabs: allTabs) }
108118
}
109119

110120
@objc func restoreTabGroupsFromArrangement(_ arrangement: [[String: Any]]) {
111-
let allTabs = tabs()
121+
let allTabs = tabs() ?? []
112122
for dict in arrangement {
113123
guard let group = iTermTabGroup(dictionary: dict) else { continue }
114-
let validIDs = group.memberTabIDs.filter { id in
115-
allTabs.contains { Int($0.uniqueId) == id }
124+
let resolvedIDs = group.memberTabIDs.compactMap { index -> Int? in
125+
guard index >= 0, index < allTabs.count else { return nil }
126+
return Int(allTabs[index].uniqueId)
116127
}
117-
guard !validIDs.isEmpty else { continue }
118-
group.memberTabIDs = validIDs
128+
guard !resolvedIDs.isEmpty else { continue }
129+
group.memberTabIDs = resolvedIDs
119130
mutableTabGroups.add(group)
120131
}
121132
updateTabGroupDecorations()
@@ -168,7 +179,7 @@ extension PseudoTerminal {
168179
}
169180

170181
private func collapseGroup(_ group: iTermTabGroup) {
171-
let allTabs = tabs()
182+
let allTabs = tabs() ?? []
172183
let memberTabs = group.memberTabIDs.compactMap { id in allTabs.first { Int($0.uniqueId) == id } }
173184
guard let representative = memberTabs.first else { return }
174185

@@ -189,7 +200,7 @@ extension PseudoTerminal {
189200

190201
private func expandGroup(_ group: iTermTabGroup) {
191202
guard let representativeID = group.memberTabIDs.first,
192-
let representativeTab = tabs().first(where: { Int($0.uniqueId) == representativeID }),
203+
let representativeTab = (tabs() ?? []).first(where: { Int($0.uniqueId) == representativeID }),
193204
let representativeItem = representativeTab.tabViewItem else { return }
194205

195206
let tabView = contentView.tabView
@@ -208,7 +219,7 @@ extension PseudoTerminal {
208219

209220
private func enforceContiguity(of group: iTermTabGroup) {
210221
guard group.memberTabIDs.count > 1 else { return }
211-
let allTabs = tabs()
222+
let allTabs = tabs() ?? []
212223
let memberTabs = group.memberTabIDs.compactMap { id in allTabs.first { Int($0.uniqueId) == id } }
213224
guard let representativeItem = memberTabs.first?.tabViewItem else { return }
214225

@@ -228,7 +239,3 @@ extension PseudoTerminal {
228239
}
229240
}
230241
}
231-
232-
private enum AssociatedKeys {
233-
static var tabGroups = "iTermTabGroupsKey"
234-
}

sources/PseudoTerminal.m

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,8 @@ @implementation PseudoTerminal {
432432
iTermIdempotentOperationJoiner *_rightExtraJoiner;
433433
BOOL _excursionPrevented;
434434

435+
NSMutableArray<iTermTabGroup *> *_mutableTabGroups;
436+
435437
}
436438

437439
@synthesize scope = _scope;
@@ -457,10 +459,15 @@ - (instancetype)initWithWindowNibName:(NSString *)windowNibName {
457459
if (self) {
458460
_automaticallySelectNewTabs = YES;
459461
self.autoCommandHistorySessionGuid = nil;
462+
_mutableTabGroups = [[NSMutableArray alloc] init];
460463
}
461464
return self;
462465
}
463466

467+
- (NSMutableArray *)mutableTabGroups {
468+
return _mutableTabGroups;
469+
}
470+
464471
- (instancetype)initWithSmartLayout:(BOOL)smartLayout
465472
windowType:(iTermWindowType)windowType
466473
savedWindowType:(iTermWindowType)savedWindowType
@@ -1098,6 +1105,7 @@ - (void)dealloc {
10981105
[_fullScreenEnteredSeal release];
10991106
[_windowSizeHelper release];
11001107
[_titlebarAccessoryNanny release];
1108+
[_mutableTabGroups release];
11011109

11021110
[super dealloc];
11031111
}

sources/iTermTabGroup.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Cocoa
1313
static let name = "name"
1414
static let color = "color"
1515
static let memberTabIDs = "memberTabIDs"
16+
static let memberTabIndices = "memberTabIndices"
1617
static let isCollapsed = "isCollapsed"
1718
}
1819

@@ -54,11 +55,15 @@ import Cocoa
5455
coder.encode(isCollapsed, forKey: CodingKey.isCollapsed)
5556
}
5657

57-
@objc func toDictionary() -> [String: Any] {
58+
@objc func toDictionary(allTabs: [PTYTab]) -> [String: Any] {
59+
let indices = memberTabIDs.compactMap { id -> NSNumber? in
60+
guard let index = allTabs.firstIndex(where: { Int($0.uniqueId) == id }) else { return nil }
61+
return NSNumber(value: index)
62+
}
5863
var dict: [String: Any] = [
5964
CodingKey.identifier: identifier,
6065
CodingKey.name: name,
61-
CodingKey.memberTabIDs: memberTabIDs.map { NSNumber(value: $0) },
66+
CodingKey.memberTabIndices: indices,
6267
CodingKey.isCollapsed: isCollapsed
6368
]
6469
if let colorData = try? NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: false) {
@@ -72,13 +77,13 @@ import Cocoa
7277
let name = dictionary[CodingKey.name] as? String,
7378
let colorData = dictionary[CodingKey.color] as? Data,
7479
let color = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: colorData),
75-
let rawIDs = dictionary[CodingKey.memberTabIDs] as? [NSNumber] else {
80+
let rawIndices = dictionary[CodingKey.memberTabIndices] as? [NSNumber] else {
7681
return nil
7782
}
7883
self.identifier = identifier
7984
self.name = name
8085
self.color = color
81-
self.memberTabIDs = rawIDs.map { $0.intValue }
86+
self.memberTabIDs = rawIndices.map { $0.intValue }
8287
self.isCollapsed = (dictionary[CodingKey.isCollapsed] as? Bool) ?? false
8388
}
8489
}

0 commit comments

Comments
 (0)