Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a5477bde7 |
@@ -178,8 +178,7 @@ be said — `^w`, `--issues` and `snap --old` all name it and date it.
|
||||
`^o` puts a legend on the status line and the next key picks the order, so the
|
||||
list stays on screen while it rearranges itself:
|
||||
|
||||
sort: n·name p·pwr c·cpu% m·mem% #·snaps o·old w·why
|
||||
s·size u·cpus v·vc h·host a·ip r·reverse
|
||||
sort: n·name p·pwr c·cpu% m·mem% z·snaps o·old w·why s·size u·cpus v·vc h·host a·ip r·reverse
|
||||
|
||||
Each order comes with its own direction, because that is what asking for it
|
||||
means: by name is a to z, by processor load is the busiest first. `r` reverses
|
||||
@@ -194,21 +193,24 @@ direction the order runs. Machines that compare equal stay in name order, so
|
||||
flipping the direction on a screen full of identical figures does not reshuffle
|
||||
them.
|
||||
|
||||
Two lines, because thirteen choices do not fit across eighty columns and a
|
||||
legend that ran off the edge would hide the very choices it exists to offer.
|
||||
They are grouped where the meaning breaks: what the machine is doing and what it
|
||||
wants doing to it above, what it is made of and where it lives below. The second
|
||||
line takes the help line's row, which describes keys that do nothing while a
|
||||
menu is waiting for one.
|
||||
Thirteen choices are ninety-three columns, so a terminal narrower than that
|
||||
gets them on two lines instead of one that runs off the right-hand edge, hiding
|
||||
the very choices the legend exists to offer. They break where the meaning
|
||||
breaks — what the machine is doing and what it wants doing to it, then what it
|
||||
is made of and where it lives — and the second line takes the help line's row,
|
||||
which describes keys that do nothing while a menu is waiting for one. Decided
|
||||
at render time, so a window dragged wider gets the one line back:
|
||||
|
||||
sort: n·name p·pwr c·cpu% m·mem% z·snaps o·old w·why
|
||||
s·size u·cpus v·vc h·host a·ip r·reverse
|
||||
|
||||
Three of them are about the two columns that are new:
|
||||
|
||||
* `#` is by how many snapshots the machine is carrying, most first. Nought is a
|
||||
* `z` is by how many snapshots the machine is carrying, most first. Nought is a
|
||||
figure here and not a missing one — nothing to clean up is a fact about the
|
||||
machine — so a machine with none sorts where nought belongs: at the bottom
|
||||
going down, at the top coming back up. The key is not a letter because every
|
||||
letter with a mnemonic in it was taken, and a hash is what a count is written
|
||||
with anyway; `--sort snapshots` needs no shell quoting.
|
||||
going down, at the top coming back up. The letter carries no mnemonic because
|
||||
every letter that does was taken; `--sort snaps` spells it out.
|
||||
* `o` is by the age of the machine's *oldest* snapshot, oldest first, which is
|
||||
the order the housekeeping is done in — a different question from `#`, and the
|
||||
more useful one: one snapshot from March wants attention before six from this
|
||||
|
||||
+3
-1
@@ -1421,7 +1421,9 @@ func TestEveryQuestionWearsOneColour(t *testing.T) {
|
||||
lines int // how many of the bottom rows the question occupies
|
||||
}{
|
||||
{"the sort legend", func() {
|
||||
lines := sortLegend()
|
||||
// Narrower than the whole legend, so it is the two-line one: the
|
||||
// second line is the part that could quietly lose its colour.
|
||||
lines := sortLegend(60)
|
||||
b.prompt = &prompt{text: lines[0], col: colPrompt, more: lines[1]}
|
||||
b.edit = nil
|
||||
}, "sort:", 2},
|
||||
|
||||
@@ -45,10 +45,10 @@ var sortOrders = []sortOrder{
|
||||
// the machine — so a machine with none sorts where nought belongs, at the
|
||||
// bottom going down and at the top coming back up.
|
||||
//
|
||||
// The key is not a letter because every letter with a mnemonic in it is
|
||||
// taken, and a hash is what a count is written with anyway. `--sort
|
||||
// snapshots` on the command line needs no shell quoting.
|
||||
{key: '#', name: "snapshots", natural: true,
|
||||
// The key carries no mnemonic — every letter that does was taken — so it is
|
||||
// simply one that is free and easy to reach. The name is what the command
|
||||
// line takes: `--sort snapshots`, or `--sort snaps`.
|
||||
{key: 'z', name: "snapshots", natural: true,
|
||||
cmp: func(a, b vmRow) int { return cmpInt(a.snapCount(), b.snapCount()) }},
|
||||
// By how long the machine has been dragging its oldest snapshot along, the
|
||||
// oldest first — which is the order the housekeeping is done in. A machine
|
||||
@@ -233,32 +233,47 @@ func (b *browser) sortLabel() string {
|
||||
return arrow + " " + b.order().name
|
||||
}
|
||||
|
||||
// sortLegend is the choices, on the two lines they are grouped into: what the
|
||||
// sortLegend is the choices, laid out for a terminal of this width: one line
|
||||
// where they fit on one, and otherwise the two groups they fall into — what the
|
||||
// machine is doing and wants doing to it, then what it is made of and where it
|
||||
// lives. Terse by necessity — it shares the bottom of the screen with nothing
|
||||
// but itself — and the title says what the order is anyway, so nobody who
|
||||
// misses it is lost.
|
||||
// lives.
|
||||
//
|
||||
// The second line is indented under the first one's entries rather than under
|
||||
// its label, so the two read as one list and not as a sentence continued.
|
||||
func sortLegend() []string {
|
||||
// One line is the better answer and the usual one; two is what a narrow
|
||||
// terminal gets instead of a legend that runs off the right-hand edge, hiding
|
||||
// the very choices it exists to offer. Decided here, at render time, so a
|
||||
// window that is dragged wider gets the one line back — the same way the table
|
||||
// itself is fitted (fitColumns) and the sheet is wrapped.
|
||||
//
|
||||
// Terse either way: it shares the bottom of the screen with nothing but itself,
|
||||
// and the title says what the order is anyway, so nobody who misses it is lost.
|
||||
func sortLegend(cols int) []string {
|
||||
const label = "sort: "
|
||||
|
||||
lines := []string{label}
|
||||
entries := make([]string, 0, len(sortOrders)+1)
|
||||
for _, o := range sortOrders {
|
||||
entry := string(o.key) + "·" + shortName(o.name)
|
||||
if o.legendBreak && len(lines) == 1 {
|
||||
lines = append(lines, SR(" ", len(label)))
|
||||
}
|
||||
at := len(lines) - 1
|
||||
if !strings.HasSuffix(lines[at], " ") {
|
||||
lines[at] += " "
|
||||
}
|
||||
lines[at] += entry
|
||||
entries = append(entries, string(o.key)+"·"+shortName(o.name))
|
||||
}
|
||||
// Reverse is not an order of its own and goes at the end.
|
||||
entries = append(entries, string(sortReverse)+"·reverse")
|
||||
|
||||
if one := label + strings.Join(entries, " "); len([]rune(one)) <= cols {
|
||||
return []string{one}
|
||||
}
|
||||
|
||||
// Two, broken where the meaning breaks. The second line is indented under
|
||||
// the first one's entries rather than under its label, so the two read as
|
||||
// one list and not as a sentence continued.
|
||||
at := len(sortOrders)
|
||||
for i, o := range sortOrders {
|
||||
if o.legendBreak {
|
||||
at = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return []string{
|
||||
label + strings.Join(entries[:at], " "),
|
||||
SR(" ", len(label)) + strings.Join(entries[at:], " "),
|
||||
}
|
||||
// Reverse is not an order of its own and goes at the end of the last line.
|
||||
lines[len(lines)-1] += " " + string(sortReverse) + "·reverse"
|
||||
return lines
|
||||
}
|
||||
|
||||
// shortName is the legend's spelling: the title has room for the whole name, one
|
||||
@@ -298,7 +313,8 @@ func (b *browser) sortPrompt() {
|
||||
// it is still gvm waiting for a key, and that is one thing wearing one
|
||||
// colour. Where it takes two lines the second goes in place of the help
|
||||
// line, which says nothing that applies while a menu is up.
|
||||
lines := sortLegend()
|
||||
cols, _ := termSize()
|
||||
lines := sortLegend(cols)
|
||||
b.prompt = &prompt{text: lines[0], col: colPrompt}
|
||||
if len(lines) > 1 {
|
||||
b.prompt.more = strings.Join(lines[1:], " ")
|
||||
|
||||
+42
-5
@@ -221,7 +221,7 @@ func TestSortIsVisibleInTheTable(t *testing.T) {
|
||||
|
||||
// Every order is offered, and the legend fits a terminal of eighty.
|
||||
func TestSortLegend(t *testing.T) {
|
||||
lines := sortLegend()
|
||||
lines := sortLegend(80)
|
||||
legend := strings.Join(lines, "\n")
|
||||
for _, o := range sortOrders {
|
||||
if !strings.Contains(legend, string(o.key)+"·"+shortName(o.name)) {
|
||||
@@ -247,6 +247,35 @@ func TestSortLegend(t *testing.T) {
|
||||
t.Errorf("the legend wants %d lines, and there is room for two:\n%s", len(lines), legend)
|
||||
}
|
||||
|
||||
// One line wherever one line will do — which is every terminal wide enough
|
||||
// for it, and the usual case. A legend on two lines is what a narrow
|
||||
// terminal gets instead of one that runs off the edge.
|
||||
wide := sortLegend(200)
|
||||
if len(wide) != 1 {
|
||||
t.Errorf("a wide terminal gets the legend on %d lines:\n%s", len(wide), strings.Join(wide, "\n"))
|
||||
}
|
||||
if n := len([]rune(wide[0])); n > 200 {
|
||||
t.Errorf("the one-line legend is %d columns wide", n)
|
||||
}
|
||||
// And every choice is on it, so nothing is reachable only when the terminal
|
||||
// happens to be narrow.
|
||||
for _, o := range sortOrders {
|
||||
if !strings.Contains(wide[0], string(o.key)+"·"+shortName(o.name)) {
|
||||
t.Errorf("the one-line legend does not offer %q for %s: %s", string(o.key), o.name, wide[0])
|
||||
}
|
||||
}
|
||||
if !strings.Contains(wide[0], string(sortReverse)+"·reverse") {
|
||||
t.Errorf("the one-line legend does not offer the reverse: %s", wide[0])
|
||||
}
|
||||
// The width at which it gives up on one line is the width of the legend
|
||||
// itself, and not a number written down somewhere.
|
||||
if got := sortLegend(len([]rune(wide[0]))); len(got) != 1 {
|
||||
t.Error("the legend broke in two at exactly its own width")
|
||||
}
|
||||
if got := sortLegend(len([]rune(wide[0])) - 1); len(got) != 2 {
|
||||
t.Error("the legend stayed on one line one column too narrow for it")
|
||||
}
|
||||
|
||||
// Distinct letters, or one of them would be unreachable.
|
||||
seen := map[rune]bool{sortReverse: true}
|
||||
for _, o := range sortOrders {
|
||||
@@ -314,10 +343,10 @@ func TestSortBySnapshotCount(t *testing.T) {
|
||||
rows[1].snaps = []snapEntry{aged("a", 1), aged("b", 2), aged("c", 3)}
|
||||
rows[2].snaps = []snapEntry{aged("a", 1)}
|
||||
|
||||
if got := orderOf(t, rows, '#', true); got != "three one none" {
|
||||
if got := orderOf(t, rows, 'z', true); got != "three one none" {
|
||||
t.Errorf("most snapshots first gave %q", got)
|
||||
}
|
||||
if got := orderOf(t, rows, '#', false); got != "none one three" {
|
||||
if got := orderOf(t, rows, 'z', false); got != "none one three" {
|
||||
t.Errorf("fewest first gave %q", got)
|
||||
}
|
||||
}
|
||||
@@ -356,14 +385,22 @@ func TestSortByIssues(t *testing.T) {
|
||||
}
|
||||
|
||||
// orderOf sorts the rows given by one order and returns the names in order.
|
||||
//
|
||||
// A key that is not an order at all is fatal here rather than left to sort by
|
||||
// name: the browser's default order is index nought, so a test naming a letter
|
||||
// that has been renamed would go on passing while checking the name order.
|
||||
func orderOf(t *testing.T, rows []vmRow, key rune, desc bool) string {
|
||||
t.Helper()
|
||||
b := &browser{rows: append([]vmRow(nil), rows...), sortDesc: desc}
|
||||
found := false
|
||||
for i, o := range sortOrders {
|
||||
if o.key == key {
|
||||
b.sortBy = i
|
||||
b.sortBy, found = i, true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("%q is not one of the sort orders", string(key))
|
||||
}
|
||||
b.applySort()
|
||||
|
||||
var names []string
|
||||
@@ -384,7 +421,7 @@ func TestTheTwoSnapshotOrdersAskDifferentThings(t *testing.T) {
|
||||
rows[0].snaps = []snapEntry{aged("a", 1), aged("b", 1), aged("c", 1), aged("d", 1)}
|
||||
rows[1].snaps = []snapEntry{aged("march", 200)}
|
||||
|
||||
if got := orderOf(t, rows, '#', true); got != "many-new one-ancient" {
|
||||
if got := orderOf(t, rows, 'z', true); got != "many-new one-ancient" {
|
||||
t.Errorf("by count: %q", got)
|
||||
}
|
||||
if got := orderOf(t, rows, 'o', true); got != "one-ancient many-new" {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.1.2
|
||||
1.1.3
|
||||
|
||||
Reference in New Issue
Block a user