The shell edits the line it is given

Three different things used to do this job, and which one you got depended on
where the machine was running. On a terminal the host held the line and did the
echoing and the backspacing; behind a window the console's own gatherer did it;
from a file nothing did it at all. One job, three implementations, none of them
in the system - which is why there was no way to move about in a line and
nowhere for a history to live.

So editLine does it. Key mode while a line is being read and line mode straight
after, so nothing else in the system and no program calling osReadLine notices
anything changed. Left and Right, Home and End, Backspace for the character
before the cursor and Delete for the one under it, and anything typed goes in
where the cursor is with the rest of the line moving along.

Ctrl-D means the end of input again, on an empty line, because that was a thing
the terminal did while it was holding the line and it is not holding it now.
Same trade as the echoing.

MOST KEYSTROKES DRAW NOTHING BUT THEMSELVES. A character typed at the end of a
line needs no cursor moved: printing it is the whole change, and a backspace
there is three ordinary bytes. That matters beyond speed - moving the cursor by
hand is what a terminal is TOLD about, in an escape sequence, so redrawing on
every keypress would fill every recorded transcript in this suite with them.
The line is only reprinted when something happened in the middle of it.

Where the line STARTS is worked out backwards from where printing ended, rather
than trusted from what was remembered. That is what makes it survive the screen
scrolling: a line printed on the bottom row moves everything up by one, and a
remembered row would be one too low from then on.

The command line holds 127 characters, up from 63. The limit started to be felt
the moment a line could be moved about in.

58 recordings changed, and every one of them by the echo. THE PROOF IS NOT A
HEURISTIC: a CosmOS built with the echo silenced reproduces 187 of the 188
recordings byte for byte. The one exception is cosmosTyped, the backspace test,
where the rub-out marks now come from the shell instead of from the console's
gatherer - same marks, different author.

cosmosEditKeys is the new test, and every line in it is typed wrong and then
corrected with a different key. Its last line is eighty six characters at a
prompt in column two on an eighty column screen, so the line runs onto the row
below and the shell has to find the start of something it can no longer see;
breaking either half of that arithmetic fails it.

Also: agree.sh looked for "> the same", anchored to a prompt that no longer
precedes what a command prints.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-08-31 23:01:55 -04:00
co-authored by Claude Opus 5
parent b3726c950a
commit 736037462e
62 changed files with 1220 additions and 291 deletions
+546 -5
View File
@@ -188,7 +188,7 @@ promptWhere:
;
; So shellReadLine says it. It is the one place that knows.
SETD.0 CommandLine
INIB 0d63
INIB 0d127
CALL shellReadLine
; Running out of typing is how this ends. It is not the same as an empty line, which is
@@ -389,7 +389,7 @@ shellReadQuiet:
shellReadTyped:
CALL sayPrompt
CALL readLine
CALL editLine
RET
; ---- The same, for a caller that prints a prompt of its own ----
@@ -411,7 +411,512 @@ shellReadRaw:
shellReadRawQuiet:
RET
shellReadRawTyped:
CALL readLine
CALL editLine
RET
; ---- The line the person at the keyboard is typing ----
;
; THREE DIFFERENT THINGS USED TO DO THIS JOB and which one you got depended on where the
; machine was running. On a terminal the host held the line and did the echoing and the
; backspacing; behind a window the console's own gatherer did it; from a file nothing did it
; at all. One job, three implementations, and none of them here - which is why there was no
; way to move about in a line and nowhere for a history to live.
;
; So the shell does it. The console delivers keys and says nothing about what they mean, the
; same way the disk says what a drive is and nothing about what should be on it. Where the
; cursor goes and what the line looks like afterwards are decisions, and decisions belong to
; whoever is reading.
;
; DP0 names a buffer and B says how many characters it holds, not counting the zero byte
; that ends it. Q is how long the line turned out to be. That is readLine's bargain exactly,
; so this drops in where that was called.
;
; ConsoleEndOfInput is set if the console ran out instead of a line being finished, which is
; readLine's other promise and the one the shell uses to know when to stop.
;
; ---- Why most keystrokes draw nothing but themselves ----
;
; A character typed at the END of a line needs no cursor moved: printing it is the whole of
; the change. That is the case almost every keystroke is, and it matters beyond speed -
; moving the cursor by hand means writing the console's cursor registers, which a terminal
; is told about in an escape sequence. Redrawing on every keypress would fill every recorded
; transcript in the test suite with them. So the cheap path is the common one, and the line
; is only reprinted when something happened in the middle of it.
editLine:
SETD.1 EditBase
STD.0.1
SETD.1 EditRoom
STB.1
RSTA
SETD.1 EditLength
STA.1
SETD.1 EditAt
STA.1
SETD.1 ConsoleEndOfInput
STA.1
; Where the line begins, ASKED rather than assumed. The prompt has just been printed and
; only the console knows where it left off.
INA 0x03
SETD.1 EditRow
STA.1
INA 0x04
SETD.1 EditColumn
STA.1
; How wide the screen is, which is what decides where a long line carries on. A machine
; with nothing on that port answers zero, and a width of zero would make the walking below
; never finish, so it is taken as the width this system asks for at boot.
INA 0x32
BNA editWidthKnown
INIA 0d80
editWidthKnown:
SETD.1 EditWidth
STA.1
; Key mode, with the cursor the shell keeps. Nothing echoes from here on: everything that
; appears below is put there by this routine.
INIA 0x05
OUTA 0x02
editKey:
INA 0x00
SETD.1 EditChar
STA.1
; The ways out first, since they leave rather than change anything. XOR leaves the answer
; in Q and A alone, so one read stands for the whole ladder.
INIB 0x0A
XOR
BRQ editDone
INIB 0x0D
XOR
BRQ editDone
INIB 0xFF
XOR
BRQ editEnded
; ---- And the key that means there is no more ----
;
; Ctrl-D, which the terminal used to turn into the end of input all by itself and cannot any
; more: that is a thing a terminal does while it is holding the line, and it is not holding
; it now. So the shell does it, which is the same trade as the echoing and the backspacing.
;
; ONLY ON AN EMPTY LINE, which is the rule everywhere else it appears. In the middle of
; something typed it means neither "stop" nor a character, so it means nothing.
INIB 0x04
XOR
BNQ editNotEnd
SETD.1 EditLength
LDB.1
BRB editEnded
editNotEnd:
INIB 0x08
XOR
BRQ editBack
INIB 0x86
XOR
BRQ editDelete
INIB 0x82
XOR
BRQ editLeft
INIB 0x83
XOR
BRQ editRight
INIB 0x84
XOR
BRQ editHome
INIB 0x85
XOR
BRQ editEnd
; Anything else is a character if it is printable and nothing at all if it is not. The
; console's own keys that this shell has no use for land here and are ignored rather than
; typed, which is the whole reason they are above ASCII.
INIB 0x20
CCF
SUB
BRC editKey
INIB 0x7F
CCF
SUB
BNC editKey
; ---- Putting a character in ----
editInsert:
SETD.1 EditLength
LDA.1
SETD.1 EditRoom
LDB.1
CCF
SUB
BNC editKey ; Full. The zero byte on the end is not counted in the room.
; A hole at the insertion point, made from the top down so nothing is overwritten before
; it has been moved.
SETD.1 EditLength
LDA.1
SETD.1 EditAt
LDB.1
CCF
SUB
MVQB ; How many characters are above the insertion point.
SETD.1 EditBase
LDD.0.1
SETD.1 EditLength
LDA.1
DPUA.0 ; One past the last character.
editInsertShift:
BRB editInsertPut
DECD.0
LDA.0
INCD.0
STA.0
DECD.0
DECB
BRI editInsertShift
editInsertPut:
SETD.1 EditChar
LDA.1
STA.0
SETD.1 EditLength
LDA.1
INCA
STA.1
SETD.1 EditAt
LDA.1
INCA
STA.1
; At the end of the line, printing it IS the change.
SETD.1 EditLength
LDB.1
CCF
SUB
BNQ editInsertRedraw
SETD.1 EditChar
LDA.1
OUTA 0x00
BRI editKey
editInsertRedraw:
CALL editRedraw
BRI editKey
; ---- Taking one out ----
;
; Backspace and Delete are different keys doing different things: one takes the character
; BEFORE the insertion point, the other the one under it. They meet at editTakeOut, which is
; the only part they share.
editBack:
SETD.1 EditAt
LDA.1
BRA editKey ; Nothing before it. Rubbing out past the start of the line
; would eat the prompt, which belongs to whoever printed it.
DECA
STA.1
; Whether that was the last character decides how it disappears, and it has to be asked
; before the buffer shortens under it.
SETD.1 EditLength
LDB.1
DECB
CCF
SUB
BNQ editBackMiddle
CALL editTakeOut
; Rubbed out where it stands, which is what a terminal has always done for a backspace: no
; cursor moved by hand, and so nothing said to the terminal but three ordinary bytes.
INIA 0x08
OUTA 0x00
INIA 0x20
OUTA 0x00
INIA 0x08
OUTA 0x00
BRI editKey
editBackMiddle:
CALL editTakeOut
CALL editRedraw
BRI editKey
editDelete:
SETD.1 EditAt
LDA.1
SETD.1 EditLength
LDB.1
CCF
SUB
BRQ editKey ; Nothing under the cursor at the end of a line.
CALL editTakeOut
CALL editRedraw
BRI editKey
; Takes the character at the insertion point out of the buffer and shortens it. Draws
; nothing: what that should look like is the caller's business and the two callers disagree.
editTakeOut:
SETD.1 EditBase
LDD.0.1
SETD.1 EditAt
LDA.1
DPUA.0
SETD.1 EditLength
LDA.1
SETD.1 EditAt
LDB.1
CCF
SUB
MVQB
DECB ; One of those is the character going.
editTakeOutShift:
BRB editTakeOutDone
INCD.0
LDA.0
DECD.0
STA.0
INCD.0
DECB
BRI editTakeOutShift
editTakeOutDone:
SETD.1 EditLength
LDA.1
DECA
STA.1
RET
; ---- Moving about in it ----
;
; Nothing on the screen changes, so nothing on the screen is redrawn: only the cursor moves.
editLeft:
SETD.1 EditAt
LDA.1
BRA editKey
DECA
STA.1
BRI editShow
editRight:
SETD.1 EditAt
LDA.1
SETD.1 EditLength
LDB.1
CCF
SUB
BRQ editKey ; Already at the end.
SETD.1 EditAt
LDA.1
INCA
STA.1
BRI editShow
editHome:
RSTA
SETD.1 EditAt
STA.1
BRI editShow
editEnd:
SETD.1 EditLength
LDA.1
SETD.1 EditAt
STA.1
editShow:
SETD.1 EditAt
LDA.1
CALL editPlace
BRI editKey
; ---- The end of a line, and the end of the typing ----
editEnded:
INIA 0x01
SETD.1 ConsoleEndOfInput
STA.1
; AND NO NEWLINE. Nobody pressed Return, so there is no line to end: the console simply
; stopped having anything to say. Printing one here pushes whatever is said next down a row
; for a reason nobody could see, which is what it did until this line was written.
BRI editFinish
editDone:
; The newline belongs after the whole line, not after wherever the cursor was left sitting.
; Asked first, because a line finished at its end - which is almost every line - needs no
; cursor moved and so says nothing to the terminal.
SETD.1 EditAt
LDA.1
SETD.1 EditLength
LDB.1
CCF
SUB
BRQ editDoneEnd
LDA.1
CALL editPlace
editDoneEnd:
CALL newLine
editFinish:
; Line mode, with the cursor still on: that is how boot left the console and how every
; program that knows nothing of any of this expects to find it.
INIA 0x04
OUTA 0x02
SETD.1 EditBase
LDD.0.1
SETD.1 EditLength
LDA.1
DPUA.0
RSTA
STA.0 ; The zero byte that ends it.
SETD.1 EditLength
LDA.1
RSTB
CCF
ADD ; Q is how long the line is.
RET
; ---- Putting the line back on the screen ----
;
; The whole of it, every time, rather than the part that changed. A hundred and twenty odd
; characters is nothing to this machine, and the alternative is four separate cases that all
; have to agree about what is already up there.
editRedraw:
SETD.1 EditRow
LDA.1
OUTA 0x03
SETD.1 EditColumn
LDA.1
OUTA 0x04
SETD.1 EditBase
LDD.0.1
SETD.1 EditLength
LDB.1
editRedrawNext:
BRB editRedrawTail
LDA.0
OUTA 0x00
INCD.0
DECB
BRI editRedrawNext
editRedrawTail:
; One space after it. A line that has just got shorter has a character left over on the
; end, and printing over it costs less than working out whether there is one.
INIA 0x20
OUTA 0x00
CALL editAnchor
SETD.1 EditAt
LDA.1
CALL editPlace
RET
; ---- Where the line starts, worked out backwards ----
;
; The console has just been told to print, so it knows where printing ENDED, and the line
; began that many characters before it. Asking afterwards rather than trusting what was
; remembered is what makes this survive the screen SCROLLING: a line printed on the bottom
; row moves everything up by one, and a remembered row would be one too low from then on.
editAnchor:
INA 0x03
SETD.1 EditWalkRow
STA.1
INA 0x04
SETD.1 EditWalkColumn
STA.1
SETD.1 EditLength
LDA.1
INCA ; The space was printed too.
SETD.1 EditWalkBack
STA.1
editAnchorWalk:
SETD.1 EditWalkColumn
LDA.1
SETD.1 EditWalkBack
LDB.1
CCF
SUB
BRC editAnchorRowUp ; Further back than this row goes.
MVQA
SETD.1 EditWalkColumn
STA.1
BRI editAnchorDone
editAnchorRowUp:
; Off the front of this row, so take what the row used and carry on along the one above.
SETD.1 EditWalkBack
LDA.1
SETD.1 EditWalkColumn
LDB.1
INCB ; The column itself, and the step onto it.
CCF
SUB
MVQA
SETD.1 EditWalkBack
STA.1
SETD.1 EditWidth
LDA.1
DECA
SETD.1 EditWalkColumn
STA.1
SETD.1 EditWalkRow
LDA.1
BRA editAnchorTop
DECA
STA.1
BRI editAnchorWalk
editAnchorTop:
; The line began off the top of the screen, which means more was scrolled away than the
; line is long. There is nothing sensible left to point at, so it starts in the corner.
RSTA
SETD.1 EditWalkColumn
STA.1
editAnchorDone:
SETD.1 EditWalkRow
LDA.1
SETD.1 EditRow
STA.1
SETD.1 EditWalkColumn
LDA.1
SETD.1 EditColumn
STA.1
RET
; A says how many characters along the line the cursor belongs. Puts it there, carrying onto
; the rows below when the line is longer than one row of the screen.
editPlace:
SETD.1 EditColumn
LDB.1
CCF
ADD
MVQA
SETD.1 EditWalkColumn
STA.1
SETD.1 EditRow
LDA.1
SETD.1 EditWalkRow
STA.1
editPlaceWrap:
SETD.1 EditWalkColumn
LDA.1
SETD.1 EditWidth
LDB.1
CCF
SUB
BRC editPlaceDone ; Inside the row.
MVQA
SETD.1 EditWalkColumn
STA.1
SETD.1 EditWalkRow
LDA.1
INCA
STA.1
BRI editPlaceWrap
editPlaceDone:
SETD.1 EditWalkRow
LDA.1
OUTA 0x03
SETD.1 EditWalkColumn
LDA.1
OUTA 0x04
RET
; ---- A command that did not work ----
@@ -4776,9 +5281,45 @@ DirSize:
WidthLeft:
0x00
; Sixty three characters and the zero byte that ends them.
; ---- What a line being typed is made of ----
;
; All of it lives here rather than being passed about, because the routine that reads a line
; is a loop over keystrokes and there are four Data Pointers, two of which it needs for the
; buffer and the walking.
EditBase:
0x00 0x00
EditRoom:
0x00
EditLength:
0x00
EditAt:
0x00
EditRow:
0x00
EditColumn:
0x00
EditWidth:
0x00
EditChar:
0x00
; Where the counting up and down the rows has got to. Separate from EditRow and EditColumn,
; which are where the LINE starts: one of the two things that reads these is working out
; that very answer, and a walk that wrote its intermediate steps into its own starting point
; would be walking away from a moving mark.
EditWalkRow:
0x00
EditWalkColumn:
0x00
EditWalkBack:
0x00
; A hundred and twenty seven characters and the zero byte that ends them. It was sixty three
; until the shell learned to edit a line, which is when the limit started to be felt: a copy
; between two disks with a directory on each is most of the way there before anything has
; been said, and the line can now be moved about in, so a long one is worth having.
CommandLine:
#Reserve 0d64
#Reserve 0d128
#Vectors