Add mouse tests; tidy TUI idioms
This commit is contained in:
@@ -245,6 +245,6 @@ func (v *dupView) key(m *Model, k string) (bool, tea.Cmd) {
|
||||
}
|
||||
|
||||
func (v *dupView) wheel(m *Model, up bool) tea.Cmd {
|
||||
v.tb.move(map[bool]int{true: -3, false: 3}[up], len(v.rows))
|
||||
v.tb.scroll(up, len(v.rows))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -112,7 +112,11 @@ func (v *infoView) paint(m *Model, cv *Canvas, r treemap.Rect) {
|
||||
line("One filesystem", fmt.Sprint(st.OneFileSystem), value)
|
||||
line("Follow symlinks", st.Follow, value)
|
||||
line("Size mode", m.sizeMode.String(), value)
|
||||
line("Units", map[bool]string{true: "SI (1000)", false: "IEC (1024)"}[textutil.SI], value)
|
||||
units := "IEC (1024)"
|
||||
if textutil.SI {
|
||||
units = "SI (1000)"
|
||||
}
|
||||
line("Units", units, value)
|
||||
if root.Flags&inventory.FlagAllocUnknown != 0 {
|
||||
line("Note", "allocated size unavailable on this platform", Style{FG: t.Warn, BG: t.Bg})
|
||||
}
|
||||
@@ -154,6 +158,6 @@ func (v *infoView) key(m *Model, k string) (bool, tea.Cmd) {
|
||||
}
|
||||
|
||||
func (v *infoView) wheel(m *Model, up bool) tea.Cmd {
|
||||
v.tb.move(map[bool]int{true: -3, false: 3}[up], len(m.tree.Stats.Errors))
|
||||
v.tb.scroll(up, len(m.tree.Stats.Errors))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ func (m *Model) describeSelection() string {
|
||||
parts = append(parts, fmt.Sprintf("%d errors", n.Errors))
|
||||
}
|
||||
if m.opts.Diff != nil {
|
||||
parts = append(parts, textutil.SignedSize(m.opts.Diff.Delta(id, m.sizeMode))+" since "+"snapshot")
|
||||
parts = append(parts, textutil.SignedSize(m.opts.Diff.Delta(id, m.sizeMode))+" vs old snapshot")
|
||||
}
|
||||
return strings.Join(parts, " · ")
|
||||
}
|
||||
@@ -403,6 +403,10 @@ func (m *Model) paintStatus(cv *Canvas, r treemap.Rect, class sizeClass) {
|
||||
if x+textutil.Width(s) > r.X+avail {
|
||||
break
|
||||
}
|
||||
x += cv.Text(x, r.Y, s, avail-x, Style{FG: p.fg, BG: t.StatusBg, Attr: map[bool]Attr{true: Bold}[i == 0]})
|
||||
st := Style{FG: p.fg, BG: t.StatusBg}
|
||||
if i == 0 {
|
||||
st.Attr = Bold
|
||||
}
|
||||
x += cv.Text(x, r.Y, s, avail-x, st)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,17 +241,10 @@ func (m *Model) paintSearch(cv *Canvas, r treemap.Rect) {
|
||||
if s.ed.cur < len(text) {
|
||||
under = textutil.Sanitize(string(text[s.ed.cur]))
|
||||
}
|
||||
cv.Text(cx, r.Y, under, 2, Style{FG: t.PanelBg, BG: t.Fg, Attr: Reverse * Attr(boolInt(t.Mono))})
|
||||
cv.Text(cx, r.Y, under, 2, Style{FG: t.PanelBg, BG: t.Fg, Attr: m.monoRev()})
|
||||
cv.TextRight(r.X, r.Y, r.W-1, textutil.Truncate(status, sw), statusSt)
|
||||
}
|
||||
|
||||
func boolInt(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Model) paintFilterSummary(cv *Canvas, r treemap.Rect) {
|
||||
t := m.theme
|
||||
s := &m.search
|
||||
|
||||
+13
-1
@@ -29,6 +29,15 @@ type table struct {
|
||||
height int // rows visible in the last paint
|
||||
}
|
||||
|
||||
// scroll moves the cursor three rows for a mouse wheel step.
|
||||
func (tb *table) scroll(up bool, n int) {
|
||||
if up {
|
||||
tb.move(-3, n)
|
||||
} else {
|
||||
tb.move(3, n)
|
||||
}
|
||||
}
|
||||
|
||||
func (tb *table) move(d, n int) {
|
||||
if n == 0 {
|
||||
tb.cursor = 0
|
||||
@@ -177,7 +186,10 @@ func (m *Model) paintBar(cv *Canvas, x, y, w int, frac float64, col Color, sel b
|
||||
ch = parts[eighths]
|
||||
}
|
||||
if t.ASCII {
|
||||
ch = map[bool]string{true: "#", false: " "}[eighths >= 4]
|
||||
ch = " "
|
||||
if eighths >= 4 {
|
||||
ch = "#"
|
||||
}
|
||||
}
|
||||
eighths -= 8
|
||||
st := Style{FG: col, BG: bg}
|
||||
|
||||
@@ -418,3 +418,39 @@ func BenchmarkResizeRelayout(b *testing.B) {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func TestMouse(t *testing.T) {
|
||||
m := newTestModel(t)
|
||||
send(m, size(120, 40))
|
||||
m.View()
|
||||
// Click the centre of a block other than the selected one.
|
||||
var target treemap.Block
|
||||
for _, b := range m.tm.blocks {
|
||||
if b.ID != m.sel && b.ID >= 0 && m.tree.Node(inventory.NodeID(b.ID)).IsDir() && b.W > 4 {
|
||||
target = b
|
||||
break
|
||||
}
|
||||
}
|
||||
click := tea.MouseClickMsg{X: target.X + target.W/2, Y: target.Y + target.H/2, Button: tea.MouseLeft}
|
||||
send(m, click)
|
||||
if m.sel != target.ID {
|
||||
t.Fatalf("click selected %d, want %d", m.sel, target.ID)
|
||||
}
|
||||
m.View()
|
||||
send(m, click) // second click within the double-click window
|
||||
if m.zoom != inventory.NodeID(target.ID) {
|
||||
t.Fatalf("double click should zoom into %d (zoom=%d)", target.ID, m.zoom)
|
||||
}
|
||||
m.View()
|
||||
send(m, tea.MouseWheelMsg{X: 50, Y: 20, Button: tea.MouseWheelDown})
|
||||
if m.zoom != 0 {
|
||||
t.Fatal("wheel down should zoom out")
|
||||
}
|
||||
// A modal swallows clicks; clicking outside closes it.
|
||||
send(m, key("?"))
|
||||
m.View()
|
||||
send(m, tea.MouseClickMsg{X: 0, Y: 39, Button: tea.MouseLeft})
|
||||
if len(m.modals) != 0 {
|
||||
t.Fatal("click outside modal should close it")
|
||||
}
|
||||
}
|
||||
|
||||
+22
-22
@@ -98,7 +98,7 @@ func (v *listView) key(m *Model, k string) (bool, tea.Cmd) {
|
||||
|
||||
func (v *listView) wheel(m *Model, up bool) tea.Cmd {
|
||||
v.sync(m)
|
||||
v.tb.move(map[bool]int{true: -3, false: 3}[up], len(m.kidsOf(m.zoom).ids))
|
||||
v.tb.scroll(up, len(m.kidsOf(m.zoom).ids))
|
||||
v.sync(m)
|
||||
return nil
|
||||
}
|
||||
@@ -106,16 +106,16 @@ func (v *listView) wheel(m *Model, up bool) tea.Cmd {
|
||||
// ---- Extension statistics ------------------------------------------------
|
||||
|
||||
type extView struct {
|
||||
tb table
|
||||
cache []inventory.ExtStat
|
||||
key_ [2]int
|
||||
tb table
|
||||
cache []inventory.ExtStat
|
||||
cacheKey [2]int
|
||||
}
|
||||
|
||||
func (*extView) name() string { return "Types" }
|
||||
|
||||
func (v *extView) stats(m *Model) []inventory.ExtStat {
|
||||
k := [2]int{int(m.zoom), m.dataVer*2 + int(m.sizeMode)}
|
||||
if v.cache == nil || v.key_ != k {
|
||||
if v.cache == nil || v.cacheKey != k {
|
||||
v.cache = m.tree.ExtStats(m.zoom)
|
||||
sort.Slice(v.cache, func(i, j int) bool {
|
||||
a, b := v.cache[i], v.cache[j]
|
||||
@@ -128,7 +128,7 @@ func (v *extView) stats(m *Model) []inventory.ExtStat {
|
||||
}
|
||||
return a.Ext < b.Ext
|
||||
})
|
||||
v.key_ = k
|
||||
v.cacheKey = k
|
||||
}
|
||||
return v.cache
|
||||
}
|
||||
@@ -189,7 +189,7 @@ func (v *extView) key(m *Model, k string) (bool, tea.Cmd) {
|
||||
}
|
||||
|
||||
func (v *extView) wheel(m *Model, up bool) tea.Cmd {
|
||||
v.tb.move(map[bool]int{true: -3, false: 3}[up], len(v.stats(m)))
|
||||
v.tb.scroll(up, len(v.stats(m)))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -212,12 +212,12 @@ var topNames = [...]string{"Largest files", "Largest directories (own files)", "
|
||||
"Newest large files", "Most files", "Sparse files", "Hard-linked files"}
|
||||
|
||||
type topView struct {
|
||||
tb table
|
||||
mode topMode
|
||||
ids []inventory.NodeID
|
||||
vals []int64
|
||||
key_ [3]int
|
||||
limit int
|
||||
tb table
|
||||
mode topMode
|
||||
ids []inventory.NodeID
|
||||
vals []int64
|
||||
cacheKey [3]int
|
||||
limit int
|
||||
}
|
||||
|
||||
func (*topView) name() string { return "Top" }
|
||||
@@ -226,10 +226,10 @@ const topLimit = 500
|
||||
|
||||
func (v *topView) compute(m *Model) {
|
||||
k := [3]int{int(m.zoom), m.dataVer*2 + int(m.sizeMode), int(v.mode)}
|
||||
if v.ids != nil && v.key_ == k {
|
||||
if v.ids != nil && v.cacheKey == k {
|
||||
return
|
||||
}
|
||||
v.key_ = k
|
||||
v.cacheKey = k
|
||||
tr := m.tree
|
||||
isFile := func(n *inventory.Node) bool {
|
||||
return n.Kind == inventory.KindFile && n.Flags&(inventory.FlagHardlinkDup|inventory.FlagDeleted) == 0
|
||||
@@ -378,16 +378,16 @@ func (v *topView) key(m *Model, k string) (bool, tea.Cmd) {
|
||||
}
|
||||
|
||||
func (v *topView) wheel(m *Model, up bool) tea.Cmd {
|
||||
v.tb.move(map[bool]int{true: -3, false: 3}[up], len(v.ids))
|
||||
v.tb.scroll(up, len(v.ids))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---- Snapshot comparison -----------------------------------------------------
|
||||
|
||||
type changesView struct {
|
||||
tb table
|
||||
rows []snapshot.Change
|
||||
key_ int
|
||||
tb table
|
||||
rows []snapshot.Change
|
||||
cacheKey int
|
||||
}
|
||||
|
||||
func (*changesView) name() string { return "Changes" }
|
||||
@@ -395,9 +395,9 @@ func (*changesView) name() string { return "Changes" }
|
||||
func (v *changesView) paint(m *Model, cv *Canvas, r treemap.Rect) {
|
||||
t := m.theme
|
||||
d := m.opts.Diff
|
||||
if v.rows == nil || v.key_ != int(m.sizeMode) {
|
||||
if v.rows == nil || v.cacheKey != int(m.sizeMode) {
|
||||
v.rows = d.Changes(1000, m.sizeMode)
|
||||
v.key_ = int(m.sizeMode)
|
||||
v.cacheKey = int(m.sizeMode)
|
||||
}
|
||||
var maxAbs int64 = 1
|
||||
for _, c := range v.rows {
|
||||
@@ -452,6 +452,6 @@ func (v *changesView) key(m *Model, k string) (bool, tea.Cmd) {
|
||||
}
|
||||
|
||||
func (v *changesView) wheel(m *Model, up bool) tea.Cmd {
|
||||
v.tb.move(map[bool]int{true: -3, false: 3}[up], len(v.rows))
|
||||
v.tb.scroll(up, len(v.rows))
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user