v0.29.1 d92a60f

theme_watch.go RawHistoryFolder

1package modes
2
3import (
4	"context"
5	"errors"
6	"os"
7	"time"
8
9	"github.com/bnema/zut/packages/tui"
10)
11
12const (
13	themeWatchInterval = 500 * time.Millisecond
14	themeMissingGrace  = 2 * time.Second
15)
16
17type themeWatchEvent struct {
18	path    string
19	source  *tui.ThemeSource
20	err     error
21	deleted bool
22}
23
24func (i *Interactive) restartThemeWatch() {
25	if i.themeWatchCancel != nil {
26		i.themeWatchCancel()
27		i.themeWatchCancel = nil
28	}
29	if i.runCtx == nil || i.activeThemeSource == nil {
30		return
31	}
32	ctx, cancel := context.WithCancel(i.runCtx)
33	i.themeWatchCancel = cancel
34	interval := i.cfg.ThemeWatchInterval
35	if interval <= 0 {
36		interval = themeWatchInterval
37	}
38	go watchThemeSource(ctx, i.cfg.ZutHome, i.cfg.EffectiveThemeName, i.activeThemeSource, interval, i.themeWatchEvents)
39}
40
41func (i *Interactive) stopThemeWatch() {
42	if i.themeWatchCancel != nil {
43		i.themeWatchCancel()
44		i.themeWatchCancel = nil
45	}
46}
47
48func watchThemeSource(ctx context.Context, home, preference string, accepted *tui.ThemeSource, interval time.Duration, events chan<- themeWatchEvent) {
49	if accepted == nil || accepted.Path == "" {
50		return
51	}
52	ticker := time.NewTicker(interval)
53	defer ticker.Stop()
54	watchThemeSourceTicks(ctx, home, preference, accepted, ticker.C, events, nil)
55}
56
57// watchThemeSourceTicks is the test seam for polling: tests advance explicit
58// timestamps and may receive a processed acknowledgement instead of sleeping.
59func watchThemeSourceTicks(ctx context.Context, home, preference string, accepted *tui.ThemeSource, ticks <-chan time.Time, events chan<- themeWatchEvent, processed chan<- struct{}) {
60	if accepted == nil || accepted.Path == "" {
61		return
62	}
63	lastDigest := accepted.Digest
64	var missingSince time.Time
65	lastError := ""
66	for {
67		select {
68		case <-ctx.Done():
69			return
70		case now := <-ticks:
71			stop := false
72			if _, statErr := os.Stat(accepted.Path); errors.Is(statErr, os.ErrNotExist) {
73				if missingSince.IsZero() {
74					missingSince = now
75				}
76				if now.Sub(missingSince) >= themeMissingGrace {
77					sendThemeWatchEvent(ctx, events, themeWatchEvent{path: accepted.Path, deleted: true})
78					stop = true
79				}
80			} else {
81				// A present-but-invalid revision remains watchable and must restart
82				// the deletion grace period just like a valid replacement does.
83				missingSince = time.Time{}
84				source, err := tui.LoadThemeSource(home, preference)
85				if err == nil && source != nil {
86					lastError = ""
87					if source.Path == accepted.Path && source.Digest != lastDigest {
88						lastDigest = source.Digest
89						sendThemeWatchEvent(ctx, events, themeWatchEvent{path: accepted.Path, source: source})
90					}
91				} else if err != nil && err.Error() != lastError {
92					lastError = err.Error()
93					sendThemeWatchEvent(ctx, events, themeWatchEvent{path: accepted.Path, err: err})
94				}
95			}
96			acknowledgeThemeWatchTick(ctx, processed)
97			if stop {
98				return
99			}
100		}
101	}
102}
103
104func acknowledgeThemeWatchTick(ctx context.Context, processed chan<- struct{}) {
105	if processed == nil {
106		return
107	}
108	select {
109	case processed <- struct{}{}:
110	case <-ctx.Done():
111	}
112}
113
114func sendThemeWatchEvent(ctx context.Context, events chan<- themeWatchEvent, event themeWatchEvent) {
115	select {
116	case events <- event:
117	case <-ctx.Done():
118	}
119}

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 1

Tags 476

↑↓ moveenter openesc close