Skip to content

Commit 6857269

Browse files
author
gluck
committed
cli: the kill question rides the bar and costs no height
The dialog was a second copy of a question the bar was already carrying: the sentence in the alert slot, the answers under it in a row of their own. Now the alert holds 'kill <id>?', the command slot holds [y/N], and the view underneath is untouched.
1 parent 88c9e49 commit 6857269

6 files changed

Lines changed: 32 additions & 52 deletions

File tree

‎internal/cli/confirm.go‎

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,35 @@ package cli
22

33
import "strings"
44

5-
// A DESTRUCTIVE VERB ASKS FIRST. `x` on a row and `:kill` put a question on
6-
// the screen and hold the keyboard until it is answered; `X` on a row does
5+
// A DESTRUCTIVE VERB ASKS FIRST. `x` on a row and `:kill` put a question to
6+
// the reader and hold the keyboard until it is answered; `X` on a row does
77
// the same work with no question at all.
88
//
9-
// The question is a pit (so the bar names it and every key that is not an
10-
// answer is swallowed) plus a pinned notice, which is the one alert that does
11-
// not retire on its own: it stands for exactly as long as the question does.
9+
// THE QUESTION COSTS NO HEIGHT. It is not a dialog: the sentence rides the
10+
// bar's alert slot as a pinned notice (the one alert that does not retire on
11+
// its own), the answers ride the bar's command slot as the pit token, and
12+
// whatever was on screen stays there. A modal that shoved the conversation
13+
// down to say six words was two copies of one question.
1214

13-
// confirmAsk is the pending question: the sentence, what a yes runs, and the
14-
// pit the question was asked from, which comes back either way. A reader who
15-
// says no must find their list exactly where they left it.
15+
// confirmAsk is the pending question: the sentence, and what a yes runs.
1616
type confirmAsk struct {
1717
prompt string
1818
yes func()
19-
behind pit
2019
}
2120

2221
// askConfirm puts the question up. The caller's action runs on the input
2322
// goroutine, so it must hand off anything that dials, exactly as every other
2423
// hook here does.
2524
func (t *transcript) askConfirm(prompt string, yes func()) {
26-
// THE PIT UNDERNEATH IS NOT CLOSED, it is set aside: showList would close
27-
// a hosted view and drop a list's cursor, and the question is a moment,
28-
// not a new place to be.
29-
behind := t.pit
30-
t.confirm = &confirmAsk{prompt: prompt, yes: yes, behind: behind}
31-
t.pit = pit{}
32-
t.pit.showList(pitConfirm, "", []pitRow{staticRow(" " + prompt + " [y/N]")})
25+
t.confirm = &confirmAsk{prompt: prompt, yes: yes}
3326
t.status.pinNotice(prompt)
34-
t.focused = focusPit
3527
t.render()
3628
}
3729

3830
// clearConfirm takes the question down and the notice with it, in that order:
3931
// the bar must not be left holding a sentence about a question nobody can see.
4032
func (t *transcript) clearConfirm() {
41-
ask := t.confirm
4233
t.confirm = nil
43-
if t.showing(pitConfirm) {
44-
t.pit = pit{}
45-
if ask != nil {
46-
t.pit = ask.behind
47-
}
48-
}
4934
t.status.setNotice("")
5035
}
5136

@@ -64,13 +49,5 @@ func confirmNo(t *transcript) {
6449
t.render()
6550
}
6651

67-
// confirmLines is the question as the drawer draws it.
68-
func (t *transcript) confirmLines() []string {
69-
if t.confirm == nil {
70-
return nil
71-
}
72-
return []string{pitGray(clipToWidth(t.confirm.prompt+" [y/N]", t.w))}
73-
}
74-
7552
// killPrompt is the sentence the two kill doors ask.
7653
func killPrompt(id string) string { return "kill " + strings.TrimSpace(id) + "?" }

‎internal/cli/confirm_test.go‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ func TestConfirm_XAsksAndCapitalXDoesNot(t *testing.T) {
3333
if tr.mode() != modeConfirm {
3434
t.Fatalf("mode = %v, want the question to own the keyboard", tr.mode())
3535
}
36-
if got := strings.Join(tr.confirmLines(), ""); !strings.Contains(stripANSI(got), "kill aria1234?") {
37-
t.Fatalf("question = %q", got)
36+
if got := tr.status.noticeText(); !strings.Contains(got, "kill aria1234?") {
37+
t.Fatalf("the bar does not carry the question: %q", got)
3838
}
3939
// A key that is not an answer changes nothing.
4040
tr.key('j')
@@ -70,8 +70,8 @@ func TestConfirm_TheNoticeStandsWhileTheQuestionDoes(t *testing.T) {
7070
}
7171
}
7272

73-
// The question is a moment, not a place: the list it was asked from is still
74-
// there afterwards, with its cursor where the reader left it.
73+
// THE QUESTION COSTS NO HEIGHT and takes nothing away: the list it was asked
74+
// from is untouched, cursor and all, and the bar carries both halves of it.
7575
func TestConfirm_TheListSurvivesTheQuestion(t *testing.T) {
7676
tr, _ := visualFixture(t)
7777
tr.dropRow = func(pit, id, next string) {}
@@ -81,7 +81,14 @@ func TestConfirm_TheListSurvivesTheQuestion(t *testing.T) {
8181
})
8282
tr.focused = focusPit
8383
tr.pit.moveSelection(1) // stand on the second row
84+
before := len(tr.pit.lines(tr.w, 12))
8485
tr.key('x')
86+
if got := len(tr.pit.lines(tr.w, 12)); got != before {
87+
t.Fatalf("the question took %d rows from the screen", got-before)
88+
}
89+
if tok := pitConfirm.token(false); tok != "[y/N]" {
90+
t.Fatalf("the bar's command slot says %q, want the answers", tok)
91+
}
8592
tr.key('n')
8693
if tr.pit.id != pitOutput {
8794
t.Fatalf("the listing did not come back: pit = %q", tr.pit.id)

‎internal/cli/keymap_equiv_test.go‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,13 +1021,7 @@ var pagerOracle = []struct {
10211021
// have: it has no pre-refactor twin.
10221022
// The y/n question. Every key that is not an answer is swallowed, which
10231023
// is the whole point: consent is typed, never stumbled into.
1024-
{"confirm", "off:same fol=true srch=false q=\"\" mq=\"\" h=false s=false Q=false g=false sel=false exp=0 jmp=false jq=\"\" vis=none", map[string]string{
1025-
"0x1b": "off:bottom fol=true srch=false q=\"\" mq=\"\" h=false s=false Q=false g=false sel=false exp=0 jmp=false jq=\"\" vis=none",
1026-
"0x4e": "off:bottom fol=true srch=false q=\"\" mq=\"\" h=false s=false Q=false g=false sel=false exp=0 jmp=false jq=\"\" vis=none",
1027-
"0x59": "off:bottom fol=true srch=false q=\"\" mq=\"\" h=false s=false Q=false g=false sel=false exp=0 jmp=false jq=\"\" vis=none",
1028-
"0x6e": "off:bottom fol=true srch=false q=\"\" mq=\"\" h=false s=false Q=false g=false sel=false exp=0 jmp=false jq=\"\" vis=none",
1029-
"0x79": "off:bottom fol=true srch=false q=\"\" mq=\"\" h=false s=false Q=false g=false sel=false exp=0 jmp=false jq=\"\" vis=none",
1030-
}},
1024+
{"confirm", "off:same fol=true srch=false q=\"\" mq=\"\" h=false s=false Q=false g=false sel=false exp=0 jmp=false jq=\"\" vis=none", map[string]string{}},
10311025
{"fork", "off:same fol=true srch=false q=\"\" mq=\"\" h=false s=false Q=false g=false sel=false exp=0 jmp=false jq=\"\" vis=none", map[string]string{
10321026
"0x4c": "off:bottom fol=true srch=false q=\"\" mq=\"\" h=false s=false Q=false g=false sel=false exp=0 jmp=false jq=\"\" vis=none",
10331027
"0x5e": "off:bottom fol=true srch=false q=\"\" mq=\"\" h=false s=false Q=false g=false sel=false exp=0 jmp=false jq=\"\" vis=none",

‎internal/cli/pitid.go‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ const (
2626
// Live form views arrive named "form listen", "form show" and so on: the
2727
// family is matched by prefix, see face().
2828
pitForm pitID = "form"
29-
// A question, not a view: it holds the keyboard until it is answered.
29+
// A question, not a view: it holds the keyboard until it is answered and
30+
// draws nothing but its own token on the bar.
3031
pitConfirm pitID = "confirm"
3132
)
3233

@@ -59,8 +60,10 @@ var pitFaces = map[pitID]pitFace{
5960
// folded onto. Wide (East Asian W, two cells) rather than ambiguous, so
6061
// runewidth reports 2 everywhere and the bar's arithmetic holds. Its
6162
// selected row is ⌖, the pin in the body rather than a note in a score.
62-
pitForm: {"웃", "form", "⌖", modePanel},
63-
pitConfirm: {"‽", "confirm", "", modeConfirm},
63+
pitForm: {"웃", "form", "⌖", modePanel},
64+
// The answers ARE the token: a question that costs no height still has to
65+
// say what will answer it.
66+
pitConfirm: {"[y/N]", "confirm", "", modeConfirm},
6467
}
6568

6669
// face is the pit's presentation, or the empty face for one nobody has named:

‎internal/cli/transcript.go‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2882,8 +2882,6 @@ func keepRefsBelow(src map[nodeRef]bool, base int) map[nodeRef]bool {
28822882
func (t *transcript) inputDrawerLines() []string {
28832883
var rows []string
28842884
switch {
2885-
case t.confirm != nil:
2886-
return t.confirmLines()
28872885
case t.inSearch:
28882886
// THE SIGIL IS THE DIRECTION. A box that always says '/' cannot tell
28892887
// a reader which way the next Enter walks.

‎skills/figaro/reference/ui-stream.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ run `figaro show` (full content above, cursor below).
468468
| `^` | attend this aria's parent, or say it is top-level |
469469
| `l` / `L` | list this aria's tree / the home tree in the pit, uncapped |
470470
| `x` / `X` | in a list, kill the aria the selected row names, asking first / at once; in the queue, drop the message |
471-
| `y` / `n` | answer that question (Esc is no). Nothing else is read while it is up |
471+
| `y` / `n` | answer that question (Esc is no). Nothing else is read while it is up; the sentence rides the bar's alert and `[y/N]` its command slot, so the question costs no height |
472472
| `h` | the help panel |
473473
| `v` / `V` | visual mode: a cursor, then a highlight by character / by line |
474474
| `y` / `Y` | copy the selection / copy its coordinate `<lt.block:a-b>!` |
@@ -477,9 +477,10 @@ run `figaro show` (full content above, cursor below).
477477
| `q` / `Esc` / `Ctrl-T` | exit the pager |
478478

479479
A listing opens with the cursor on the aria you are attending, not on the top
480-
row. `x` and `:kill` ask before they kill, and the question holds the keyboard
481-
and the status bar until it is answered; `X` on a row does the same work with
482-
no question. Killing the aria on screen moves the pager to the row the cursor
480+
row. `x` and `:kill` ask before they kill. The question holds the keyboard and the
481+
bar, and nothing else: the sentence is the bar's alert, `[y/N]` is its command
482+
token, and the view underneath is untouched. `X` on a row does the same work
483+
with no question. Killing the aria on screen moves the pager to the row the cursor
483484
falls onto, else the jumplist's previous aria, else the parent, and ends the
484485
session only when there is none of the three.
485486

0 commit comments

Comments
 (0)