v0.20.0 c741a39

tab.go RawHistoryFolder

1package entity
2
3import (
4	"fmt"
5	"time"
6)
7
8// TabID uniquely identifies a tab.
9type TabID string
10
11// Tab represents a browser tab containing a workspace.
12// Tabs are the top-level container in the browser's tab bar.
13type Tab struct {
14	ID        TabID
15	Name      string     // Display name (often derived from active pane title)
16	Workspace *Workspace // The workspace this tab contains
17	Position  int        // Position in the tab bar (0-indexed)
18	IsPinned  bool       // Pinned tabs stay at the left
19	CreatedAt time.Time
20}
21
22// NewTab creates a new tab with an initial pane.
23func NewTab(tabID TabID, workspaceID WorkspaceID, initialPane *Pane) *Tab {
24	return &Tab{
25		ID:        tabID,
26		Workspace: NewWorkspace(workspaceID, initialPane),
27		Position:  0,
28		CreatedAt: time.Now(),
29	}
30}
31
32// Title returns the display title for the tab.
33// Uses the tab's Name if set, otherwise returns "Tab N" based on position.
34func (t *Tab) Title() string {
35	if t.Name != "" {
36		return t.Name
37	}
38	return fmt.Sprintf("Tab %d", t.Position+1)
39}
40
41// PaneCount returns the number of panes in this tab's workspace.
42func (t *Tab) PaneCount() int {
43	if t.Workspace == nil {
44		return 0
45	}
46	return t.Workspace.PaneCount()
47}
48
49// TabList manages an ordered collection of tabs.
50type TabList struct {
51	Tabs                []*Tab
52	ActiveTabID         TabID
53	PreviousActiveTabID TabID // Tracks last active tab for Alt+Tab style switching
54}
55
56// NewTabList creates an empty tab list.
57func NewTabList() *TabList {
58	return &TabList{
59		Tabs: make([]*Tab, 0),
60	}
61}
62
63// Add appends a tab to the list.
64func (tl *TabList) Add(tab *Tab) {
65	tab.Position = len(tl.Tabs)
66	tl.Tabs = append(tl.Tabs, tab)
67	if tl.ActiveTabID == "" {
68		tl.ActiveTabID = tab.ID
69	}
70}
71
72// Remove removes a tab by ID and reindexes positions.
73func (tl *TabList) Remove(id TabID) bool {
74	for i, tab := range tl.Tabs {
75		if tab.ID == id {
76			tl.Tabs = append(tl.Tabs[:i], tl.Tabs[i+1:]...)
77			// Reindex positions
78			for j := i; j < len(tl.Tabs); j++ {
79				tl.Tabs[j].Position = j
80			}
81			// Update active tab if needed
82			if tl.ActiveTabID == id && len(tl.Tabs) > 0 {
83				if i < len(tl.Tabs) {
84					tl.ActiveTabID = tl.Tabs[i].ID
85				} else {
86					tl.ActiveTabID = tl.Tabs[len(tl.Tabs)-1].ID
87				}
88			}
89			return true
90		}
91	}
92	return false
93}
94
95// Find returns a tab by ID.
96func (tl *TabList) Find(id TabID) *Tab {
97	for _, tab := range tl.Tabs {
98		if tab.ID == id {
99			return tab
100		}
101	}
102	return nil
103}
104
105// ActiveTab returns the currently active tab.
106func (tl *TabList) ActiveTab() *Tab {
107	return tl.Find(tl.ActiveTabID)
108}
109
110// Count returns the number of tabs.
111func (tl *TabList) Count() int {
112	return len(tl.Tabs)
113}
114
115// SetActive sets the active tab and updates the previous active tab.
116func (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.
124func (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
131// Move moves a tab to a new position.
132func (tl *TabList) Move(id TabID, newPos int) bool {
133	if newPos < 0 || newPos >= len(tl.Tabs) {
134		return false
135	}
136	var tab *Tab
137	var oldPos int
138	for i, t := range tl.Tabs {
139		if t.ID == id {
140			tab = t
141			oldPos = i
142			break
143		}
144	}
145	if tab == nil {
146		return false
147	}
148	// Remove from old position
149	tl.Tabs = append(tl.Tabs[:oldPos], tl.Tabs[oldPos+1:]...)
150	// Insert at new position
151	tl.Tabs = append(tl.Tabs[:newPos], append([]*Tab{tab}, tl.Tabs[newPos:]...)...)
152	// Reindex all
153	for i := range tl.Tabs {
154		tl.Tabs[i].Position = i
155	}
156	return true
157}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close