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:
co-authored by
Claude Opus 5
parent
b3726c950a
commit
736037462e
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user