v0.26.1 75cd777

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 {
83				if len(tl.Tabs) > 0 {
84					if i < len(tl.Tabs) {
85						tl.ActiveTabID = tl.Tabs[i].ID
86					} else {
87						tl.ActiveTabID = tl.Tabs[len(tl.Tabs)-1].ID
88					}
89				} else {
90					tl.ActiveTabID = ""
91					tl.PreviousActiveTabID = ""
92				}
93			}
94			return true
95		}
96	}
97	return false
98}
99
100// Find returns a tab by ID.
101func (tl *TabList) Find(id TabID) *Tab {
102	for _, tab := range tl.Tabs {
103		if tab.ID == id {
104			return tab
105		}
106	}
107	return nil
108}
109
110// ActiveTab returns the currently active tab.
111func (tl *TabList) ActiveTab() *Tab {
112	return tl.Find(tl.ActiveTabID)
113}
114
115// Count returns the number of tabs.
116func (tl *TabList) Count() int {
117	return len(tl.Tabs)
118}
119
120// SetActive sets the active tab and updates the previous active tab.
121func (tl *TabList) SetActive(id TabID) {
122	if id != tl.ActiveTabID && tl.ActiveTabID != "" {
123		tl.PreviousActiveTabID = tl.ActiveTabID
124	}
125	tl.ActiveTabID = id
126}
127
128// TabAt returns the tab at the given 0-based index.
129func (tl *TabList) TabAt(index int) *Tab {
130	if index < 0 || index >= len(tl.Tabs) {
131		return nil
132	}
133	return tl.Tabs[index]
134}
135
136// Move moves a tab to a new position.
137func (tl *TabList) Move(id TabID, newPos int) bool {
138	if newPos < 0 || newPos >= len(tl.Tabs) {
139		return false
140	}
141	var tab *Tab
142	var oldPos int
143	for i, t := range tl.Tabs {
144		if t.ID == id {
145			tab = t
146			oldPos = i
147			break
148		}
149	}
150	if tab == nil {
151		return false
152	}
153	// Remove from old position
154	tl.Tabs = append(tl.Tabs[:oldPos], tl.Tabs[oldPos+1:]...)
155	// Insert at new position
156	tl.Tabs = append(tl.Tabs[:newPos], append([]*Tab{tab}, tl.Tabs[newPos:]...)...)
157	// Reindex all
158	for i := range tl.Tabs {
159		tl.Tabs[i].Position = i
160	}
161	return true
162}
163
164// ReplaceFrom replaces this TabList's contents with those from another TabList.
165// This modifies in-place so existing references to this TabList remain valid.
166func (tl *TabList) ReplaceFrom(other *TabList) {
167	if other == nil {
168		tl.Tabs = make([]*Tab, 0)
169		tl.ActiveTabID = ""
170		tl.PreviousActiveTabID = ""
171		return
172	}
173	tl.Tabs = other.Tabs
174	tl.ActiveTabID = other.ActiveTabID
175	tl.PreviousActiveTabID = other.PreviousActiveTabID
176}

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