1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
#!/usr/bin/env python3
"""Bindings that fire without the leader.
Every binding used to need the prefix first, which is the safe design: a
multiplexer that takes chords away from the programs inside it is a
multiplexer you fight. But it is the user's keyboard, and somebody who wants
ctrl+alt+arrow to move between panes should be allowed to have it — including
the part where that chord no longer reaches anything else.
So the deal is exactly this, and it is what these tests pin: a `direct` block
fires without the prefix, and what it fires on, nothing else sees.
"""
import sys
import tempfile
from harness import Session, check, report
# `cat -v` renders control bytes visibly, so "did the program see it" is a
# question the screen can answer.
SH = ["/bin/sh", "-c", 'printf "\\033]2;p\\007"; stty raw -echo; cat -v']
C_M_RIGHT = r"\e[1;7C" # ctrl+alt+right
C_M_LEFT = r"\e[1;7D" # ctrl+alt+left, deliberately left unbound
def cfg(text):
f = tempfile.NamedTemporaryFile("w", suffix=".kdl", delete=False)
f.write(text)
f.close()
return f.name
DIRECT = cfg(
'keys {\n direct {\n bind "ctrl+alt+right" "split-cols"\n }\n}\n'
)
def test_it_fires_with_no_leader():
with Session(SH, cols=80, rows=14, config=DIRECT) as s:
s.settle()
check("one pane to start", len(s.panes()) == 1)
s.send(C_M_RIGHT)
s.settle()
check(
"the chord split the pane on its own", len(s.panes()) == 2, str(s.panes())
)
def test_what_it_fires_on_the_program_never_sees():
"""The footgun, stated: a direct binding takes that chord away."""
with Session(SH, cols=80, rows=14, config=DIRECT) as s:
s.settle()
s.send(C_M_RIGHT)
s.settle(40)
check(
"the pane was not sent the keystroke",
"1;7C" not in s.snapshot().screen(),
s.snapshot().screen()[:300],
)
def test_everything_else_still_reaches_the_program():
with Session(SH, cols=80, rows=14, config=DIRECT) as s:
s.settle()
s.send(C_M_LEFT)
snap = s.until(lambda sn: "1;7D" in sn.screen())
check(
"an unbound chord goes straight through",
"1;7D" in snap.screen(),
snap.screen()[:300],
)
check("and did nothing else", len(s.panes()) == 1, str(s.panes()))
def test_it_also_works_after_the_leader():
"""Pressing the prefix first must not make a binding stop working — that
would be a rule nobody could keep track of."""
with Session(SH, cols=80, rows=14, config=DIRECT) as s:
s.settle()
s.send(r"\x01" + C_M_RIGHT)
s.settle()
check(
"the leader then the chord does the same thing",
len(s.panes()) == 2,
str(s.panes()),
)
def test_prefixed_bindings_are_unaffected():
with Session(SH, cols=80, rows=14, config=DIRECT) as s:
s.settle()
s.send(r"\x01\\")
s.settle()
check("C-a \\ still splits", len(s.panes()) == 2, str(s.panes()))
def test_the_leader_itself_cannot_be_stolen():
"""A direct binding on the prefix key would make the prefix unusable, and
the ordering has to guarantee it cannot."""
conf = cfg(
'keys {\n prefix "ctrl+a"\n direct { bind "ctrl+a" "close-pane" }\n}\n'
)
with Session(SH, cols=80, rows=14, config=conf) as s:
s.settle()
s.send(r"\x01")
s.settle()
check(
"the prefix is still the prefix",
"C-a" in s.snapshot().line(1),
s.snapshot().line(1),
)
check("and the pane was not closed", len(s.panes()) == 1, str(s.panes()))
def test_an_overlay_still_owns_the_keyboard():
"""While a modal is up, a direct binding must not fire underneath it."""
with Session(SH, cols=92, rows=28, config=DIRECT) as s:
s.settle()
s.send(r"\x01?") # the cheatsheet
s.settle()
s.send(C_M_RIGHT)
s.settle()
check("the modal swallowed it", len(s.panes()) == 1, str(s.panes()))
check(
"and closed, as any key does",
"split into columns" not in s.snapshot().screen(),
)
def test_the_cheatsheet_lists_them_apart():
with Session(SH, cols=96, rows=40, config=DIRECT) as s:
s.settle()
s.send(r"\x01?")
s.settle()
screen = s.snapshot().screen()
check("under a heading of their own", "without the leader" in screen, screen)
check("named the way you press them", "C-M-→" in screen, screen)
def test_none_unbinds_a_direct_binding():
conf = cfg(
"keys {\n"
" direct {\n"
' bind "ctrl+alt+right" "split-cols"\n'
' bind "ctrl+alt+right" "none"\n'
" }\n"
"}\n"
)
with Session(SH, cols=80, rows=14, config=conf) as s:
s.settle()
s.send(C_M_RIGHT)
snap = s.until(lambda sn: "1;7C" in sn.screen())
check("it does not fire", len(s.panes()) == 1, str(s.panes()))
check(
"and the chord goes back to the program",
"1;7C" in snap.screen(),
snap.screen()[:300],
)
for name, fn in sorted(list(globals().items())):
if name.startswith("test_"):
fn()
sys.exit(report())
|