package app func visibleSessionTree(children []*Child, focusID string) []*Child { rootID := activeRootID(children, focusID) if rootID == "" { return nil } out := make([]*Child, 0, len(children)) for _, c := range children { if c.Status() != StatusRunning { continue } if c.ID == rootID || c.ParentID == rootID { out = append(out, c) } } return out } func activeRootID(children []*Child, focusID string) string { if focusID != "" { for _, c := range children { if c.ID != focusID { continue } if c.ParentID == "" { return c.ID } if parent := findChildInSnapshot(children, c.ParentID); parent != nil { return parent.ID } return "" } } for _, c := range children { if c.ParentID == "" && c.Status() == StatusRunning { return c.ID } } return "" } func findChildInSnapshot(children []*Child, id string) *Child { for _, c := range children { if c.ID == id { return c } } return nil } func firstRunningTopLevel(children []*Child) *Child { for _, c := range children { if c.ParentID == "" && c.Status() == StatusRunning { return c } } return nil } // runningTopLevels lists every running top-level session in the order // they appear in the snapshot — the same order the tab bar uses, so // Ctrl+A/D navigation matches what the user sees on screen. func runningTopLevels(children []*Child) []*Child { out := make([]*Child, 0, len(children)) for _, c := range children { if c.ParentID == "" && c.Status() == StatusRunning { out = append(out, c) } } return out } // nextTabID returns the id of the top-level session `step` positions // away from the current focus in the runningTopLevels list, wrapping // at both ends. Returns "" when there's nothing to switch to. func nextTabID(children []*Child, focusID string, step int) string { roots := runningTopLevels(children) if len(roots) == 0 { return "" } rootID := activeRootID(children, focusID) idx := -1 for i, r := range roots { if r.ID == rootID { idx = i break } } if idx < 0 { idx = 0 } idx = (idx + step) % len(roots) if idx < 0 { idx += len(roots) } if roots[idx].ID == focusID { return "" } return roots[idx].ID } // currentTabFlat returns the focused tab's processes (root first, then // its running children) in display order. Used to step focus with // Ctrl+W/S. func currentTabFlat(children []*Child, focusID string) []*Child { rootID := activeRootID(children, focusID) if rootID == "" { return nil } out := make([]*Child, 0, 4) for _, c := range children { if c.ID == rootID && c.Status() == StatusRunning { out = append(out, c) break } } for _, c := range children { if c.ParentID == rootID && c.Status() == StatusRunning { out = append(out, c) } } return out } // nextChildID returns the process id `step` positions away from the // current focus inside its tab, wrapping at both ends. Empty when // there's only one process in the tab. func nextChildID(children []*Child, focusID string, step int) string { flat := currentTabFlat(children, focusID) if len(flat) < 2 { return "" } idx := -1 for i, c := range flat { if c.ID == focusID { idx = i break } } if idx < 0 { idx = 0 } idx = (idx + step) % len(flat) if idx < 0 { idx += len(flat) } if flat[idx].ID == focusID { return "" } return flat[idx].ID }