Backspace, whatever the terminal calls it
CosmOS's line editor looks for 0x08, which is what Voyager's keyboard
sends. A POSIX terminal sends its own erase character instead, and on
most of them that is 0x7F.
It stayed hidden while the terminal was doing the editing: canonical
mode consumes the erase character itself and hands over a finished
line. Key mode turns ICANON off, which is the point of it, so from the
day the shell started editing its own line - 7360374 - Backspace worked
in Voyager and did nothing at all in the console-only emulator. The
Programming Manual already claimed the console normalised Backspace on a
terminal the way it normalises the arrow keys; the code did not.
The erase character is read from the terminal's own VERASE rather than
assumed to be 0x7F, because some terminals are set to 0x08 and a person
who has moved their erase key has said where it is. Forward Delete is
untouched and stays 0x86: two keys, two values.
Only when standard input really is a terminal. A file or a pipe holding
0x7F holds a byte somebody wrote, not a key somebody pressed.
Three checks in terminal.sh under a pseudo-terminal whose VERASE is set
on the slave side: erase at 0x7F arrives as Backspace, erase at 0x08
still does, and a 0x7F read from a file is left alone. The last one is
what fails on the obvious wrong fix of translating 0x7F unconditionally,
which was verified with break.sh along with the fix's removal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
019c93a587
commit
8631a78229
+50
-2
@@ -91,6 +91,9 @@ show:
|
||||
INIB 0x86
|
||||
XOR
|
||||
BRQ showDelete
|
||||
INIB 0x08
|
||||
XOR
|
||||
BRQ showBack
|
||||
INIB 0x1B
|
||||
XOR
|
||||
BRQ showEscape
|
||||
@@ -109,6 +112,10 @@ showDelete:
|
||||
INIA 0x44 ; 'D'
|
||||
OUTA 0x00
|
||||
RET
|
||||
showBack:
|
||||
INIA 0x42 ; 'B'
|
||||
OUTA 0x00
|
||||
RET
|
||||
showEscape:
|
||||
INIA 0x45 ; 'E'
|
||||
OUTA 0x00
|
||||
@@ -134,6 +141,7 @@ python3 - "$ROOT" "$BUILD" <<'PY'
|
||||
import os
|
||||
import pty
|
||||
import select
|
||||
import termios
|
||||
import signal
|
||||
import re
|
||||
import subprocess
|
||||
@@ -251,11 +259,17 @@ os.close(fd)
|
||||
# expects the far end to know what that means, and the console is the far end. Every other
|
||||
# test in this suite feeds a file, where the translation deliberately does not happen, so
|
||||
# this is the only place the sequences are ever read as sequences.
|
||||
def typedAt(typing, seconds=4):
|
||||
def typedAt(typing, seconds=4, erase=None):
|
||||
"""Runs the naming program under a pseudo-terminal, types at it, and gives back the
|
||||
letters it printed."""
|
||||
letters it printed. erase sets the terminal's own erase character first, which is set on
|
||||
the SLAVE side in the child: that is the terminal the machine will find and save, and
|
||||
configuring the master would be configuring something else."""
|
||||
pid, fd = pty.fork()
|
||||
if pid == 0:
|
||||
if erase is not None:
|
||||
attributes = termios.tcgetattr(0)
|
||||
attributes[6][termios.VERASE] = erase
|
||||
termios.tcsetattr(0, termios.TCSANOW, attributes)
|
||||
os.execv(emulator, [emulator, "--fast", os.path.join(build, "escape.bin")])
|
||||
# The machine has to have asked for key mode before anything is typed at it. Until it
|
||||
# does, the terminal is still holding what arrives until Return and the sequences would
|
||||
@@ -293,6 +307,40 @@ seen = typedAt([(b"\x1b[A", 0.2), (b"\x1b[D", 0.2), (b"\x1b[3~", 0.2)])
|
||||
report(seen == b"ULD", "a terminal's escape sequences arrive as keys",
|
||||
"" if seen == b"ULD" else "expected ULD, saw %r" % seen)
|
||||
|
||||
|
||||
# ---- Backspace, whatever this terminal calls it ----
|
||||
#
|
||||
# SplitBit's Backspace is 0x08 and CosmOS's line editor looks for that. A POSIX terminal
|
||||
# sends its own erase character, which is usually 0x7F, and while the terminal was doing the
|
||||
# editing nobody could tell: canonical mode ate the byte and handed over a finished line. Key
|
||||
# mode turns that off, so from the day the shell started editing its own line, Backspace
|
||||
# worked in Voyager and did nothing at all in the console-only emulator.
|
||||
#
|
||||
# Both settings are tried because the fix reads VERASE rather than assuming 0x7F, and a
|
||||
# terminal set to 0x08 has to keep working - it is already sending the right byte.
|
||||
seen = typedAt([(b"\x7f", 0.3), (b"\x1b[3~", 0.2)], erase=0x7F)
|
||||
report(seen == b"BD", "a terminal's erase character arrives as Backspace",
|
||||
"" if seen == b"BD" else "expected BD, saw %r" % seen)
|
||||
|
||||
seen = typedAt([(b"\x08", 0.3), (b"\x1b[3~", 0.2)], erase=0x08)
|
||||
report(seen == b"BD", "and a terminal that already sends 0x08 still works",
|
||||
"" if seen == b"BD" else "expected BD, saw %r" % seen)
|
||||
|
||||
|
||||
# ---- And nothing is rewritten when there is no terminal ----
|
||||
#
|
||||
# The translation is a thing done TO A TERMINAL, because a terminal is the only thing that
|
||||
# has an erase character. A file or a pipe holding 0x7F holds a byte somebody wrote, and a
|
||||
# console that rewrote it would corrupt input that has nothing to do with keys.
|
||||
fed = os.path.join(build, "erase.keys")
|
||||
with open(fed, "wb") as f:
|
||||
f.write(b"\x7f\x7f\x7f")
|
||||
ran = subprocess.run([emulator, "--fast", os.path.join(build, "escape.bin")],
|
||||
stdin=open(fed, "rb"), capture_output=True)
|
||||
said = ran.stdout.split(b"Execution")[0].strip()
|
||||
report(said == b"???", "a 0x7F from a file is left alone",
|
||||
"" if said == b"???" else "expected ??? for three unnamed bytes, saw %r" % said)
|
||||
|
||||
# ---- And pressing Escape is still pressing Escape ----
|
||||
#
|
||||
# Which is the whole difficulty: Up and Escape both begin with 0x1B and the only thing
|
||||
|
||||
Reference in New Issue
Block a user