history_policy.go RawHistoryFolder
1package daemon
2
3import (
4 "context"
5 "time"
6
7 vt "github.com/bnema/vev-vt"
8 "github.com/bnema/vev/internal/domain"
9)
10
11func historyConfigFor(policy domain.ScrollbackConfig) vt.HistoryConfig {
12 if policy.Megabytes == 0 {
13 return vt.HistoryConfig{}
14 }
15 return vt.HistoryConfig{MaxBytes: policy.Megabytes * 1_000_000, MaxRows: policy.Lines}
16}
17
18func (d *Daemon) currentHistoryConfig() vt.HistoryConfig {
19 policy := d.scrollbackConfig.Load()
20 if policy == nil {
21 return historyConfigFor(domain.DefaultScrollbackConfig())
22 }
23 return historyConfigFor(*policy)
24}
25
26// historyPanes gathers references without holding a session/tab lock while
27// touching history. Include hidden floating panes; deduplicate a pane that moves
28// between tabs while the separate session snapshots are being collected.
29func (d *Daemon) historyPanes() []*pane {
30 var panes []*pane
31 seen := make(map[*pane]struct{})
32 for _, sess := range d.sessionsSnapshot() {
33 sess.mu.Lock()
34 tabs := append([]*tab(nil), sess.tabs...)
35 sess.mu.Unlock()
36 for _, tb := range tabs {
37 if tb == nil {
38 continue
39 }
40 tb.mu.Lock()
41 current := tb.panesSnapshot()
42 if tb.floating.pane != nil {
43 current = append(current, tb.floating.pane)
44 }
45 tb.mu.Unlock()
46 for _, p := range current {
47 if p == nil {
48 continue
49 }
50 if _, exists := seen[p]; exists {
51 continue
52 }
53 seen[p] = struct{}{}
54 panes = append(panes, p)
55 }
56 }
57 }
58 return panes
59}
60
61func (d *Daemon) applyHistoryLimitsToPane(p *pane) {
62 p.mu.Lock()
63 var err error
64 if p.history != nil {
65 // Load inside the pane lock so an older concurrent reload cannot install
66 // a stale policy after a newer reload has already published its value.
67 err = p.history.SetLimits(d.currentHistoryConfig())
68 }
69 p.mu.Unlock()
70 if err != nil {
71 d.log.Warn("cannot apply scrollback limits", "err", err)
72 }
73}
74
75func (d *Daemon) applyHistoryLimits() {
76 for _, p := range d.historyPanes() {
77 d.applyHistoryLimitsToPane(p)
78 }
79}
80
81// Keep background compression infrequent and bounded; it must not compete
82// with interactive output when many panes retain history.
83const historyMaintenanceInterval = 5 * time.Second
84
85// historyMaintenance is application scheduling, not a VT-owned worker. It
86// visits one history page per pane per interval and skips busy/resizing panes.
87func (d *Daemon) historyMaintenance(ctx context.Context) {
88 timer := d.clock.NewTimer(historyMaintenanceInterval)
89 defer timer.Stop()
90 for {
91 select {
92 case <-ctx.Done():
93 return
94 case <-timer.C():
95 d.compressIdleHistories()
96 timer.Reset(historyMaintenanceInterval)
97 }
98 }
99}
100
101func (d *Daemon) compressIdleHistories() {
102 for _, p := range d.historyPanes() {
103 if !p.mu.TryLock() {
104 continue
105 }
106 var err error
107 if p.history != nil && !p.resizeApplying {
108 _, err = p.history.CompressIdle(1)
109 }
110 p.mu.Unlock()
111 if err != nil {
112 d.log.Warn("cannot compress idle scrollback", "err", err)
113 }
114 }
115}