main 5041353

sessions_cli.go RawHistoryFolder

1package styles
2
3import (
4	"fmt"
5	"strings"
6
7	"github.com/bnema/dumber/internal/application/usecase"
8	"github.com/bnema/dumber/internal/domain/entity"
9)
10
11// SessionsCLIRenderer renders non-interactive CLI output for sessions subcommands
12// (e.g. `dumber sessions list`, `restore`, `delete`).
13type SessionsCLIRenderer struct {
14	theme *Theme
15}
16
17func NewSessionsCLIRenderer(theme *Theme) *SessionsCLIRenderer {
18	return &SessionsCLIRenderer{theme: theme}
19}
20
21func (r *SessionsCLIRenderer) RenderEmptyList() string {
22	return r.theme.Subtle.Render("No saved sessions found.")
23}
24
25func (r *SessionsCLIRenderer) RenderList(items []entity.SessionInfo, limit int) string {
26	if len(items) == 0 {
27		return r.RenderEmptyList()
28	}
29
30	var b strings.Builder
31	title := fmt.Sprintf("%s %s", r.theme.Highlight.Render(IconSessionStack), r.theme.Title.Render("Sessions"))
32	b.WriteString(title)
33	if limit > 0 {
34		b.WriteString(r.theme.Subtle.Render(fmt.Sprintf(" (showing up to %d)", limit)))
35	}
36	b.WriteString("\n\n")
37
38	for _, s := range items {
39		b.WriteString(r.renderOne(s))
40		b.WriteString("\n")
41	}
42
43	b.WriteString("\n")
44	b.WriteString(r.theme.Subtle.Render("Tip: use `dumber sessions` for the interactive browser."))
45	return b.String()
46}
47
48func (r *SessionsCLIRenderer) renderOne(info entity.SessionInfo) string {
49	status := " "
50	statusStyle := r.theme.Subtle
51	switch {
52	case info.IsCurrent:
53		status = "●"
54		statusStyle = r.theme.Highlight
55	case info.IsActive:
56		status = "○"
57		statusStyle = r.theme.Subtle
58	}
59
60	id := r.theme.Highlight.Render(string(info.Session.ID))
61	tabs := r.theme.BadgeMuted.Render(fmt.Sprintf("%d tabs", info.TabCount))
62	panes := r.theme.BadgeMuted.Render(fmt.Sprintf("%d panes", info.PaneCount))
63	updated := r.theme.Subtle.Render(usecase.GetRelativeTime(info.UpdatedAt))
64
65	return fmt.Sprintf("%s %s  %s %s  %s",
66		statusStyle.Render(status),
67		id,
68		tabs,
69		panes,
70		updated,
71	)
72}
73
74func (r *SessionsCLIRenderer) RenderRestoreStarted(sessionID entity.SessionID) string {
75	return fmt.Sprintf("%s Restoring session %s...",
76		r.theme.SuccessStyle.Render(IconRestore),
77		r.theme.Highlight.Render(string(sessionID)),
78	)
79}
80
81func (r *SessionsCLIRenderer) RenderDeleted(sessionID entity.SessionID) string {
82	return fmt.Sprintf("%s Session %s deleted.",
83		r.theme.SuccessStyle.Render(IconCheck),
84		r.theme.Highlight.Render(string(sessionID)),
85	)
86}
87
88func (r *SessionsCLIRenderer) RenderError(err error) string {
89	return fmt.Sprintf("%s %v", r.theme.ErrorStyle.Render(IconX), err)
90}

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