feat(tabs): add direct tab switching with index and last-active support
- Add PreviousActiveTabID to TabList for tracking previous active tab - Add SetActive() method to TabList to update active tab and track previous - Add TabAt() method to TabList for 0-based index access - Update Switch() usecase to use SetActive() for proper state tracking - Add SwitchToLastActive() usecase for Alt+Tab style tab switching
- commit
- 56901179757430d7637f4447da9f24335ad6f6b0
- author
- brice · · 2025-12-18 06:44 UTC
- parent
- bba9426
- signature
- unsigned
2 files · +44 -3
M internal/application/usecase/manage_tabs.go +25 -1
| @@ -133,7 +133,7 @@ func (uc *ManageTabsUseCase) Switch(ctx context.Context, tabs *entity.TabList, t | |||
| 133 | 133 | } | |
| 134 | 134 | ||
| 135 | 135 | oldActive := tabs.ActiveTabID | |
| 136 | - | tabs.ActiveTabID = tabID | |
| 136 | + | tabs.SetActive(tabID) // Uses SetActive to track previous tab | |
| 137 | 137 | ||
| 138 | 138 | log.Info(). | |
| 139 | 139 | Str("from", string(oldActive)). | |
| @@ -258,6 +258,30 @@ func (uc *ManageTabsUseCase) SwitchByIndex(ctx context.Context, tabs *entity.Tab | |||
| 258 | 258 | return uc.Switch(ctx, tabs, tabs.Tabs[index].ID) | |
| 259 | 259 | } | |
| 260 | 260 | ||
| 261 | + | // SwitchToLastActive switches to the previously active tab (Alt+Tab style). | |
| 262 | + | func (uc *ManageTabsUseCase) SwitchToLastActive(ctx context.Context, tabs *entity.TabList) error { | |
| 263 | + | log := logging.FromContext(ctx) | |
| 264 | + | ||
| 265 | + | if tabs == nil { | |
| 266 | + | return fmt.Errorf("tab list is required") | |
| 267 | + | } | |
| 268 | + | ||
| 269 | + | // If no previous tab recorded, do nothing | |
| 270 | + | if tabs.PreviousActiveTabID == "" { | |
| 271 | + | log.Debug().Msg("no previous active tab") | |
| 272 | + | return nil | |
| 273 | + | } | |
| 274 | + | ||
| 275 | + | // Verify previous tab still exists | |
| 276 | + | if tabs.Find(tabs.PreviousActiveTabID) == nil { | |
| 277 | + | log.Debug().Str("prev_id", string(tabs.PreviousActiveTabID)).Msg("previous tab no longer exists") | |
| 278 | + | tabs.PreviousActiveTabID = "" | |
| 279 | + | return nil | |
| 280 | + | } | |
| 281 | + | ||
| 282 | + | return uc.Switch(ctx, tabs, tabs.PreviousActiveTabID) | |
| 283 | + | } | |
| 284 | + | ||
| 261 | 285 | // Pin toggles the pinned state of a tab. | |
| 262 | 286 | func (uc *ManageTabsUseCase) Pin(ctx context.Context, tabs *entity.TabList, tabID entity.TabID, pinned bool) error { | |
| 263 | 287 | log := logging.FromContext(ctx) | |
| @@ -133,7 +133,7 @@ func (uc *ManageTabsUseCase) Switch(ctx context.Context, tabs *entity.TabList, t | |||||
| 133 | } | 133 | } | ||
| 134 | 134 | ||||
| 135 | oldActive := tabs.ActiveTabID | 135 | oldActive := tabs.ActiveTabID | ||
| 136 | - | tabs.ActiveTabID = tabID | 136 | + | tabs.SetActive(tabID) // Uses SetActive to track previous tab |
| 137 | 137 | ||||
| 138 | log.Info(). | 138 | log.Info(). | ||
| 139 | Str("from", string(oldActive)). | 139 | Str("from", string(oldActive)). | ||
| @@ -258,6 +258,30 @@ func (uc *ManageTabsUseCase) SwitchByIndex(ctx context.Context, tabs *entity.Tab | |||||
| 258 | return uc.Switch(ctx, tabs, tabs.Tabs[index].ID) | 258 | return uc.Switch(ctx, tabs, tabs.Tabs[index].ID) | ||
| 259 | } | 259 | } | ||
| 260 | 260 | ||||
| 261 | + | // SwitchToLastActive switches to the previously active tab (Alt+Tab style). | |||
| 262 | + | func (uc *ManageTabsUseCase) SwitchToLastActive(ctx context.Context, tabs *entity.TabList) error { | |||
| 263 | + | log := logging.FromContext(ctx) | |||
| 264 | + | ||||
| 265 | + | if tabs == nil { | |||
| 266 | + | return fmt.Errorf("tab list is required") | |||
| 267 | + | } | |||
| 268 | + | ||||
| 269 | + | // If no previous tab recorded, do nothing | |||
| 270 | + | if tabs.PreviousActiveTabID == "" { | |||
| 271 | + | log.Debug().Msg("no previous active tab") | |||
| 272 | + | return nil | |||
| 273 | + | } | |||
| 274 | + | ||||
| 275 | + | // Verify previous tab still exists | |||
| 276 | + | if tabs.Find(tabs.PreviousActiveTabID) == nil { | |||
| 277 | + | log.Debug().Str("prev_id", string(tabs.PreviousActiveTabID)).Msg("previous tab no longer exists") | |||
| 278 | + | tabs.PreviousActiveTabID = "" | |||
| 279 | + | return nil | |||
| 280 | + | } | |||
| 281 | + | ||||
| 282 | + | return uc.Switch(ctx, tabs, tabs.PreviousActiveTabID) | |||
| 283 | + | } | |||
| 284 | + | ||||
| 261 | // Pin toggles the pinned state of a tab. | 285 | // Pin toggles the pinned state of a tab. | ||
| 262 | func (uc *ManageTabsUseCase) Pin(ctx context.Context, tabs *entity.TabList, tabID entity.TabID, pinned bool) error { | 286 | func (uc *ManageTabsUseCase) Pin(ctx context.Context, tabs *entity.TabList, tabID entity.TabID, pinned bool) error { | ||
| 263 | log := logging.FromContext(ctx) | 287 | log := logging.FromContext(ctx) | ||
M internal/domain/entity/tab.go +19 -2
| @@ -48,8 +48,9 @@ func (t *Tab) PaneCount() int { | |||
| 48 | 48 | ||
| 49 | 49 | // TabList manages an ordered collection of tabs. | |
| 50 | 50 | type TabList struct { | |
| 51 | - | Tabs []*Tab | |
| 52 | - | ActiveTabID TabID | |
| 51 | + | Tabs []*Tab | |
| 52 | + | ActiveTabID TabID | |
| 53 | + | PreviousActiveTabID TabID // Tracks last active tab for Alt+Tab style switching | |
| 53 | 54 | } | |
| 54 | 55 | ||
| 55 | 56 | // NewTabList creates an empty tab list. | |
| @@ -111,6 +112,22 @@ func (tl *TabList) Count() int { | |||
| 111 | 112 | return len(tl.Tabs) | |
| 112 | 113 | } | |
| 113 | 114 | ||
| 115 | + | // SetActive sets the active tab and updates the previous active tab. | |
| 116 | + | func (tl *TabList) SetActive(id TabID) { | |
| 117 | + | if id != tl.ActiveTabID && tl.ActiveTabID != "" { | |
| 118 | + | tl.PreviousActiveTabID = tl.ActiveTabID | |
| 119 | + | } | |
| 120 | + | tl.ActiveTabID = id | |
| 121 | + | } | |
| 122 | + | ||
| 123 | + | // TabAt returns the tab at the given 0-based index. | |
| 124 | + | func (tl *TabList) TabAt(index int) *Tab { | |
| 125 | + | if index < 0 || index >= len(tl.Tabs) { | |
| 126 | + | return nil | |
| 127 | + | } | |
| 128 | + | return tl.Tabs[index] | |
| 129 | + | } | |
| 130 | + | ||
| 114 | 131 | // Move moves a tab to a new position. | |
| 115 | 132 | func (tl *TabList) Move(id TabID, newPos int) bool { | |
| 116 | 133 | if newPos < 0 || newPos >= len(tl.Tabs) { | |
| @@ -48,8 +48,9 @@ func (t *Tab) PaneCount() int { | |||||
| 48 | 48 | ||||
| 49 | // TabList manages an ordered collection of tabs. | 49 | // TabList manages an ordered collection of tabs. | ||
| 50 | type TabList struct { | 50 | type TabList struct { | ||
| 51 | - | Tabs []*Tab | 51 | + | Tabs []*Tab |
| 52 | - | ActiveTabID TabID | 52 | + | ActiveTabID TabID |
| 53 | + | PreviousActiveTabID TabID // Tracks last active tab for Alt+Tab style switching | |||
| 53 | } | 54 | } | ||
| 54 | 55 | ||||
| 55 | // NewTabList creates an empty tab list. | 56 | // NewTabList creates an empty tab list. | ||
| @@ -111,6 +112,22 @@ func (tl *TabList) Count() int { | |||||
| 111 | return len(tl.Tabs) | 112 | return len(tl.Tabs) | ||
| 112 | } | 113 | } | ||
| 113 | 114 | ||||
| 115 | + | // SetActive sets the active tab and updates the previous active tab. | |||
| 116 | + | func (tl *TabList) SetActive(id TabID) { | |||
| 117 | + | if id != tl.ActiveTabID && tl.ActiveTabID != "" { | |||
| 118 | + | tl.PreviousActiveTabID = tl.ActiveTabID | |||
| 119 | + | } | |||
| 120 | + | tl.ActiveTabID = id | |||
| 121 | + | } | |||
| 122 | + | ||||
| 123 | + | // TabAt returns the tab at the given 0-based index. | |||
| 124 | + | func (tl *TabList) TabAt(index int) *Tab { | |||
| 125 | + | if index < 0 || index >= len(tl.Tabs) { | |||
| 126 | + | return nil | |||
| 127 | + | } | |||
| 128 | + | return tl.Tabs[index] | |||
| 129 | + | } | |||
| 130 | + | ||||
| 114 | // Move moves a tab to a new position. | 131 | // Move moves a tab to a new position. | ||
| 115 | func (tl *TabList) Move(id TabID, newPos int) bool { | 132 | func (tl *TabList) Move(id TabID, newPos int) bool { | ||
| 116 | if newPos < 0 || newPos >= len(tl.Tabs) { | 133 | if newPos < 0 || newPos >= len(tl.Tabs) { | ||