diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index b0bdc58..b97dbf4 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -294,6 +294,7 @@ line can be moved about in at all. | Home, End | Go to the start of the line or the end of it. | | Backspace | Take out the character before the cursor. | | Delete | Take out the one under it. | +| Up, Down | Walk back through the last eight lines, and forward again. | | Return | Finish the line, wherever the cursor happens to be sitting. | Anything typed goes in where the cursor is, so a word left out of the middle of a line is @@ -307,6 +308,25 @@ line, and nowhere for a history to live. Now the console delivers keys and says about what they mean, the same way it reports what a drive is and says nothing about what should be on it, and the shell decides. +### The History: + +The last eight lines are kept, and Up walks back through them. It is a ring: a ninth line +pushes the oldest out by moving where the ring starts rather than by moving any of the +lines. + +An empty line is not kept - that is somebody pressing Return - and neither is a line the +same as the one already at the top, so running a command twice does not put it in twice. + +**What you were typing is kept too.** Pressing Up when you were half way through a line puts +that line somewhere and Down brings it back, so looking at what you did before does not cost +you what you were doing. + +The history belongs to the shell rather than to the console, and that is the whole reason it +can exist: until the keys reached the system there was nothing to press Up at. It is shared +with the monitor, which reads its lines the same way - and since the monitor reads into a +buffer of forty characters where the shell reads into one of 127, a longer line recalled +there is cut short rather than written past the end of it. + The line holds 127 characters. It held 63 until the shell could edit one, 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. diff --git a/Programs/CosmOS/Source/cosmos.asm b/Programs/CosmOS/Source/cosmos.asm index 9388e59..46ae33f 100644 --- a/Programs/CosmOS/Source/cosmos.asm +++ b/Programs/CosmOS/Source/cosmos.asm @@ -453,6 +453,8 @@ editLine: STA.1 SETD.1 EditAt STA.1 + SETD.1 EditDrawn + STA.1 SETD.1 ConsoleEndOfInput STA.1 @@ -531,6 +533,12 @@ editNotEnd: INIB 0x85 XOR BRQ editEnd + INIB 0x80 + XOR + BRQ editUp + INIB 0x81 + XOR + BRQ editDown ; 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 @@ -600,6 +608,10 @@ editInsertPut: SETD.1 EditChar LDA.1 OUTA 0x00 + SETD.1 EditLength + LDA.1 + SETD.1 EditDrawn + STA.1 BRI editKey editInsertRedraw: CALL editRedraw @@ -636,6 +648,10 @@ editBack: OUTA 0x00 INIA 0x08 OUTA 0x00 + SETD.1 EditLength + LDA.1 + SETD.1 EditDrawn + STA.1 BRI editKey editBackMiddle: CALL editTakeOut @@ -722,6 +738,64 @@ editEnd: LDA.1 SETD.1 EditAt STA.1 + BRI editShow + +; ---- Backwards and forwards through what was typed before ---- +; +; The line on the screen is replaced outright, so both of these redraw. That is also what +; makes the padding below matter: a long line replaced by a short one leaves the tail of the +; long one behind unless something rubs it out. +editUp: + SETD.1 HistoryPick + LDA.1 + BRA editKey ; Already at the oldest one kept. + + ; Leaving the line being typed for the first time, so it is put somewhere. Down brings it + ; back, and a person who pressed Up to look at something gets their line returned rather + ; than taken. + SETD.1 HistoryCount + LDB.1 + CCF + SUB + BNQ editUpMoving + SETD.3 HistoryTyped + CALL historyPut +editUpMoving: + SETD.1 HistoryPick + LDA.1 + DECA + STA.1 + CALL historySlot + BRI editRecall + +editDown: + SETD.1 HistoryPick + LDA.1 + SETD.1 HistoryCount + LDB.1 + CCF + SUB + BRQ editKey ; Already back at the line being typed. + SETD.1 HistoryPick + LDA.1 + INCA + STA.1 + SETD.1 HistoryCount + LDB.1 + CCF + SUB + BRQ editDownTyped + SETD.1 HistoryPick + LDA.1 + CALL historySlot + BRI editRecall +editDownTyped: + SETD.3 HistoryTyped +editRecall: + CALL historyTake + CALL editRedraw + BRI editKey + editShow: SETD.1 EditAt LDA.1 @@ -752,6 +826,10 @@ editDone: CALL editPlace editDoneEnd: CALL newLine + + ; Kept before the buffer is handed over and while EditLength still says how long it is. + ; Only on this path: a console that ran out was not somebody finishing a line. + CALL historyAdd editFinish: ; Line mode, with the cursor still on: that is how boot left the console and how every @@ -799,10 +877,48 @@ editRedrawNext: 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 + ; ---- Rubbing out what is no longer there ---- + ; + ; One space was enough while the only thing that ever shortened a line was taking one + ; character out of it. Pressing Up replaces the whole line, and a long one replaced by a + ; short one leaves the tail of the long one sitting on the screen looking like part of + ; what you are typing. + ; + ; So the spaces go on for exactly as far as the line has shrunk, which is one after a + ; character was taken out and none at all when one was put in. printSpaces takes none for + ; an answer and prints nothing, which is what makes that case cost nothing to allow. + SETD.1 EditDrawn + LDA.1 + SETD.1 EditLength + LDB.1 + CCF + SUB + BRC editRedrawGrew ; Borrowed, so the line is longer than what is up there. + MVQA + BRI editRedrawPad +editRedrawGrew: + RSTA +editRedrawPad: + SETD.1 EditPrinted + STA.1 + CALL printSpaces + + ; How much went to the screen altogether, which is how far back the start of the line is. + SETD.1 EditPrinted + LDA.1 + SETD.1 EditLength + LDB.1 + CCF + ADD + MVQA + SETD.1 EditPrinted + STA.1 + + ; And what is up there now is exactly the line. + SETD.1 EditLength + LDA.1 + SETD.1 EditDrawn + STA.1 CALL editAnchor SETD.1 EditAt @@ -823,9 +939,8 @@ editAnchor: INA 0x04 SETD.1 EditWalkColumn STA.1 - SETD.1 EditLength + SETD.1 EditPrinted LDA.1 - INCA ; The space was printed too. SETD.1 EditWalkBack STA.1 editAnchorWalk: @@ -919,6 +1034,183 @@ editPlaceDone: OUTA 0x04 RET +; ---- What was typed before ---- +; +; Eight lines, oldest first, in a RING: a ninth pushes the oldest out by moving where the +; ring starts rather than by moving any of the lines. Eight is a power of two, so which slot +; an entry lives in is an AND rather than a division, and nothing is ever copied to make +; room. +; +; A NINTH SLOT HOLDS WHAT WAS BEING TYPED when Up was first pressed, and Down walks back into +; it. Losing a half written line to a keypress is exactly the sort of small rudeness that +; makes a thing unpleasant to use, and it costs one slot to avoid. +; +; The history belongs to the SHELL rather than to the console, which is the whole reason it +; can exist at all: until the keys arrived here, there was nothing to press Up at. + +; DP3 to history entry A, where entry nought is the oldest one still kept. +historySlot: + SETD.1 HistoryStart + LDB.1 + CCF + ADD + MVQA + INIB 0d7 + AND + MVQA + SETD.3 HistoryLines + + ; A slot is 128 bytes, and A and B are a sixteen bit shift register: one rotation right + ; with B empty turns the slot number into the offset of the slot, high byte and low. + RSTB + SHR + DPUW.3 + RET + +; Copies the line being edited into the slot DP3 names. +historyPut: + SETD.1 EditBase + LDD.0.1 + SETD.1 EditLength + LDB.1 +historyPutNext: + BRB historyPutEnd + LDA.0 + STA.3 + INCD.0 + INCD.3 + DECB + BRI historyPutNext +historyPutEnd: + RSTA + STA.3 + RET + +; Copies the line in the slot DP3 names into the buffer, and puts the cursor at the end of +; it, which is where somebody who has just recalled a line wants to be. +; +; A LINE TOO LONG FOR THE BUFFER IS CUT SHORT rather than written past the end of it. The +; history is shared with the monitor, which reads into forty bytes where the shell reads +; into a hundred and twenty seven, so this is a real case and not a defensive one. +historyTake: + SETD.1 EditBase + LDD.0.1 + RSTA + SETD.1 EditLength + STA.1 +historyTakeNext: + SETD.1 EditLength + LDA.1 + SETD.1 EditRoom + LDB.1 + CCF + SUB + BNC historyTakeDone ; No room for another character. + LDA.3 + BRA historyTakeDone + STA.0 + INCD.0 + INCD.3 + SETD.1 EditLength + LDA.1 + INCA + STA.1 + BRI historyTakeNext +historyTakeDone: + RSTA + STA.0 + SETD.1 EditLength + LDA.1 + SETD.1 EditAt + STA.1 + RET + +; DP0 and DP3 name strings ending in zero bytes. Q is zero if they are the same. +; +; textSame does this already and takes its two strings in DP0 and DP1, and getting a pointer +; from DP3 into DP1 costs a store, a scratch word and a load. This is shorter than that and +; says what it is doing. +historySameAs: + LDA.0 + LDB.3 + XOR + BNQ historySameNo + LDA.0 + BRA historySameYes ; They matched and they both ended. + INCD.0 + INCD.3 + BRI historySameAs +historySameNo: + INIA 0x01 + RSTB + CCF + ADD ; Q is one. + RET +historySameYes: + RSTA + RSTB + CCF + ADD ; Q is zero. + RET + +; The line just finished, kept. An empty one is not - that is somebody pressing Return - and +; neither is one the same as the newest already there, because running a command twice +; should not put it in twice. +historyAdd: + SETD.1 EditLength + LDA.1 + BRA historyAddDone + + SETD.1 HistoryCount + LDA.1 + BRA historyAddPut ; Nothing kept yet, so nothing to be the same as. + DECA + CALL historySlot + SETD.1 EditBase + LDD.0.1 + CALL historySameAs + BRQ historyAddDone + +historyAddPut: + SETD.1 HistoryCount + LDA.1 + INIB 0d8 + CCF + SUB + BRC historyAddRoom ; Fewer than eight, so there is a slot on the end. + + ; Full. The new line goes over the oldest and the ring starts one further along, which + ; forgets the oldest without moving any of the others. + INIA 0d8 + CALL historySlot + CALL historyPut + SETD.1 HistoryStart + LDA.1 + INCA + INIB 0d7 + AND + MVQA + STA.1 + BRI historyAddDone + +historyAddRoom: + SETD.1 HistoryCount + LDA.1 + CALL historySlot + CALL historyPut + SETD.1 HistoryCount + LDA.1 + INCA + STA.1 + +historyAddDone: + ; However that went, the next Up starts from the newest again. + SETD.1 HistoryCount + LDA.1 + SETD.1 HistoryPick + STA.1 + 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 @@ -5337,6 +5629,36 @@ EditWalkColumn: EditWalkBack: 0x00 +; How many characters of the line are on the screen, which is not always how many are in it: +; between a line getting shorter and the screen being put right, the screen still has the +; old one on it. That gap is one character wide for a Delete and a whole line wide for a +; recalled one. +EditDrawn: + 0x00 + +; How much a redraw sent to the screen, line and rubbing out together. The start of the line +; is that far back from where the cursor ended up. +EditPrinted: + 0x00 + +; Eight lines of a hundred and twenty eight bytes, and one more holding whatever was being +; typed when somebody pressed Up. +HistoryLines: + #Reserve 0d1024 +HistoryTyped: + #Reserve 0d128 + +; Which slot holds the oldest, which is what moves when a ninth line pushes one out. +HistoryStart: + 0x00 +; How many of the eight hold anything. +HistoryCount: + 0x00 +; Which one is on the screen, counting the oldest as nought. HistoryCount means none of them +; is: what is on the screen is the line being typed. +HistoryPick: + 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 diff --git a/SplitBit Test Manual.md b/SplitBit Test Manual.md index 35693f2..608bedf 100644 --- a/SplitBit Test Manual.md +++ b/SplitBit Test Manual.md @@ -79,7 +79,7 @@ from `make`, not from here. ### 1. Recorded output `Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares -everything it printed against a file in `Tests/expected`. 190 tests, of which 128 run, 35 +everything it printed against a file in `Tests/expected`. 191 tests, of which 129 run, 35 only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image given at all. diff --git a/Tests/expected/cosmosEditKeys.out b/Tests/expected/cosmosEditKeys.out index 98dec0e..e6bb5ae 100644 --- a/Tests/expected/cosmosEditKeys.out +++ b/Tests/expected/cosmosEditKeys.out @@ -1,5 +1,5 @@ CosmOS -> eco okecho ok  +> eco okecho ok ok > Xecho okecho ok  ok @@ -7,7 +7,7 @@ ok ok > echo okX  ok -> echo kecho ok  +> echo kecho ok ok > Xecho aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaecho aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ! aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa! diff --git a/Tests/expected/cosmosHistory.out b/Tests/expected/cosmosHistory.out new file mode 100644 index 0000000..c57a373 --- /dev/null +++ b/Tests/expected/cosmosHistory.out @@ -0,0 +1,33 @@ +CosmOS +> echo a +a +> echo b +b +> echo c +c +> echo d +d +> echo e +e +> echo f +f +> echo g +g +> echo h +h +> echo i +i +> echo iecho hecho gecho fecho eecho decho cecho b +b +> echo zecho becho z +z +> echo z +z +> echo zecho b +b +> echo yyyyyyyyyyyyyyyyyyyyecho b echo yyyyyyyyyyyyyyyyyyyy +yyyyyyyyyyyyyyyyyyyy +> exit +halted +Execution halted. +[exit 0] diff --git a/Tests/input/cosmosHistory.in b/Tests/input/cosmosHistory.in new file mode 100644 index 0000000..c227c21 --- /dev/null +++ b/Tests/input/cosmosHistory.in @@ -0,0 +1,15 @@ +echo a +echo b +echo c +echo d +echo e +echo f +echo g +echo h +echo i +€€€€€€€€€ +echo z€ +€ +€€ +echo yyyyyyyyyyyyyyyyyyyy€ +exit diff --git a/Tests/manifest b/Tests/manifest index 753175d..4056185 100644 --- a/Tests/manifest +++ b/Tests/manifest @@ -326,6 +326,16 @@ cosmosNoData | CosmOS/Source/cosmos.asm | run | cosmosNoD # is. The last line is eighty five characters at a prompt in column two, so the cursor has to # be found on the row below - the one case the arithmetic exists for. cosmosEditKeys | CosmOS/Source/cosmos.asm | run | cosmosEditKeys.in | - | disks/cosmos.img +# And the shell remembering what was typed before it. Nine lines against eight slots, so the +# oldest is pushed out and nine presses of Up can only walk back eight; then the line being +# typed is kept when Up leaves it and comes back on Down; then a line run twice is not kept +# twice, which the LAST case is what proves - if it were, one further back would be the same +# line again instead of the one before it. +# +# The long line at the end is the rubbing out. A short line recalled over a long one leaves +# the tail of the long one on the screen unless something covers it, and one space - which +# is all a Delete ever needs - covers nothing. +cosmosHistory | CosmOS/Source/cosmos.asm | run | cosmosHistory.in | - | disks/cosmos.img # The shell taking things off a disk and calling them something else, which is the last of # CosmOS's original four verbs to be built and the first time anything has changed a disk # from the shell. Its own image, because it changes what is on it: a fixture named with a