scotws / TaliForth2

A Subroutine Threaded Code (STC) ANS-like Forth for the 65c02
Other
86 stars 22 forks source link

Add a history function #25

Closed scotws closed 6 years ago

scotws commented 6 years ago

Use CTRL-P and CTRL-N (which should be the traditional Unix shell codes) to bring up at least the last input. This probably requires the ability to get multi-byte input(?)

SamCoVT commented 6 years ago

CTRL keys are fairly easy - the CTRL key simply reduces the ASCII value of a letter (the capital version) by 64. This makes CTRL-A send ascii value 1, CTRL-B send ascii value 2...

CTRL-P sends 16 and CTRL-N sends 14. I don't believe these are in common use and should be usable.

You do have to be careful as some already have other uses, like CTRL-H is character 8 which is also the backspace character, CTRL-M is character 13 which is also a carriage return, CTRL-J is character 10 which is also a newline, and CTRL-L is character 12 which is also a form feed. I use CTRL-L all the time in terminals as it clears the screen.

SamCoVT commented 6 years ago

After reading your code, I see that you knew most of this already.
Skip to the bottom as my thoughts below don't work but I wanted to save them

It looks like xt_accept is the place to add this in your native_words.asm source code. The simplest form would be to support CTRL-P only and have it reuse the "stale" data already in the input buffer. My thoughts, when character 16 (decimal) is discovered would be:

While Y is less than the previous entry size:
    print the character already in the buffer
    increment the number of chars in the buffer in y and store at 0,x

This would result in the previous command being "replayed" at the prompt, and the user would be able to edit it (with backspace or more typing) before hitting enter. It would also allow one to hit CTRL-P to get the previous command, hit backspace a bunch of times, fix a typo, and hit CTRL-P again to finish up the command from that point (assuming no characters were added/removed - just replaced). Your _bs: code already looks like it is non-destructive to the buffer contents.

This probably needs some extra checking to make sure you don't "fill" the buffer more than was requested, in case someone calls ACCEPT with a smaller number. Another possible issue is if the previous data filled the entire buffer, what should happen if you hit CTRL-P? Should it replay the entire buffer and then automatically return because the buffer is now full?

I'd be interested in trying to implement this. It looks like it needs an extra 16-bit spot in memory (or 8-bits if you didn't mind committing to 256 characters) Where would you suggest placing this "Previous Input Buffer Used" variable? If you really wanted to commit to 256 byte max sized input buffer, I could use the upper half of your current 16-bit memory location.

Skip to here

And now.... while looking at the code, I've realized that ACCEPT takes the address of the buffer to use and won't always be used on the same buffer. I was under the impression that your input buffer was always where the input went. I think this pretty much obliterates my thoughts of reusing the existing input buffer. I've left my thoughts above in case they are helpful towards figuring this out. I don't think I'm interested in trying to implement this anymore, but I will certainly think about it some more.

scotws commented 6 years ago

Well, the stuff in the code is ... confused. I had originally wanted to have two buffers and be able to switch between them, so the user had one line at least of history to go back to with CNTRL-p and CNTRL-n (which in practice would have done the same thing). That turned out to be harder than I thought, so I shelved the whole thing. In the meantime, I've been wondering if it wouldn't be worth the effort to

  1. have a far larger history by using the normally unused high RAM parts -- stuff at $7FFF and growing towards $0000 -- as a series of buffers,
  2. capture the more complex input such as ARROW-UP that normal people use for history as well

I've put both on the back burner until the code is more stable, but every time I test something and have to retype the whole friggin' line, I think that a real history would be really, really cool.

So, long story short, I'm very open to any suggestion how to do this. The memory model is going to get more messy anyway because at some point Tali Forth should have cooperative multitasking with PAUSE etc., which means that there are going to have to be real USER variables etc. If you have a suggestion how to handle history, feel free to completely ignore my previous (and half-baked) concepts.

SamCoVT commented 6 years ago

I think that I would enjoy a history as much as you would, but I can see now how complex it can become. I have no problem with waiting until later to solve this, but I do enjoy thinking about the problem and possible solutions.

I think it makes sense to hide as much as possible of this in ACCEPT so that the rest of Tali doesn't know anything about it. That would even allow the use of the history when ACCEPT is used in interactive programs.

If 1K were reserved for history, you could have 4x256 byte buffers or 8x128 byte (my preference) buffers. The 256 byte buffers is probably significanly easier to code. I would store the history similar to how s" works now, with a length byte followed by the string data. Just before ACCEPT returns, it would copy the data to the next location. If 128 byte history buffers are used, I'd recommend still having ACCEPT allow up to 256 characters, but only remembering the first 127 of them for the history. For me, that would be perfectly acceptable for live use.

You will need a method to determine which history buffer is going to be "next". An 8-bit memory location could be used as a mod 4 or mod 8 counter, and then that could be used to calculate the address of the next history buffer to use (round robin). While least-recently-used would be nicer, I think I could deal with 8 round-robin locations as most of my hand-entered forth is 4 lines or less. With that said, I suppose I'd also be able to deal with a 4 memory history if you wanted longer lines.

CTRL-P and CTRL-N, when used, would adjust the modulo counter, then copy the data from that history buffer on top of the current editing buffer (down in low memory). Once edits are complete and enter is pressed, the currently selected history buffer is overwritten with the new data and the modulo counter is advanced. It's debatable whether the new line should go on top of the one you pulled up, or if it should go in the history buffer originally slated for it.

Because the history buffers aren't overwritten until you actually hit enter, you could hit CTRL-P and then CTRL-N to get the contents of the buffer you were about to overwrite. It also might make sense to add CTRL-C or ESC to clear out the current line (not in the history, just at the prompt - eg. to cancel CTRL-P and give you a blank line again).

At startup the buffers would need to be cleared, which should be as simple as writing 0 in the length byte of each. It doesn't actually matter where the modulo counter starts.

I think that's about as much thinking as I can do on that for today. Let me know if any of this sounds appealing. I certainly could try to mock some of these ideas up so that we could take it for a "test drive". I don't have a whole lot of 65c02 assembly experience, but I don't mind learning while trying. I've programmed for simpler (Microchip PIC, the low end) and more complex (x386 segmented real mode) processors in assembly, and the 6502 seems to be pretty decent so far.

scotws commented 6 years ago

My suggestion would be -- pick anything that seems like it would be fun to do, send a branch with it to test, and we'll take it from there. I'm not going to be able get much done this coming week (I usually don't get anything coded in summer, so far, so good), so the code base should be a bit more stable.

And for the record, people who program in x86 assembler scare me :-). How did you stand it? I took one look years ago and ran away screaming ...

SamCoVT commented 6 years ago

It's slow going, but I'm making some progress. I currently have memory chosen (at the end of RAM). I realised that I put my I/O section from $7F00 to $7FFF on my hardware, but I'll either move that or work around that. I have (untested) code to select the previous history buffer and compute its starting address, and my next bit of code to tackle is the copying of the data in the history buffer into the current buffer (and printing it on the screen).

I'm not sure what to do with the text that's already on the screen - my code isn't going to save it (it only will save completed lines when ENTER is pressed), but I'm not sure how to erase it off the screen. For now, I'm just going to send a AscCR (which will either move the cursor to the beginning of the current line or the beginning of the next line, depending on terminal settings) and print out the history from there, leaving the cursor at the end (as if the user just typed it all.) I'll be working on that this weekend.

I'm going to need to initialize the history buffers (by just setting the length in the first byte to 0). Can you give me a hint where I should put that initialization code?

I also need to get more familiar with the py65 emulator so I can step through and test my code. I think that will be much easier than real hardware.

As for my x86 assembly programming, I'll just say it's what I had for a computer at the time, and I didn't know any better :)

SamCoVT commented 6 years ago

I've submitted pull request #39 I think I've got it working. Take a look and let me know what you think. I've already found it useful even while just trying to test it.

scotws commented 6 years ago

Thanks for the code! It certainly makes a big difference, next project I'm going to include something like this from the beginning.

I've added a line to the documentation about the command line history and replaced the LDA/STA combo with STZ; also, I've been using INC instead of INA, and though I'm thinking INA might make more sense, I'd like to keep it the same in all the code until we can go through and change it all.

Thanks again!