✨ feat(tab-groups): add tab groups with color-coded inline grouping#594
✨ feat(tab-groups): add tab groups with color-coded inline grouping#594kud wants to merge 10 commits into
Conversation
|
I get some compiler errors: The idea of tab groups in general is a good and thank you for taking this on! A few comments:
|
|
🔧 All addressed in 19ffef9:
|
|
I still get compiler errors and warnings. |
|
@gnachman Oh yeah sorry, it's still in draft! |
|
Ah, this is just what I was looking for. Hopefully this comes to fruition! |
|
@abrambailey Working on it, I've just got a hard time to build the app on my different computers. I'm working on the DX for now to help the |
|
@kud , |
|
@namp10010 Yes :] |
|
@kud , I created a small PR to your branch to fix a compile issue on my machine (M1 Pro). it compiles and runs now. hope that helps |
|
Thank you! @namp10010 |
Groups tabs in the tab bar under a named, color-coded header. Users can create, rename, collapse/expand, reorder, and persist groups across restarts. - Right-click any tab → "Add to New Group…" opens a sheet with name field and 9 color swatches plus an eyedropper for custom colors - Group header tab renders with color dot, chevron, and member count - Single click toggles collapse/expand; double click opens the edit sheet (rename + recolor in one place) - Member tabs render a left-edge color accent bar matching the group color - Dragging a group header auto-collapses it first to prevent self-nesting - "Move to Group" / "Remove from Group" in the context menu - Groups and collapse state persist via window arrangements - Pre-ship bug fixes: NSColorPanel dangling-target crash, stash integrity on removeTab, multi-group collapse completions, selectNearestTabOutside bounds, timer invalidation in dealloc, retain cycle in deleteTabGroup, and intra-group reorder no longer ejects tabs
gnachman
left a comment
There was a problem hiding this comment.
Missing Tab Style and Orientation Coverage
Tahoe style is completely unhandled. PSMTahoeTabStyle.swift has its own independent drawTabCell(_:highlightAmount:) and desiredWidth(ofTabCell:). The PR only modifies PSMYosemiteTabStyle.m. On Tahoe:
- Group headers render as normal tabs (with close button, interior text, etc.) instead of the colored pill
closeButtonRect(forTabCell:)doesn't returnNSZeroRectfor group headersdesiredWidth(ofTabCell:)doesn't account for group header sizing- Member tabs get no accent bar
The Yosemite-derived styles (Minimal, Dark, DarkHighContrast, LightHighContrast) all inherit drawTabCell:highlightAmount: and desiredWidthOfTabCell: without overriding them, so those are covered.
Vertical orientation is not handled. The group decoration methods assume horizontal layout:
drawGroupMemberSidebar:draws a full-width horizontal strip at the cell's bottom edge. In vertical orientation a left-edge vertical strip would make more sense.drawGroupHeaderDecorations:uses margins and text centering tuned for horizontal cells (short and wide). Vertical tab cells are tall and narrow — the pill and text will likely look wrong.- The group header width calculation in
desiredWidthOfTabCell:is only meaningful for horizontal layout; vertical tabs use height.
Light mode has hardcoded dark-background assumptions. The default neon style hardcodes [NSColor whiteColor] for group header text. The active-state border (colorWithWhite:1.0 alpha:0.4) and hover overlay (colorWithWhite:0.0 alpha:0.25) also don't adapt. The flat style does luminance-adaptive text, which is good — the neon path should do the same. The PSMGroupPillButton multi-select pill has the same problem (white text/borders hardcoded).
Memory Management (MRC)
PSMTabBarControl.m is MRC ([super dealloc], explicit retain/release throughout). The new code has leaks:
- Missing releases in
-dealloc:_fadingInCells,_collapsingCells,_collapseCompletions,_multiSelectedTabViewItems, and_groupSelectionButtonare allocated but never released. PSMGroupPillButton -updateTrackingAreas: The old_trackingAreais removed from the view but not released before reassignment.PSMGroupPillButtonhas no-dealloc:_trackingAreais never released on teardown.-groupGradientForColor:leaks:[[NSGradient alloc] initWithColors:...]is returned without autorelease.
Architectural Fragility
The PR adds 16+ isKindOfClass:[PTYTab class] guards scattered across PseudoTerminal.m (in windowWillResize:, tabViewDidChangeNumberOfTabViewItems:, fitTabsToWindow, allSessions, refreshTerminal:, tabs, sizeOfLargestTabWithExclusion:, minWidth, buildSessionSubmenu:, and every delegate method). Every existing and future code path that iterates tabViewItems must remember this check or it will crash on group header items.
A centralizing approach would reduce risk — e.g., a -[PseudoTerminal realTabViewItems] filtered accessor, or a category on NSTabViewItem that provides -isGroupHeader.
NSColorPanel Dangling Target
iTermCreateTabGroupViewController sets itself as the shared NSColorPanel's target/action but only cleans up via dismissColorPanel() called from button actions. If the sheet is dismissed externally (parent window closes, escape key), deinit doesn't clean up and the color panel retains a dangling target. Should add:
deinit {
dismissColorPanel()
}Naming / Rendering Inconsistencies
drawGroupMemberSidebar:draws a horizontal top/bottom strip, not a sidebar. The PR description says "left-edge accent bar." Either the rendering or the name should change.updateAnimatedcalls[self update:NO]— theNOargument means no animation. The name suggests the opposite.
Dead Code
NSCodingoniTermTabGroup:encode(with:)andinit?(coder:)are implemented, but arrangements use thetoDictionary/init?(dictionary:)path. The NSCoding conformance appears unused._cellSlideOffsetproperty onPSMTabBarCell: declared, synthesized, initialized, never read.
|
Hey all. What's needed to help move this PR forward? I've been looking for exactly this feature and am encouraged to see so much progress already made on it. |
|
Hi, and thanks for putting this together. There's a lot of good work here — the sheet UI, the color picker with the eyedropper, the gradient drawing, and the overall feature scope are all well-conceived. Before either of us invests more time in revisions, I'd like to step back and talk about the underlying design, because I think we need to change the foundation before the detail-level feedback is worth addressing. I pulled the branch down and tried it, and hit a bunch of issues:
Aside from that, there is a real architectural issue: the group header is a real NSTabViewItem inserted into NSTabView.tabViewItems, with identifier = iTermTabGroup. That creates a bunch of issues:
It would be simpler to keep NSTabView.tabViewItems the same, with only PTYTabs. Group headers can be synthetic cells rendered by PSMTabBarControl, interleaved between real tab cells based on a model the tab bar queries through a delegate callback along the lines of -orderedGroupsForTabBarControl: and -groupForTab:. iTermTabGroup holds [uniqueId] (no NSTabViewItem, which can create a retain cycle). The expanded/collapsed state of a group is reflected by the cells that PSMTabBarControl sees internally, while NSTabBarControl does not need to know about them. Under that model, a lot complexity disappears: the isKindOfClass: guards, stashedTabViewItems, enforceContiguity, reconcileGroupsAfterReorder, moveGroupAfterLastGroup, autoManageGroupCollapseForTab, the groupCollapseManaging reentrancy guard, the animation state machine with markNextInsertionsAsAnimated:, and the expand-encode-recollapse in A few things carry forward pretty much as-is: iTermCreateTabGroupViewController, the gradient drawing in drawGroupHeaderDecorations:, the sidebar drawing for members, the menu wiring, and the basic shape of iTermTabGroup (name / color / memberTabIDs / isCollapsed). Two smaller points I'd separate out, independent of the core:
How would you feel about taking another pass along these lines? Happy to discuss the delegate shape, the collapse-as-render-filter approach, or anything else before you start. I want to make sure we land something we're both comfortable maintaining — the feature itself is worth having, I just want the foundation to be right. I think it would be less painful to do as several smaller PRs, since this is a pretty big project. |
|
Just curious if this one is still being worked on? |
# Conflicts: # ThirdParty/PSMTabBarControl/source/PSMTabBarControl.m # iTerm2.xcodeproj/project.pbxproj
- Remove unused `cellSlideOffset` property from PSMTabBarCell - Rename `updateAnimated` → `update` in PSMTabBarControl public API - Fix memory leaks: add `dealloc` to release `_trackingArea`/`_title`; release `_fadingInCells`, `_collapsingCells`, `_collapseCompletions`, `_multiSelectedTabViewItems`, `_groupSelectionButton` in PSMTabBarControl dealloc; add `autorelease` to copied completion-block arrays - Add `hasDarkAppearance` / `tabBarHasDarkAppearance` helpers and apply dark-mode-aware colours to group header button and group header decorations in both Tahoe and Yosemite styles - Compute `lightGroupColor` luminance once and use it for active-border, hover-overlay, and text-colour decisions instead of hardcoding white - Draw group-header accent as a vertical stripe in vertical orientation (`drawGroupMemberAccent` replaces `drawGroupMemberSidebar`) - Skip drawing collapsed cells in `drawTabCell` (`isAnimatingCollapse` early return) - Add `closeButtonRect` / `widthOfCell` overrides for group-header cells in PSMTahoeTabStyle - Add `NSTabViewItem.it_tab` category to safely unwrap PTYTab identifiers; replace all `isKindOfClass:` guards across PseudoTerminal.m and iTermApplicationDelegate.m - Drop NSCoding conformance from iTermTabGroup (serialisation now via `toDictionary`) - Add `deinit` to iTermCreateTabGroupViewController to dismiss colour panel - Document tab-group feature in docs/notes-3.7.txt
Fix two issues on PR gnachman#594: 1. Tab progress bar conflicts with tab groups: - PSMYosemiteTabStyle.m / PSMTahoeTabStyle.swift: move group member accent bar from cell top edge to bottom edge (top band is where progress bar draws) - iTermTabBarControlView.m: cellAllowsTabProgressBar now returns NO for group header items; both helpers use it_tab category 2. Compiler warnings introduced by PR: - PSMTabBarCell.h / PTYTab.h: remove stray nullable tokens that enabled nullability-completeness auditing on previously-unaudited headers - iTermRootTerminalView.h: add full nullability auditing with NS_ASSUME_NONNULL_BEGIN/END; explicit nullable on documented-nil members (divisionView, statusBarViewController, windowTitle, toolbelt params) - PseudoTerminal.h: move five Swift-implemented tab-group method declarations into PseudoTerminal (TabGroups) category to silence incomplete-implementation - PSMYosemiteTabStyle.m: replace NSBezierPath.CGPath (macOS 14+) with iterm_CGPath category (deployment target 12.4) - PseudoTerminal+TabGroups.swift: remove dead nil-coalescing operator
Remove the neon gradient style (tabGroupNeonStyle advanced setting) and replace it with a simpler, flat rendering path in both Tahoe and Yosemite tab styles. - Rename drawGroupMemberAccent → drawGroupMemberDecoration in both styles; selected members now render a thin colour ring, unselected members a 14%-alpha colour wash over the pill - Remove tabBarHasDarkAppearance and groupGradient helpers (no longer needed without gradient paths) - Remove DEFINE_BOOL(tabGroupNeonStyle) from advanced settings (model + header) - Reorder drawing calls so group-member decoration is applied before interior rendering, matching the new Tahoe behaviour - Add update:(BOOL)animate overload to PSMTabBarControl.h and call it with animate:YES from PseudoTerminal+TabGroups collapse/restore paths
…collapsed - Add `drawGroupUnderlines`/`drawGroupUnderlinesInBar:clipRect:` to PSMTahoeTabStyle and PSMYosemiteTabStyle: renders a Chrome-style rounded underline in the group colour spanning the header and all member tabs (horizontal orientation only) - Introduce `groupHeaderDisplayName(for:)` / `groupHeaderDisplayNameForCell:` helpers: returns "<name> (N)" when the group is collapsed and has members, falling back to just "N" or the raw name; both style files consume this in width calculations and drawing instead of `cell.groupName` directly - Rewrite `reconcileGroupsAfterReorder` in PseudoTerminal+TabGroups: Chrome-style drag-in logic adds a single foreign tab dropped between members to the group; tabs no longer in the span are removed (drag-out) - Update docs/notes-3.7.txt to mention drag-in/out membership changes
Adds three new context menu actions for tab groups: - "New Tab in Group": opens a tab using the current session's profile and assigns it to the selected group - "Close Group": expands a collapsed group if needed, then closes all its member tabs - "Collapse Group" / "Expand Group": toggles collapse state via the context menu Also renames the existing "Edit Group" item to "Edit Group…" (with ellipsis) and adds separator items to visually group related actions.
…opover, polish decoration - Implement time-based cubic ease-out animation for tab-bar resize, replacing delta-driven loop (PSMTabBarControl) - Replace group underlines with Chrome-style management popover: single-click opens editor with name/swatches/actions, double-click toggles collapse - Make group decoration loader-safe and consistent: Tahoe uses filled tinted container with rounded border; Yosemite/Sequoia/Minimal/Compact remove redundant underline

🎟️ Ticket
Ticket: N/A
📄 Description
Adds tab groups to the tab bar. Users can organise tabs into named, color-coded groups that collapse and expand inline — similar to how Firefox handles tab groups.
What was added:
Key files:
sources/iTermTabGroup.swiftsources/PseudoTerminal+TabGroups.swiftsources/iTermCreateTabGroupViewController.swiftThirdParty/PSMTabBarControl/source/PSMTabBarCell.h/.mThirdParty/PSMTabBarControl/source/PSMTabBarControl.h/.mThirdParty/PSMTabBarControl/source/PSMYosemiteTabStyle.msources/PseudoTerminal.h/.m✅ How to Validate
make run🛠️ Developer Checklist