Tab finishes a word somebody started
The first word of a line, against the shell's fifteen commands. One match goes in with a space after it, because a word that can only be one thing is finished. Several are folded into their longest common prefix and that goes in, which is the most that can be said without guessing which was meant - and if that adds nothing, the matches are listed and the line put back underneath. THE LINE COMING BACK IS THE HALF I EXPECTED TO BE HARD and it was already solved. The prompt has been reprinted somewhere else entirely, so the editor's idea of where the line begins is wrong - but editAnchor works that out backwards from where printing ended, precisely so it survives the screen moving. Listing is a redraw it already knew how to do. editInsert became editPut, a routine, because completing a word puts in several characters and every one of them is that. Which cost a bug immediately: the old inline code left the insertion point in A, and a RET puts A back to what the caller had. Two more bugs worth naming, both mine and both the same shape - a pointer that had moved: THE CANDIDATE'S START HAS TO BE KEPT. The comparison walks DP3 through the name as it matches, so by the time a match is declared, DP3 points at the part AFTER what was typed - and that is what got copied. "he" completed to "he" because the answer taken was "lp". AND THE INSERTION STOPS AT OR PAST, not exactly equal. With the wrong answer the two counters passed each other and the loop ran off the end of the buffer, filling the line with whatever was next in memory. They cannot pass each other now, and the branch stays, because the cheaper failure is worth nothing. MY OWN TEST HAD A HOLE and breaking the code found it. The later-word case pressed Tab after a space, where there is nothing to finish anyway, so it passed whether or not the shell checked which word it was on. It types "echo he" now, which would become "echo help" if it did not. The assembler's label table went past 1024 and is doubled. A ceiling reached once will be reached again, and it is pointers into source already in memory. 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
749fef8ce2
commit
bb065fe221
@@ -593,6 +593,9 @@ editNotEnd:
|
||||
INIB 0x81
|
||||
XOR
|
||||
BRQ editDown
|
||||
INIB 0x09
|
||||
XOR
|
||||
BRQ tabComplete
|
||||
|
||||
; 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
|
||||
@@ -608,52 +611,14 @@ editNotEnd:
|
||||
|
||||
; ---- 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.
|
||||
CALL editPut
|
||||
BNQ editKey ; No room for it. The zero byte on the end is not counted.
|
||||
|
||||
; 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
|
||||
; At the end of the line, printing it IS the change. The insertion point is READ BACK
|
||||
; rather than still being in A: it was, while this was one run of code, and a RET puts A
|
||||
; back to whatever the caller had.
|
||||
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
|
||||
@@ -671,6 +636,70 @@ editInsertRedraw:
|
||||
CALL editRedraw
|
||||
BRI editKey
|
||||
|
||||
; Puts the character in EditChar into the line where the insertion point is, and moves the
|
||||
; insertion point past it. DRAWS NOTHING: what that should look like depends on where in the
|
||||
; line it went, and the caller is what knows. Q is zero if it went in and one if there was
|
||||
; no room.
|
||||
;
|
||||
; A routine rather than the run of code it used to be, because Tab completing a word puts in
|
||||
; several characters and every one of them is this.
|
||||
editPut:
|
||||
SETD.1 EditLength
|
||||
LDA.1
|
||||
SETD.1 EditRoom
|
||||
LDB.1
|
||||
CCF
|
||||
SUB
|
||||
BNC editPutFull
|
||||
|
||||
; 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.
|
||||
editPutShift:
|
||||
BRB editPutHere
|
||||
DECD.0
|
||||
LDA.0
|
||||
INCD.0
|
||||
STA.0
|
||||
DECD.0
|
||||
DECB
|
||||
BRI editPutShift
|
||||
editPutHere:
|
||||
SETD.1 EditChar
|
||||
LDA.1
|
||||
STA.0
|
||||
|
||||
SETD.1 EditLength
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
SETD.1 EditAt
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; Q is zero: it went in.
|
||||
RET
|
||||
editPutFull:
|
||||
INIA 0x01
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; Q is one: it did not.
|
||||
RET
|
||||
|
||||
; ---- Taking one out ----
|
||||
;
|
||||
; Backspace and Delete are different keys doing different things: one takes the character
|
||||
@@ -1478,6 +1507,433 @@ faultScreen:
|
||||
OUTA 0x02
|
||||
RET
|
||||
|
||||
; ---- Finishing a word somebody started ----
|
||||
;
|
||||
; Tab. What it can finish depends on where in the line it is: the FIRST word is a command or
|
||||
; the name of a program, and everything after it is a file. Only the first is done here.
|
||||
;
|
||||
; None of this could have existed a fortnight ago. The shell never saw a keystroke - the
|
||||
; terminal handed it a finished line - so there was no moment at which somebody had typed
|
||||
; half a word and the system knew about it. That is the same wall the history was behind.
|
||||
;
|
||||
; ---- What it does with what it finds ----
|
||||
;
|
||||
; One match goes in with a space after it, because a word that can only be one thing is
|
||||
; finished. Several are folded into their longest common prefix and that goes in, which is
|
||||
; the most that can be said without guessing. If that adds nothing - because what is typed
|
||||
; IS already the common prefix - the matches are listed and the line put back underneath.
|
||||
;
|
||||
; EVERY HELPER BELOW ANSWERS IN Q. A RET puts A, B and the first three pointers back, so a
|
||||
; routine here that answered in A would be answering into a register its caller cannot see.
|
||||
tabComplete:
|
||||
; ---- Which word, and is it the first ----
|
||||
;
|
||||
; Back from the insertion point to a space or the start of the line. What lies between
|
||||
; there and the cursor is what somebody has typed, and what lies before it decides whether
|
||||
; this is a command being named or an argument being given.
|
||||
SETD.1 EditAt
|
||||
LDA.1
|
||||
SETD.1 TabWordAt
|
||||
STA.1
|
||||
tabBack:
|
||||
SETD.1 TabWordAt
|
||||
LDA.1
|
||||
BRA tabIsFirst ; The start of the line is the start of the word, and of the line.
|
||||
DECA
|
||||
CALL tabCharAt
|
||||
MVQA
|
||||
INIB 0x20
|
||||
XOR
|
||||
BRQ tabFoundStart ; A space, so the word begins after it.
|
||||
SETD.1 TabWordAt
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BRI tabBack
|
||||
|
||||
tabFoundStart:
|
||||
; There is a space behind the word, so something may be in front of that space. Only a run
|
||||
; of spaces means this is still the first word; anything else is an argument, and finishing
|
||||
; one of those is not built yet.
|
||||
SETD.1 TabWordAt
|
||||
LDA.1
|
||||
SETD.1 TabLeft
|
||||
STA.1
|
||||
tabFirstCheck:
|
||||
SETD.1 TabLeft
|
||||
LDA.1
|
||||
BRA tabIsFirst
|
||||
DECA
|
||||
STA.1
|
||||
CALL tabCharAt
|
||||
MVQA
|
||||
INIB 0x20
|
||||
XOR
|
||||
BNQ tabDone ; Not a space, so a word came before this one.
|
||||
BRI tabFirstCheck
|
||||
|
||||
tabIsFirst:
|
||||
; How much of it is typed. Nothing at all is not a question anybody asked - it would offer
|
||||
; every command there is, which is what help is for.
|
||||
SETD.1 EditAt
|
||||
LDA.1
|
||||
SETD.1 TabWordAt
|
||||
LDB.1
|
||||
CCF
|
||||
SUB
|
||||
BRQ tabDone
|
||||
MVQA
|
||||
SETD.1 TabWordLen
|
||||
STA.1
|
||||
|
||||
RSTA
|
||||
SETD.1 TabCount
|
||||
STA.1
|
||||
SETD.1 TabBestLen
|
||||
STA.1
|
||||
|
||||
CALL tabTryNames
|
||||
|
||||
SETD.1 TabCount
|
||||
LDA.1
|
||||
BRA tabDone ; Nothing matched, and saying so would be noise.
|
||||
|
||||
; ---- Putting it in ----
|
||||
;
|
||||
; Everything of the answer past what was already typed. The insertion point is at the end
|
||||
; of the word, so each character goes in where the one before it left off.
|
||||
SETD.1 TabWordLen
|
||||
LDA.1
|
||||
SETD.1 TabPut
|
||||
STA.1
|
||||
tabPutNext:
|
||||
SETD.1 TabPut
|
||||
LDA.1
|
||||
SETD.1 TabBestLen
|
||||
LDB.1
|
||||
CCF
|
||||
SUB
|
||||
; At or past, rather than exactly equal. They cannot pass each other now - a candidate is
|
||||
; only offered if it matches what was typed, so the answer is never shorter than that - but
|
||||
; they DID while the answer was being taken from the wrong place, and the difference
|
||||
; between the two branches was a line that ran off the end and a line that simply did not
|
||||
; grow. The cheaper failure is worth a branch that costs nothing.
|
||||
BNC tabPutDone
|
||||
SETD.3 TabBest
|
||||
SETD.1 TabPut
|
||||
LDA.1
|
||||
DPUA.3
|
||||
LDA.3
|
||||
SETD.1 EditChar
|
||||
STA.1
|
||||
CALL editPut
|
||||
BNQ tabPutDone ; The line is full, so the rest of the answer will not fit.
|
||||
SETD.1 TabPut
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
BRI tabPutNext
|
||||
tabPutDone:
|
||||
|
||||
SETD.1 TabCount
|
||||
LDA.1
|
||||
INIB 0d1
|
||||
XOR
|
||||
BNQ tabAmbiguous
|
||||
|
||||
; Only one thing it can be, so it is finished and a space says so.
|
||||
INIA 0x20
|
||||
SETD.1 EditChar
|
||||
STA.1
|
||||
CALL editPut
|
||||
CALL editRedraw
|
||||
BRI tabDone
|
||||
|
||||
tabAmbiguous:
|
||||
; Several. The common prefix has gone in; if it added nothing then what was typed is
|
||||
; already as far as they all agree, and the only useful thing left is to show them.
|
||||
SETD.1 TabBestLen
|
||||
LDA.1
|
||||
SETD.1 TabWordLen
|
||||
LDB.1
|
||||
CCF
|
||||
SUB
|
||||
BRQ tabList
|
||||
CALL editRedraw
|
||||
tabDone:
|
||||
BRI editKey
|
||||
|
||||
; A holds an offset into the line. Q is the character there.
|
||||
tabCharAt:
|
||||
SETD.1 EditBase
|
||||
LDD.0.1
|
||||
DPUA.0
|
||||
LDA.0
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; ---- Offering a candidate ----
|
||||
;
|
||||
; DP3 names a string ending in a zero. If it begins with what has been typed it is folded
|
||||
; into the answer, and DP3 is left just past that zero either way - which is what makes a run
|
||||
; of packed strings walkable by calling this over and over.
|
||||
tabOffer:
|
||||
; Where this candidate begins, before the comparison below walks DP3 through it. What is
|
||||
; taken as the answer is the WHOLE name and not the part after the bit that matched.
|
||||
SETD.1 TabStart
|
||||
STD.3.1
|
||||
SETD.1 TabWordLen
|
||||
LDA.1
|
||||
SETD.1 TabSeen
|
||||
STA.1
|
||||
RSTA
|
||||
SETD.1 TabAt
|
||||
STA.1
|
||||
tabOfferMatch:
|
||||
SETD.1 TabSeen
|
||||
LDA.1
|
||||
BRA tabOfferTake ; Every typed character agreed.
|
||||
DECA
|
||||
STA.1
|
||||
|
||||
LDA.3
|
||||
BRA tabOfferSkip ; The candidate ended before what was typed did.
|
||||
SETD.1 TabHold
|
||||
STA.1 ; Kept, while the line's character is fetched.
|
||||
|
||||
SETD.1 TabWordAt
|
||||
LDA.1
|
||||
SETD.1 TabAt
|
||||
LDB.1
|
||||
CCF
|
||||
ADD
|
||||
MVQA
|
||||
CALL tabCharAt
|
||||
MVQA
|
||||
SETD.1 TabHold
|
||||
LDB.1
|
||||
XOR
|
||||
BNQ tabOfferSkip
|
||||
|
||||
INCD.3
|
||||
SETD.1 TabAt
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
BRI tabOfferMatch
|
||||
|
||||
tabOfferTake:
|
||||
; The first match is the answer. Every one after it cuts the answer back to where the two
|
||||
; stop agreeing, which is all that can be said without guessing which was meant.
|
||||
SETD.1 TabCount
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
INIB 0d1
|
||||
XOR
|
||||
BRQ tabOfferFirst
|
||||
CALL tabNarrow
|
||||
BRI tabOfferSkip
|
||||
tabOfferFirst:
|
||||
CALL tabTake
|
||||
|
||||
tabOfferSkip:
|
||||
; Past the end of this candidate, from wherever the comparison stopped.
|
||||
LDA.3
|
||||
BRA tabOfferPast
|
||||
INCD.3
|
||||
BRI tabOfferSkip
|
||||
tabOfferPast:
|
||||
INCD.3
|
||||
RET
|
||||
|
||||
; The candidate DP3 names becomes the answer. DP3 is put somewhere and read back rather than
|
||||
; walked, because it is the only pointer that survives a call and the caller still needs it.
|
||||
tabTake:
|
||||
SETD.1 TabStart
|
||||
LDD.0.1
|
||||
SETD.2 TabBest
|
||||
RSTA
|
||||
SETD.1 TabBestLen
|
||||
STA.1
|
||||
tabTakeNext:
|
||||
SETD.1 TabBestLen
|
||||
LDA.1
|
||||
INIB 0d63
|
||||
CCF
|
||||
SUB
|
||||
BNC tabTakeDone ; As much of a name as there is room for.
|
||||
LDA.0
|
||||
BRA tabTakeDone
|
||||
STA.2
|
||||
INCD.0
|
||||
INCD.2
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
BRI tabTakeNext
|
||||
tabTakeDone:
|
||||
RET
|
||||
|
||||
; The answer is cut back to where it and the candidate DP3 names stop agreeing.
|
||||
tabNarrow:
|
||||
SETD.1 TabStart
|
||||
LDD.0.1
|
||||
SETD.2 TabBest
|
||||
RSTA
|
||||
SETD.1 TabNarrowAt
|
||||
STA.1
|
||||
tabNarrowNext:
|
||||
SETD.1 TabNarrowAt
|
||||
LDA.1
|
||||
SETD.1 TabBestLen
|
||||
LDB.1
|
||||
CCF
|
||||
SUB
|
||||
BNC tabNarrowDone ; The answer ran out first, so all of it still agrees.
|
||||
LDA.0
|
||||
BRA tabNarrowCut ; The candidate ran out, so the answer stops here.
|
||||
LDB.2
|
||||
XOR
|
||||
BNQ tabNarrowCut
|
||||
INCD.0
|
||||
INCD.2
|
||||
SETD.1 TabNarrowAt
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
BRI tabNarrowNext
|
||||
tabNarrowCut:
|
||||
SETD.1 TabNarrowAt
|
||||
LDA.1
|
||||
SETD.1 TabBestLen
|
||||
STA.1
|
||||
tabNarrowDone:
|
||||
RET
|
||||
|
||||
; ---- The shell's own words ----
|
||||
;
|
||||
; Fifteen strings packed end to end, each ending in the zero that says where the next one
|
||||
; begins. That is why they are laid out that way: a chain of comparisons can be executed and
|
||||
; cannot be walked.
|
||||
tabTryNames:
|
||||
SETD.1 ShellNameCount
|
||||
LDA.1
|
||||
SETD.1 TabLeft
|
||||
STA.1
|
||||
SETD.3 ShellNames
|
||||
tabNameNext:
|
||||
SETD.1 TabLeft
|
||||
LDA.1
|
||||
BRA tabNamesDone
|
||||
DECA
|
||||
STA.1
|
||||
CALL tabOffer
|
||||
BRI tabNameNext
|
||||
tabNamesDone:
|
||||
RET
|
||||
|
||||
; ---- Showing them when they cannot be narrowed further ----
|
||||
;
|
||||
; The line is reprinted underneath afterwards, and where it now BEGINS is asked rather than
|
||||
; remembered: the prompt has just been printed somewhere else entirely, and the whole of the
|
||||
; editor's idea of where things are hangs off those two registers.
|
||||
tabList:
|
||||
CALL newLine
|
||||
CALL tabListNames
|
||||
CALL newLine
|
||||
CALL sayPrompt
|
||||
|
||||
INA 0x03
|
||||
SETD.1 EditRow
|
||||
STA.1
|
||||
INA 0x04
|
||||
SETD.1 EditColumn
|
||||
STA.1
|
||||
RSTA
|
||||
SETD.1 EditDrawn
|
||||
STA.1 ; Nothing of the line is on the screen at its new place.
|
||||
CALL editRedraw
|
||||
BRI editKey
|
||||
|
||||
tabListNames:
|
||||
SETD.1 ShellNameCount
|
||||
LDA.1
|
||||
SETD.1 TabLeft
|
||||
STA.1
|
||||
SETD.3 ShellNames
|
||||
tabListNext:
|
||||
SETD.1 TabLeft
|
||||
LDA.1
|
||||
BRA tabListDone
|
||||
DECA
|
||||
STA.1
|
||||
CALL tabShow
|
||||
BRI tabListNext
|
||||
tabListDone:
|
||||
RET
|
||||
|
||||
; DP3 names a candidate. Prints it if it begins with what was typed, and leaves DP3 past its
|
||||
; zero either way, the same bargain tabOffer makes.
|
||||
tabShow:
|
||||
SETD.1 TabStart
|
||||
STD.3.1
|
||||
SETD.1 TabWordLen
|
||||
LDA.1
|
||||
SETD.1 TabSeen
|
||||
STA.1
|
||||
RSTA
|
||||
SETD.1 TabAt
|
||||
STA.1
|
||||
tabShowMatch:
|
||||
SETD.1 TabSeen
|
||||
LDA.1
|
||||
BRA tabShowIt
|
||||
DECA
|
||||
STA.1
|
||||
LDA.3
|
||||
BRA tabShowSkip
|
||||
SETD.1 TabHold
|
||||
STA.1
|
||||
SETD.1 TabWordAt
|
||||
LDA.1
|
||||
SETD.1 TabAt
|
||||
LDB.1
|
||||
CCF
|
||||
ADD
|
||||
MVQA
|
||||
CALL tabCharAt
|
||||
MVQA
|
||||
SETD.1 TabHold
|
||||
LDB.1
|
||||
XOR
|
||||
BNQ tabShowSkip
|
||||
INCD.3
|
||||
SETD.1 TabAt
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
BRI tabShowMatch
|
||||
|
||||
tabShowIt:
|
||||
SETD.1 TabStart
|
||||
LDD.0.1
|
||||
CALL printString
|
||||
INIA 0x20
|
||||
OUTA 0x00
|
||||
OUTA 0x00 ; Two spaces between them, and A already holds one.
|
||||
|
||||
tabShowSkip:
|
||||
LDA.3
|
||||
BRA tabShowPast
|
||||
INCD.3
|
||||
BRI tabShowSkip
|
||||
tabShowPast:
|
||||
INCD.3
|
||||
RET
|
||||
|
||||
; ---- A command that did not work ----
|
||||
;
|
||||
; The one place a failure is recorded, so that the thing reading lines out of a file can
|
||||
@@ -5970,6 +6426,38 @@ EditWalkBack:
|
||||
EditWasControl:
|
||||
0x00
|
||||
|
||||
; ---- What Tab is working on ----
|
||||
TabWordAt:
|
||||
0x00
|
||||
TabWordLen:
|
||||
0x00
|
||||
TabCount:
|
||||
0x00
|
||||
TabLeft:
|
||||
0x00
|
||||
TabSeen:
|
||||
0x00
|
||||
TabAt:
|
||||
0x00
|
||||
TabHold:
|
||||
0x00
|
||||
TabPut:
|
||||
0x00
|
||||
TabNarrowAt:
|
||||
0x00
|
||||
TabBestLen:
|
||||
0x00
|
||||
; Where a candidate is, put down so that a routine which needs to walk it can pick it up
|
||||
; without moving the pointer its caller is still using.
|
||||
TabPointer:
|
||||
0x00 0x00
|
||||
; And where the candidate being looked at begins, since the comparison walks past it.
|
||||
TabStart:
|
||||
0x00 0x00
|
||||
; The answer so far: the first match, cut back by every one after it to where they agree.
|
||||
TabBest:
|
||||
#Reserve 0d64
|
||||
|
||||
; ---- What the fault screen is saying ----
|
||||
;
|
||||
; Whether this cause names an entry as well as itself, and which one. Only the two faults
|
||||
|
||||
Reference in New Issue
Block a user