tty: arrow keys, backspace, and four terminals nobody was home on
Three things, all in the first minute of using a shell.
**Arrow keys corrupted the two keystrokes after them.** They arrive as single
keycodes -- KEY_UP is 0xE2 -- and were pushed into the input buffer exactly
like that. No terminal works that way, and 0xE2 is the worst possible byte to
have chosen: it is a UTF-8 lead byte for a three-byte character, so a reader
decoding UTF-8 swallowed the next two keys waiting for continuations that never
arrived. consoleintr() sends the DEC/xterm sequences now -- ESC[A, ESC[D,
ESC[5~ and the rest -- which every terminfo entry and every line editor already
knows. Not echoed: in canonical mode echoing them would move the cursor for
real, which was the other half of what "weird glitches" looked like.
**Then the end of the line vanished as you typed into it**, with the buffer
perfectly correct. One line in vt_putc(): backspace erased the cell it moved
onto. That is a line-discipline erase, not the BS control character -- a
program rubbing a character out sends `\b \b`, and the separation matters
because programs also use plain `\b` to go back. zsh does, on every mid-line
edit:
echo alpha \b\b zha \b\b
back two, rewrite the tail, back two again to park the cursor. Against a
destructive backspace that last pair deleted the `ha` it had just drawn.
`\b` moves and nothing else now, 0x7f is ignored as on a real terminal, and
consputc() spells its own erase out as `\b \b` as it already did for serial.
ESC[@, ESC[P and ESC[X went in alongside, because TERM=linux declares them.
**VT3 to VT6 were blank** because nothing ran on them. NVT is 6 and the kernel
made all six; only two had a shell, so Alt+F3 switched to a terminal that was
working perfectly and had nobody to talk to. Four more manifests.
Six shells starting at once is enough concurrency to expose an intermittent
fault in one of them -- one boot in five, a wild jump into .data that runs
until it writes to 0, which nothing catches because SchOS never sets EFER.NXE.
Three boots with two shells, none. Recorded rather than fixed: it is not caused
by the manifests, only made likelier by them.
Also: QEMU_NET forwards host 8080/8081/8000 to guest 80/8080/8000, so the httpd
is reachable from the machine running QEMU instead of being sealed behind NAT.
Verified end to end. And the greeting prints once again -- a replaced
supervisor was announcing a boot that had already happened.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
75ccf9841c
commit
ff7afa746e
9 changed files with 317 additions and 8 deletions
|
|
@ -325,13 +325,32 @@ vt_putc(struct vt *v, int glyph)
|
|||
v->cx = 0;
|
||||
break;
|
||||
|
||||
// Backspace moves the cursor. It does **not** erase.
|
||||
//
|
||||
// This blanked the cell it moved onto, which is what a line-discipline erase
|
||||
// does, not what the BS control character does. A program that wants to rub
|
||||
// a character out sends `\b \b` -- backspace, space, backspace -- and the
|
||||
// separation matters because programs also use plain `\b` simply to go back.
|
||||
//
|
||||
// zsh does, on every edit of a line you have already typed. Inserting a
|
||||
// character in the middle, it sends:
|
||||
//
|
||||
// echo alpha \b\b zha \b\b
|
||||
//
|
||||
// -- move back two, rewrite the tail, then move back two again to leave the
|
||||
// cursor where the user expects it. Against a destructive backspace that
|
||||
// last pair deleted the `ha` it had just drawn, so the line appeared to lose
|
||||
// its tail while the buffer was perfectly correct. Which is exactly how it
|
||||
// looked: type in the middle of a line and the end of it vanishes.
|
||||
case '\b':
|
||||
case 0x7f:
|
||||
v->wrap_pending = 0;
|
||||
if (v->cx > 0) {
|
||||
if (v->cx > 0)
|
||||
v->cx--;
|
||||
put_blank_at(v, v->cx, v->cy);
|
||||
}
|
||||
break;
|
||||
|
||||
// DEL is ignored, as it is on a real terminal. It is not a movement and it
|
||||
// is not an erase; it exists to be discarded on a paper tape.
|
||||
case 0x7f:
|
||||
break;
|
||||
|
||||
case '\t':
|
||||
|
|
@ -453,6 +472,62 @@ vt_erase_line(struct vt *v, int mode)
|
|||
}
|
||||
}
|
||||
|
||||
// Insert and delete characters within a line: ESC[n@ and ESC[nP.
|
||||
//
|
||||
// These were missing, and what was missing with them was **editing a line you
|
||||
// have already typed**. `TERM=linux` declares `ich1=\E[@` and `dch1=\E[P`, so a
|
||||
// line editor that inserts a character in the middle sends one of these and
|
||||
// then writes the character -- and against a terminal that silently drops the
|
||||
// escape, the text after the cursor is overwritten instead of shifted. What
|
||||
// that looks like is the end of the line disappearing as you type into it,
|
||||
// with the *buffer* perfectly correct, which is a maddening pair of symptoms
|
||||
// because the program is doing nothing wrong.
|
||||
//
|
||||
// The row is shifted in the grid and then repainted from the cursor to the end
|
||||
// of the line. No attempt at a hardware scroll: a line is at most a couple of
|
||||
// hundred cells and this happens once per keystroke.
|
||||
static void
|
||||
vt_shift_line(struct vt *v, int n, int insert)
|
||||
{
|
||||
int row = v->cy, start = v->cx, width = v->cols;
|
||||
int i;
|
||||
|
||||
if (n < 1)
|
||||
n = 1;
|
||||
if (n > width - start)
|
||||
n = width - start;
|
||||
|
||||
if (insert) {
|
||||
for (i = width - 1; i >= start + n; i--)
|
||||
*cell_at(v, i, row) = *cell_at(v, i - n, row);
|
||||
for (i = start; i < start + n; i++) {
|
||||
struct vtcell *k = cell_at(v, i, row);
|
||||
k->ch = ' '; k->fg = v->fg; k->bg = v->bg; k->attr = 0;
|
||||
}
|
||||
} else {
|
||||
for (i = start; i < width - n; i++)
|
||||
*cell_at(v, i, row) = *cell_at(v, i + n, row);
|
||||
for (i = width - n; i < width; i++) {
|
||||
struct vtcell *k = cell_at(v, i, row);
|
||||
k->ch = ' '; k->fg = v->fg; k->bg = v->bg; k->attr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = start; i < width; i++)
|
||||
show(v, i, row);
|
||||
}
|
||||
|
||||
// ESC[nX -- erase n characters where they stand, without moving anything.
|
||||
static void
|
||||
vt_erase_chars(struct vt *v, int n)
|
||||
{
|
||||
if (n < 1)
|
||||
n = 1;
|
||||
if (n > v->cols - v->cx)
|
||||
n = v->cols - v->cx;
|
||||
erase_run(v, v->cx, v->cy, n);
|
||||
}
|
||||
|
||||
void
|
||||
vt_clear(struct vt *v)
|
||||
{
|
||||
|
|
@ -647,6 +722,10 @@ vt_csi(struct vt *v, int final)
|
|||
case 'J': vt_erase_display(v, n); break;
|
||||
case 'K': vt_erase_line(v, n); break;
|
||||
|
||||
case '@': vt_shift_line(v, n1, 1); break; // ICH -- insert characters
|
||||
case 'P': vt_shift_line(v, n1, 0); break; // DCH -- delete characters
|
||||
case 'X': vt_erase_chars(v, n1); break; // ECH -- erase characters
|
||||
|
||||
case 'm': vt_sgr(v); break;
|
||||
|
||||
case 's': v->saved_cx = col; v->saved_cy = row; break;
|
||||
|
|
|
|||
Loading…
Reference in a new issue