So here we are once again with the screen capture software for another live coding session and today I'm going to write the last remaining piece for my CPM open-source distribution, which is the text editor Now I've been having trouble finding a text editor for CPM. It's not like there aren't lots, but they're all either commercial or unlicensed or far too big But recently I happened to discover this This is Ants editor. It's a VI subset Written in 31 lines of code for the international obfuscated C contest. It's intended to be unreadable Now this is a really really simple But working text editor. Here is the unobfuscated version and it's still only 360 lines. I Can't use this editor. This relies heavily on Cursors to do the screen updates and I don't have cursors because I'm on CPM but nevertheless the logic it uses to actually do the editing is spectacularly simple fast and extremely effective so With inspiration from this I Am going to attempt to write my own editor and it is going to be Like Ants editor a VI subset over hopefully a slightly bigger VI subset so Let us start with some boilerplate This is going to be written in C But I'm not using STCC this time I'm using the Amsterdam compiler kit because it generates 8080 code and is substantially less buggy So here we have a minimal program. Let us compile this. There we go and our file is 420 bytes long which is quite big, but most of that is library overhead and Now I want to run it. So over here. I have CPM is running in a K-Pro and I Can hear disc noises when I do things. This is using the mess emulator and mess emulates floppy disk drive sounds, but I'm not sure if you can Due to the way the screen capture works So in order to actually run the thing. I need to copy the file Think that's right Into This is right Not quite this is right Okay, so that copies the QE.com file into the disk image. So if you do here and do control C Control C the K-Pro control key is in the wrong place and dir It hasn't shown up. Oh great. That means that I'm going to have to restart the emulator every time Which is annoying so I can't Use the emulator to test this because the emulator doesn't have the terminal It doesn't emulate the terminal emulator that the K-Pro's got So there you can see our QE.com you can run that when it does nothing of course Well, that's irritating. I thought I was going to have the I Thought I was going to be able to leave the emulator running and insert the file into the image but it looks like mess is Somehow taking a copy of the image like loading it into memory okay, well Let's just add the QE.com to our disk image then just to make life easier and Add a program to build system Okay, so now I can just do make and It will build QE.com as well as a whole bunch of other things into a disk image There it is good Okay Back to the program Now there are two parts to our editor The second part, which is actually probably the easiest bit is the actual editor That is manipulating the buffer of text in memory However, the first bit is the trickier one and this is performing screen updates CPM terminal emulators are notoriously slow the K-Pro one is actually pretty quick Some of the machines I've got have got chronically slow terminals and you want to keep screen updates to an absolute minimum cursors does this by Maintaining a copy of what it thinks is on the screen and a copy of what the user wants to be on the screen And then it computes the minimum necessary change to update the physical screen to the logical screen And we are going to implement something similar but smaller So the K-Pro has an 80 by 24 screen. We're going to be using the K-Pro as the target for this but it's going to be hopefully portable and We want a array of bytes for the Physical screen Actually, we want to use unsigned values wherever possible. So let's just do that for an unsigned char And we have the logical screen, which is the same. All right So initialize screens to be all blank And we want to actually clear the real screen Which I have over here the K-Pro the terminal emulator Reference the K-Pro implements a extended version of the venerable ADM 3a terminal one of the simplest terminals in existence These are all the sequences it supports So clear the screen is key is control Z. So that is 26 Okay Yeah, another bit of state we want the Cursor position, which I do not need to initialize to zero. So let's try that and see what it does. Luckily CPM is really quick to boot Okay, that cleared the screen. How big is it? It is Five records one kilobyte Okay now We want to define some Functions to write to the screen So Let us put a single character. There's should be straightforward Advance the cursor if we hit the right hand bound Move to the left and increment the Why position we have no bounds checking We're going to make sure Hmm, let's just put in that check here No, let's put in a check here We're not going to bother checking the x-coordinate, but it's more likely that will run off the bottom for the y-coordinate Okay Is this what we want to do? So there are no control sequences here. It's you give it a string and it just writes it Let us Do a Yeah, if we try to print a Control code an unprintable code Then okay So if the user tries to print a control code we print instead a humanized version of it Control codes will not be going through this routine. They'll be going through something else instead. We let's do it This just does a new line instead we also want a Routine to move the cursor to a particular position remember this is all working on the logical screen This is just manipulating a buffer in memory and nothing else So this won't actually do anything. I must find a way to make mess Not display that splash screen Yeah, of course that does nothing we weren't expecting it to and we are bigger I don't think I'm going to bother looking for this right now This is annoying Okay, so then we want to actually update the physical screen so What we're going to do to start with is To just iterate through the logical screen and just put each character onto the physical screen And we're going to define some more routines to do the ADM 7a control codes if The user tries to go to zero zero Then we actually have What is this RS home cursor and earth is the value for RS? 30 okay, otherwise we actually have to omit the escape sequence to locate the cursor So that is escape equals Why? So when refreshing the screen the first thing we want to do is go to That line and we want to choose a and just We will also 20 so put some more stuff on screen right Build and run does not build Okay, that works. You can see the time it takes to do the update and we actually scroll off the bottom So we want to reserve the bottom line for a status bar because Drawing on the bottom line is a little bit problematic So we're going to avoid the issue by not actually putting text there, but this gives you an idea of How slow the text is and in fact the kpro is pretty quick compared to my other machines right So all this has done is it's just taken the logic logical screen and it's whacked it onto the physical screen So now we want to do a bit of update logic. So rather than just do this copy We're going to find the first change We're going to find the first change So this will scan the screen until it finds something that's different When we find something that's different We immediately go there and then we want to start writing text. It's not quite right Okay, here we go. So what happens is We've at this point we've already advanced physical pointers So the logical pointer and the physical pointer are pointing at the character after the one we've just noticed doesn't match So we update the physical screen copy we write out the the character and we and then we Load the character for next time while we haven't reached the end of the line and Our text is different Okay, so this is a pretty brute force system that will I hope Find string.h and help so this is pretty brute force But it should I hope work or maybe not Interesting so what this is intended to do is it finds Strings of changed characters and it just writes those changed characters. So why has that failed? It's also failed slightly oddly. Why has it just dropped the Lo bit so that's ELLO space. That's five characters. It's lost So let me double-check that I've got my go-to correct Four characters are required first two characters always s equals The next two characters to find the column row coordinates using the chart below And I hope that this is Yep, that's just simple linear So it's the oh, that's not mmm. I've got haven't quite got that right So the coordinates the ADM use are one based So but the coordinates my editor is using are zero based. So in fact, I want zero plus No, I have got that right now. Where is Square go to yeah, that's correct and I've got why first which is which is right Okay, interesting and we've lost some characters here as well.  The reason I don't like them is The the modification only happens if the expression is evaluated. So for example here P pointer is only incremented if the first part of the while is hit So let's just make this a little bit more a little bit more reliable This is probably going to do exactly the same thing hmm interesting I have to say my Gut feeling is this isn't quite right and in fact as we're just doing a brute force memory copy We can do this rather more efficiently But we're not actually going to be doing a brute force memory copy in the second stage of this There's some more optimization. I want to do so. Let's just leave it like this for the time being but why isn't it working? So what that's going to do is actually do the update in two parts and wait for a key between each one That's the first bit. That's the second bit. Why did it print the characters? Is it just red? Does the K Pro have echo? I hope not that'll be really awkward. Let's change the text to see if that makes a difference. Ah I know what's going on. I know what's causing it see that has Skipped the OO in space. So what it's doing is It's that space here that is Causing whatever is happening to happen. So it's printing the first character Then it's somehow skipping up to the space and then it starts to emit proper text after that Right. I can see something that's wrong here, which is that We start scanning We find a changed character We drop into this code and we start printing text when we find the the terminating space that is The same in the logical screen as the physical screen Then we immediately exit the loop Don't draw that advanced the next one We're also not advancing X. Yeah, I don't like this code. I'm going to reach I'm going to change it I think it needs inversion. Okay. That does seem to be working now Okay, I do not like this code We're going to do it. I think differently So rather than having two blocks of code It's actually going to be better So it's going to use a state variable to decide whether we were drawing or not so that we Combine The two pieces of code into one But actually I don't think that will help at all What we are going to do is change this to a while So at this point we actually want to What we really have here is a state machine There are two states we can be in there's Reading and drawing and there's reading and skipping And they are both fundamentally the same So I actually do think that using a state variable where is preferable What I'm a little nervous about is whether having a state variable will actually make things slower So if we are If we are not drawing If the characters are different, we need to be drawing If our state is different Then If we previously weren't drawing Then we need to position the cursor Otherwise we don't care where the cursor is This does involve doing that comparison twice, which I don't really like and it's a It's uh, it is an eight-bit comparisons. It's fairly cheap Okay, well if we're drawing then update the physical screen update the actual screen And advance our pointers. How's this going to look? Okay, that has in fact not done what I wanted at all That has also not worked at all That is in fact just redrawing the screen every time so if the like this wants to be If the character if the character we're looking at needs updating And we weren't previously drawing Then move the cursor But why is it trying to update the entire screen that looks better? That does look better There's still quite an irritating delay as it gets from here to here and I do wonder That's it looking at that that's actually thinking time as it looks at All the characters here. Do I want to do this a different way entirely? So what I was thinking of is that we can actually because these are just contiguous blocks of memory then Doing a simple comparison is actually straightforward And then once we've found the character that's different We can then figure out the screen position from that Let us check this in And give it a try So the reason I'm I'm concerned about this is that We are going to be spending the bulk of our time Doing no op uh Screen updates Due to the way the editor works So we want to make this as fast as humanly possible while The logical screen pointer Is not at the end search for the next difference So I think even the 80k is terrible code generation will be able to optimize That no, it's not right so if So this searches for the next change then if the If you reach the end then we finish their update cycle Right now l pointer and p pointer are pointing at The actual position on the screen So we wish to calculate the screen coordinates of where we are So that y is zero So this gives us the screen x and y position So we go there Now we wish to start drawing characters Until we find a difference and this will actually wrap correctly Okay, you can do that Update the physical screen No, hang on if They're the same Stop our update Otherwise Update the physical screen and move on to the next location and draw the character Is that it will that be faster? subtracting incompatible pointers, that's because Let's actually do a that's wrong Let's see what this does It's faster. It's a lot faster Because but the bulk of the work is happening in this little loop here The ack's code generation is dismal But then all c compiled to 8080 machine code is going to be dismal So this is our hot loop And it may actually be worthwhile um Trying to make this in line assembly. Well, then now I think about it Let's do this So the reason why it's so bad Is the 8080 has very limited ability to do to allocate variables off the stack frame to access variables on the stack frame So I'm making those two variables static Then we can do global lookups Just simple memory load saves And that should be faster. That doesn't work It hasn't worked because Uh by making these actual globals Then we can use direct memory accesses to get at the variables, which is significantly faster And smaller bit faster Is it worth Okay, I think this is good enough Because there's there's another optimization we're going to want to do in a moment. Okay, and Yeah, that's faster Good that'll do Right now the other optimization you want to do Is I will actually like To demonstrate Is that Clearing a line is very cheap on the adm 3a because it has a Uh, it has a clear to end of Wait a minute There's clear screen. So I looked this up earlier. See I thought that was code 17 17 etb Huh, okay. So I was about to say it has a fast clear to end of line operation So if we can track how long lines are Then this means that clearing them is very cheap We can like, uh Erase an entire line by putting the cursor beginning of the line and clearing the line but I have a suspicion that I've been looking at the k pro documentation Because the k pro implements a slightly extended version of the adm 3a Uh instruction set But I just want to implement the adm 3a instructions because they are the lowest common denominator And most cpm platforms will implement these in some way Okay So there's one last thing left to do which is to put the cursor in the right place. So let's give this a try And I think our screen refresh is probably done Whoops. All right. Well, it's drawn it and it's put the cursor in the correct position. Okay, um In terms of We also need to make sure that we're getting the height of the screen correct Right, that's what we wanted so our Text editor working space is going to be all of this but not this line here This line is going to be our status line now The reason why I don't want to extend the editor to the status line is drawing to this character here is kind of problematic because if we put a character there then It May cause the screen to scroll which we don't want to do The adm 3a does not contain line insert or deletion operations Though the kpro does So we're going to ignore those for the time being I also want to test this Because I want to make sure that wrapping at the end of lines works correctly We do this So we're going to be drawing on the status line in a Different way. Do I want to do that? I need do we need to keep the status line in memory? We don't we just need to we just need to know the length Okay, so to draw on the status line, we are actually just going to Put the cursor there draw the draw the text while We don't need to make sure that we erase what's left over so So then we want to draw spaces until we've erased the old line Assuming that the new line is shorter than the old line and then we want to put the cursor back where it was so So we do that So wrapping works correctly We have a status line here. We have text on the last line and the cursor is placed in the correct position. Good I am going to call this done Now we are going to actually Start some text editor stuff now The algorithm we're using to store text is a gap buffer So we define a fixed size buffer This is going to occupy all the leftover cpm workspace after our application is loaded The text gets loaded at the beginning of this buffer Where the insertion point is We then insert a big gap so that the text above the insertion point is aligned at the end of the buffer This gives us a place where we can easily insert new text So in order to do this We need a pointer, which is going to be the beginning of our buffer A pointer, which is going to be the end of our buffer A pointer for the beginning of the gap And a pointer for the end of the gap Buffers start and buffer end are fixed They are actually going to be Based solely on leftover RAM Gaps start and gap end are the things that will move So so what this does is it just cpm ram and cpm ram top Are defined to be let me just double check this Are defined to be the yep, here we go The beginning and end of the leftover buffer We call brook here to update the Internal pointers This updates the runtime library's internal pointers to stop anything from allocating it So once we hit this point we can no longer do any more memory allocation because our memory is full When our application starts the text buffer is empty So the gap The beginning of the gap is of course the beginning of the buffer And the end of the gap is of course the end of the buffer But we are actually going to Load a file And I'm just going to use the same That I put it There we go We're going to use the same Uh cpm command shell source code That we were assembling previously So this will actually load Hang on i'm doing this right i'm doing this wrong, okay new file initializes the Initializes the buffer to be an empty file load file Loads an existing file into the buffer. In fact, we are going to um S printf is huge sadly So this is going to be for test purposes. I'm actually going to remove it later If the printf is in general very big and deadly on small scale systems like this cpm provides us a useful General purpose buffer, which is cpm default dma So, okay Oh, yeah, and there's one other thing we want to do Which is we want to call This What this does is it tells the runtime library To extend our application workspace over the top of the command shell This gets us an extra 2k of ram So let's run this and see how much space we have left Let's need to see something. I think this has done something Bad. I'm quite sure what wonder if if Oh, oh, I know what's happened, right uh Yeah S printf is evil. I can't use s printf because it will Try to do dynamic memory allocation except that just up here we've like removed our heap So This is it's just tried to allocate memory. It's gotten null It's either produced and it's either caught this reduced an error or it's over a certain cpm workspace Right that ain't gonna work So we're actually want to do um Try and remind myself what i2a looks like i2a is a old and very non-standard Is an old and very non-standard function for here it is For turning integers into text because weirdly the c runtime library does not contain a way to do this Other than s printf and we can't use s printf here so This renders the value into text then we wish to Append some text So i'm particularly interested in this number Because this will tell us the biggest file we can load Bear in mind. There is no editor logic here yet There we go That is not a number I was expecting Also, my implementation of i2a is not right that number is also not right And okay, that's a bit of a pain. What's happened here is that buffer end We're on a 16-bit system. So buffer end is in fact negative And buffer start is positive so So subtracting a positive number from a negative number produces a negative number Okay, that's better 53k is respectable So we can afford to use up a decent chunk of that for our actual editor logic Though I still need to I need to fix the i2a logic. It shouldn't be producing leading zeros I just hacked that in this morning Okay, right loading a file My runtime library contains emulation for File descriptor based stuff. It's not very good emulation But it does work So we attempt to open the file CPM doesn't do byte access for files. You can only read or write complete 128 byte chunks This makes its text files a little bit interesting So not only does it traditionally use crlf line endings But there's a special control code that goes to the end of the text file that tells you where the end of the file is because uh, otherwise you know If you have 10 bytes of text in a file Where you only know the size By number of 128 byte records. What are you going to do with the other 118 bytes? So what we're actually going to be doing is Reading text Into a temporary buffer and we're going to use cpm default dma for that and then we translate the text and read it into our editor buffer and in fact Because we're just going to be inserting it into the gap That means we get file insertion for free Okay repeatedly Let us read our file. So keep reading single records one at a time until We reach the end of the file Once we've read each record We now want to iterate Through it and we see keep reading One byte at a time But we want we want to translate crlf sequences into new line sequences because we want each of our lines in our buffer to be terminated by a single byte So if we get a carriage return at the end of a record we need to know that we are planning on skipping the Next one If we are skipping Otherwise you read a character if the character is An end of file stop reading right now If it is a Carriage I'm doing this a complicated way. We don't want to bother with skipping So we're going to do the line conversion In a really simple fashion, which is that we just drop all carriage return Characters thus leaving only new line characters If the file contains bare carriage returns it won't work That is the lines will just be merged together If it turns out this is an issue we can deal with it later But bare carriage return files are exceptionally rare. There's only a few systems that use them The only one I know of the only two I know of are early apple max and the bbc micro Everything else uses either unix convention new lines or dos and cpm convention carriage return line feeds So if the character is not a carriage return then write it to the Then insert it into the gap We probably wish to check That we've run out of memory So we read as much as we can and we stop Okay, I believe this will load a file. Let's try it and see At some point in the future. I may Uh, not stop using Yeah, my runtime library doesn't have sysstat Okay, at some point in the future I may want to move away from using open and use the raw cpm calls, but there are advantages to using open So we've lost some we've lost Quite a lot of ram that really 3k That seems not right. I bet. Oh, right. Uh, yeah, I've just pulled in the file descriptor code Which is quite big That's why we've lost a couple of k of ram That should be the last big library thing that we Uh, we will need but this is why I'm wondering about just switching to the raw cpm calls But we can deal with that later if I want to make it smaller So this has inserted the text into the gap. We now want to render Our text Now there is There are a few more bits of state we need We need A pointer to the beginning of the top line that we're going to display on the screen Because we render text by starting at the top line And just outputting lines until we hit the bottom of the screen We need A pointer to the current line That is the line the cursor is in. We're talking physical Uh, we're talking lines of source text not lines on the screen But we also want The uh, I need to think of names for this We need the position of the Current line on the screen To call that current line y Every time we modify the current line We are going to Rerender the entire line This is why we wanted our Screen refresh code to be efficient so With an empty file the first line is obviously The beginning of the buffer The current line is obviously The first line and the current line y position is obviously zero So So we now try to render the entire screen This is in fact going to re-render the entire screen So the first thing we do is There's one of these helper functions that we forgot to do Which is clear and that just like wipes the screen And let's actually put So when we render the screen we clear it now Starting at the top how do you do this? Starting at the top we're going to draw text onto our logical screen so while While we haven't run out of screen space If our input position Is the beginning of the gap Skip over to the end of the gap If our input position Is the end of the buffer then Draw an end of file marker And stop otherwise Read one character from the from the The text if it is a tab We also want to track how far in the current line we are Actually, let's ignore tabs for now They're they're easy to implement, but let's just not do it If it is a new line This is the end of a line then move on to the new next line and Reset our x-off set otherwise print it Okay, let's see what this does Hmm I have a feeling that failed to load my text file Yes, it did in fact fail to load the text file because I forgot to put my ccp file into the disc image So I can do that like this and there is my ccp Okay, let's try this snag try this again. Okay, it's loading the text and it rendered it So the tabs have come out as control eyes, which is just what we wanted It filled one tech one screen full of stuff Uh, it didn't overwrite the status line I believe that has done just what I wanted Okay, um We want tabs the way we do tabs is While we just insert spaces until the x offset Is a nice round number We want to insert at least one space always Yeah, that should do it Now there is some other a few other things we want to do with this When we hit a new line We do also want to check if the input pointer If we are looking at the current line that the cursor is on Record The y position of it Now bear in mind the in p here is before we do any gap adjustment So if you're looking at an empty line of text then uh First line is pointing at gap start And the character at gap end is the new line terminator This is what we want so that we can insert text by simply adding it to the beginning of the gap Now, I don't know how fast render screen is. I suspect not very So we probably don't want to call it every iteration around the loop But let's just do that for now for simplicity So we're actually this beginning our control loop So we read a We read a character from the keyboard and now we operate on it actually Let's just test this first So you stick a break in so that uh, we can terminate the program It doesn't oh should be current line white now I hope I also need to look to see if mess has a speed up key because I'm going to be spending a lot of time waiting for this thing to load Okay, so it's expanded tabs, but I'm not sure it's done it right No, it hasn't done it right. This is what it's supposed to look like Because these are not lining up okay And also the cursor is in the wrong place. It's was It's down here It should be Okay, we haven't set the cursor. That's why oh, yeah, I've forgotten about this Yep We also need a A pointer to the byte in the line where the cursor is This is not the same as the screen cursor. This is the editor cursor This wants to be Here So if we are looking if we are actually right now looking at the cursor then So obviously the cursor must be in the current line That's a invariant. We mustn't violate Now we also want to set the screen cursor We do the only way we know where the screen cursor is is by rendering the line and noticing that we are looking at where the Editor cursor is like this and after we've finished Refreshing we actually want to go to the screen x and screen y position. Oh, yeah, we're going to look at that tab code Uh, so printer space Advanced ah That's what's wrong. Right. Let's try that So we load our text right our tabs are lined up and our cursor is in the correct place. Good It almost looks like a text editor Okay Check in So it is worth remembering that the insertion point, which is where the gap is Is not necessarily the same as the cursor We can insert text We can only insert text like modify things at the insertion point However, the cursor can go anywhere. So just moving the editor cursor around Won't actually modify the Uh insertion point Now before I go mad, I'm actually going to do some renaming So I'm going to change cursor x to screen x throughout cursor y to screen y throughout And I'm going to make cursor x and cursor y New variables that refer to the location of the editor cursor on the screen So down in render screen, we change this to so x So y Then these two lines Okay That will make things a little easier to understand Right. We are now at the point where we want to actually Read Characters from the keyboard and I've just remembered why It's echoing the characters and that's because we're actually going through the cpm con in entry point Which is defined to do this Uh, sorry, this is the this is the this is the bios So I was thinking in terms of the bios the bios con in call just Reads a character and returns it. It doesn't print it, but we're using the bedoss Entry point with bedoss functions We are using This one Wait for character echo it and return it what we actually want is raw console input I'm not sure that Yeah, I actually think that There isn't a stock cpm way to do this And we're actually going to need to call the bios well that Is annoying Yeah, I'd forgotten that was an issue and if we're going to be using the bios for Reading characters then We probably also want to use it for writing characters Yeah, we're on cpm 2.2. So we don't support this Yeah, a lot of these are the the advanced Uh Later version to cpm which which cpm is not Where you can have like multiple consoles and multi user systems and things like that We are just working on the very simple cpm 2.2 because that's the interesting one Okay Right We need to call bios functions That's a little bit trickier than calling bedos functions because we don't have entry points So let us create a couple of variables that We want con in con out and const const state console status Are we going to need to do this as Yeah, we're going to have to write actual machine code for this Blast Okay, uh I'm going to have to modify my libc to do this I wonder can Is there something else I can do I could connect the reader to the Console because I can read from the reader without Um Without echoing Yeah, I can do that. I can do that. Let me just double check the So what I am thinking about here Is it's possible that cpm defines several input and output devices. We have the console Which are these functions here? We have the lister which is traditionally the printer We have the Uh The punch and the reader these are These are originally intended to be connected to a card punch And a paper tape reader, but these days what they actually mean Well, I say these days. I mean days after about 1977 What they actually refer to is the serial device or just auxiliary input and output And it is possible to use a feature called the iobytes which I can't find and again, I am looking at the wrong piece of documentation A feature called the iobytes To redirect them to something else I believe I'm getting my bedoss and my I'm even getting my bedoss and my bios mixed up again. Yeah, okay. There are bedoss calls for the auxiliary Input and output But there is no auxiliary status So the iobytes is a simple 8-bit bit field that maps the logical devices So you've got the list device punch reader and console to physical devices Which is either the terminal The actual screen a few other things so we could A tty is a serial port Nope, uh, it actually Okay, we can't map punch and reader to the console I'm going to have to do this the proper way All right So we need to take some time off to go edit the Uh To edit the amsterdam compiler kits um Lipsy because I need to edit my cpm runtime So here we have the header file containing all the cpm entry points And we're going to need to add a few things for the bios entry points And i'm just going to do const con in con out for now. That was my screen editor a screen capture This one so So you've got three functions. She's con in const and con out The tricky bit here is that the bios is got a rather different calling convention So where the bdos there is a single function you call you pass in the Uh The function number to tell it what you want to do with the bios. There's actually a jump table and You get at the jump table via A vector in low memory that actually points at this entry here So what we need to do is to get this pointer Then add a value to it to point to the different table And then work from there. That's actually not difficult, but It's fiddly so we want to create some This is like unrelated to the editor stuff so load the address of the The warm boot vector which is pointing at this jump here This is con in so we then want to add six to it become a six Add the two together We call cpm Which i'm going to be looking at this is con in We call cppm and it returns the value in it a but we actually want it in hl for because that's the calling convention of our Um Of our c compiler. So we now want to call The function pointer to in hl and there's a Standard way to do that You can remember what it's called l Yeah call dot pch l This this calls the function returned to by hl and returns So it's then Move Okay Move the return value which is an a into l clear h And we are good The console status Routine is exactly the same Except we call a different routine We want to call this one which is offset plus three The one that's different is con out now The reason why it's different is the parameter that we actually want to send to The bios is on the stack and there is a Standard way to do that Because there is no stack frame A relative addressing modes You either have to fetch the stack pointer add a value to it and de-reference it Or you do this Which is to be pop the return address of the stack then we pop the actual parameter I really want the parameter to go Con out writes the character in c to the screen. So We want it to go in to Yeah, I just realized I've actually got this quite these two routines quite wrong Not too badly wrong The stack frame is in register b and will be corrupted by the call here. So we need to save it So that pops out the parameter that we want the thing that we actually want to write To the console into de We then Need to get this into bc However, because we need to put the the value in c however bc contains our stack frame also, I Forgot the second part of this So you pop the two parameters off the stack But we actually want to keep them on the stack because the routine that called this routine is going to expect it so Each of these instructions is four bytes and they're quite fast If we wanted to do it the other way that is via sp We would need to Um, let me see So this is the offset into the stack frame we want There's a routine that uh, oh, yeah, it would be load that into hl add on the stack frame Then it's h I think Which loads the a register with the value Point to two by the hl register pair. So that's three bytes one byte one byte So it's cheaper to do just to pop the values off the stack and put them back on again Okay So now we want to push our stack frame because we're about to overwrite bc We move We move the parameter which is in which we put in de here into c now We compute the address of con out, which is our vector plus nine call pchl Pop our stack frame back again Into b and we should be ready to go let's build that meanwhile Let's go to our editor while that builds you want to replace all calls to cpm con out to bios con out And we want to replace this call to cpm con in to bios con in Okay Our compiler built so let's go back to here And let's see if this works No, it doesn't Okay, let's see if this works Well, we've successfully written to the screen. You know that and it loads our text like let's press a key Fantastic that has worked first time. Good. We're now calling the bios to actually do work Uh, okay, so we have Where we were Actually short hydration break Okay, rehydrated and I've also figured out the mess speed up key At least hope I figured out the mess speed up key Which should allow us to like test more quickly Right, but we've now reached the point where we want to actually start, you know doing things so I'm actually going to take the opportunity to Put in a few comments. So let's just split things up slightly just divide things up in a few banners And insert file doesn't really belong here. So let's push this down a bit So far belongs here And here we put now these are going to be the This string will contain the sequence of characters that our editor will understand. So the first one we want is l Which is going to simply like It's a vi it moves the it moves the cursor and here we have a array of Callbacks that actually do that work and I cannot remember the C Array of function pointer syntax because no one can remember it. So I'll just copy this from here Uh, yeah, I'm using the same logic that ants editor used For this because it's, you know, nice and simple and here is the The team that actually makes it happen now So you notice that as I move the cursor around in this actual vim You can't put can't move the cursor right beyond the last character So the way we do that is We want to look at the Yeah, that's a little bit odd To be honest, I've never really understood why vi never let you Put the cursor in the space to the right of the line But anyway, let's just replicate this. We wish to move the cursor one space to the right I mean the obvious thing to do is just to Move the cursor, but remember the gaps in the way. So if We are looking at The gap if the cursor is at the beginning of the gap Then we want to look at the curse the character to the right The cursor nominally goes Before the character that we will insert before So that here we'll be inserting before the parentheses So we actually want to look at the character immediately to the right So if the cursor is Looking at the end of the buffer then there is nowhere to go So do nothing If the character to the right of the cursor is a new line Do nothing otherwise Move the cursor and render the screen Now this is pretty bad because we've actually just moved the Cursor to the end of the gap which we didn't really want to do And in fact, we don't really care about the About the gap itself So all we really want to do is fetch the Character to the right of the cursor now. How did aunt said it to do it? Ah, it it operates by indices also It does allow wrapping So moving the cursor to the right here will actually move you to the beginning of the It'll move you on to the new line actually You know operating with indices is actually a much better idea than operating with pointers And okay, no, we're going to stick with pointers. I think that we're going to do things a bit differently So we wish to move the gap So that the gap is at the cursor What this means Is that Uh We will update the cursor position We will we will we will move the gap so the gap is immediately to the left of the thing being pointed at This means that Uh, the new gap end Will be the thing that used to be pointed to by gap here That means the cursor will now after this after the move gap returns be gap end So we don't need to worry about this at all And remember that we can move the gap as much as we like and it will not actually, you know Change anything semantically and in fact, we only need to move the gap if the We only need to move the gap if we are going to be looking at No, no, let's always do that So we need to Nope, no and editor is right. We're going to use indices So, uh, given an index we want to return a pointer to it Hmm I am she's still not not entirely sure i'm doing this right Using pointers here is actually really advantageous because it allows us to jump directly to the location in memory But it does require us to update the pointers when we move things around So it'd be safer to use indices here So that as we move if we use pointers here, then as we move the gap We are required to update all these things if the gap moves from after First line to before first line and of course the pointer will change But we also do not want to keep converting from indices to Um Pointers and back again a lot So let's try So this should convert from an index for pointer and back Now These are now indices But these are all go to zero We wish to detect if we're looking at the cursor Will that build cleanly? So yes, if all we do is do if cursor Is not pointing at the Index of the end of the buffer Advance the cursor And render the screen I may be as simple as that So let's actually try running stuff now The way we're going to do things here is we received a character from the user We look it up in this string using Stir in stir Kerr there's one of the standard string functions Indeck, no, we don't have index I'm a well in doubt plagiarize Ah, it's interesting Anteditor is in fact not doing It's not using stature at all uh, okay So stature looks up a character in a string And returns a pointer to it If it is a recognized command Then call the callback Done and that doesn't work because There's knr It worked better You see your illegal code Oh, that right No Cont syntax is kind of bizarre and see I think that's better there. That's working three one three Um Compatible pointers in call Because this is a CPM default DMA is an array of bytes, but these functions want chars Yep three one five Yeah, same thing here three two seven incompatible pointers That should be all right Oh C is uh There we go Okay, let's see what happens when I actually run it The speed up key does not seem to be working. Maybe it's not enabled Okay, now press we press l and it exits right it exits because I forgot to take the breakout. Yeah Yeah, I think I think the speed up key is speeding up execution, but it's not speeding up the disk never mind Right now we press l And nothing's happening Well, that could be because my pointer and pos functions here are wrong So if the pointer is to the left of the gap Then the offset is obviously Between the pointer and the beginning of the buffer if it's after the gap Then Yeah, that looks right Well, we need to find out whether It's actually calling my cursor right routine. So this is the easiest way to do that I'm listening to disk noises So we press l right Okay, I know what's happening. It's because caps lock is on And it's registering a uppercase l. So let's try Turning caps lock off intriguing Let's check to see whether the caps lock key works so That's capitals that doesn't work Okay, now we are in lowercase. Good. Let us run the program all loading Right, this means that something is wrong with this code down here Maybe we are returning the wrong value I just can't out see Lots of loading times Right Yeah, okay That's why it's not responding to the characters. It's receiving complete garbage That's a problem with my Con in routine here So con in wait still keyboard is ready to provide a character and returns it in a And I believe I want to put my result in Uh h l Maybe it goes in de I think it goes in de Yeah, it goes in de. Oops Let's try that and I'll need to modify const as well I think it'll work. It did not rebuild the Editor that's better Maybe I will use a smaller text file Just to make the testing faster. Okay Right, we're now receiving Yeah, the long sequences are due to k pro key up issues And we're now actually receiving the correct Uh characters Let's make that a bit shorter Also, we do need to remember to put a control z and a file character at the end of the cpm text file Let's try this So we load the editor Now we press l and we exit good The reason we exited is because I put their cpm exit here. So we're now calling the appropriate routine l Okay, that is actually working However, there is about a second delay between me pressing the key and it advancing the Cursor this is because it's going through and it's re-rending re-rendering everything on the screen And as our text file and it goes down to here, that's actually quite bad So there was a plan The reason why we had first line is so that we can render a single line at a time So is that going to work? So as we render we actually want to So I would rather not render one line at a time Let's try our old trick and just stick some statics in here and here And see what difference this makes The reason i'm testing on a k pro 2 is it's got like a two and a half megahertz at 80 And it's like the slowest thing you're likely to find It's not a lot better to be honest Yeah, that's terrible. What can we do to speed this up? It's not actually doing a lot of work The main thing is to just render the line the cursor is on. That's why we had first line in place This will work fine, but it will mean that moving to the next line will be rather slow Because we can render within one line Quickly, but moving to the next will not be quick And of course we are doing the full screen refresh One thing we can do is to hint to the system that we know that only parts of the screen will be Made dirty therefore we don't need to bother actually refreshing Other parts of it that might be worth considering Because we know when we move the cursor that you know, it's the old cursor position and the new cursor position What can we improve this? Now one thing we can Do Is we keep Here we're doing a couple of complex multiplications in order to Uh locate the position on the screen But we don't actually need to do that every time So this will save a complex multiplication in the hot path Because put C is called a lot So that should make rendering faster Hmm right screen pointer has not been initialized and it's just scribbled over Yeah, okay The screen pointer has not been initialized and it's just scribbled over Uh the bottom of memory Our screen is 2k I believe And our program starts at Starts 256 bytes in And you know go and zero page is there's got lots of system variables. So that won't work Okay, let's try It's not faster. That's disappointing All right, we're obviously going to have to avoid Rendering the screen Though i'm actually just going to Make a slight Just want to check something So this is going I just want to see How long a minimal refresh is because I thought we'd tried that before and it was okay I just want to double check Every time press l Print say exclamation mark Oh, yeah, it's So it's calling render screen here. So I'm actually just going to do Just stop this out Here Oh, I think we were calling render screen twice actually Yes, we were we were calling render screen twice So this will actually help a lot by only calling it once Like But let's just check this anyway So every time we press a key you will print a cue and do a screen refresh Well, it's supposed to print a cue or Yeah, yeah It can't keep up with my typing That's too slow cursors I really hope I can do this and see Maybe it's just not fast enough and I'll have to do the At least the screen refresh code in machine code I really hope not Because 8080 machine code is kind of grim We've got a division here. We've got two divisions here. That's not going to be fun Can we Improve things there. Anyway, let's try Like this I think it is faster, but still very slow Yeah, I have a nasty nasty feeling That we may not be able to do this in C This way So we can We can hint to the system as to which bits of the screen actually need refreshing That's this part But I don't really want to go there Refreshing the screen is Like so core that I want to make it as simple as possible. I just want to refresh everything in one go And rely on the refreshing code to be cheap You've got some variables here that might be profitably made static Because trying to do basic work in 8080 machine code is awful Like 16 bit arithmetic is just grim If we use sdcc, but it's rather better code generation. We may Make it work, but I'd rather not Yeah, that's still too slow So the other thing is to simply rethink my lifestyle choices entirely Rather than having The logical and physical Screen refresh system We could just draw directly onto the screen So we move the cursor We actually compute that we need to move the Screen cursor from this position to this position and just do it This also means however that things like Drawing text is just going to be bad For example, if we want to Insert a line Well, if we want to insert a line, we have to redraw everything from the cursor down anyway So that's not actually a ludicrous idea So what we would do is our render screen routine here would actually just draw directly onto the screen It would mean that drawing a line would replace what was already there And we would just have to you know brute force text out to the terminal So that in order to be efficient when doing things like Small updates we would have to be a lot clever in our logic For example, if we insert text into the current line And it doesn't disturb the line following We would need to notice this and not Like disturb it, but we only really care about the line the cursor is on And Our modifications will consist of when we when we modify the line We are only going to change these stuff to the right of the current line So we really have three types of update then We have the update when we move the cursor and don't do anything else. There's a lot of those and they need to be quick There are the updates where we modify the current line, but no other lines And remember that lines may wrap If we insert into the current line, we have no choice but to redraw everything to the right of the line And the third update is when we Modify the current line in such a way that there are cascade changes below the current line for example if the line is 80 characters long and we insert another character into it the line will wrap Which means that we have to redraw everything below that on the screen. There is no other option With this terminal doesn't have any line insert or delete codes So when moving the cursor What we would need to do Is to compute the new position on the line And then adjust the screen cursor Computing the new position on the line basically means running through this rendering logic But without actually drawing anything The downside is it will mean throwing away nearly all my code but Well, there is no but Let us check in and let's start throwing away my code Also, I don't like UC style. I think it's ugly So as we're going to be throwing everything away, let's actually just switch to Though it does actually occur to me to wonder whether the Whether I got round to setting the 8080 code generator to do unsigned charts I can't remember Okay Well screen Drawing Plus side this will probably make the code a fair bit smaller We do want to keep track of where the cursor is. I believe So Okay, we have no refresh logic anymore We do still have status line logic But we're going to do this lightly differently Because we don't want to upset the Um We don't want to upset the actual stored cursor position So y is going to always be height plus space This is going to be zero plus space Okay, buffer management doesn't change render screen will now there's What we want to do I believe Is that there's two primitives that we want here one is that We start We give it a pointer. We give it a pair of pointers and It will walk from the first pointer to the second pointer and compute The the length on the screen of that text and we will use this to determine cursor positions Uh cursor positions actually Like we move the offset in a line We want to know where the new cursor is going to be so we use this to compute The x and y position so that's going to be We actually that means we use 16 A 16 bit type because our lines can be very long and this gets This is going to need to be quick because we're going to do this in every cursor movement Our second primitive Is going to work very similarly Except that rather than computing the length It will actually draw the line onto the screen And it will need to return The position of the next line So But this will actually draw a complete line We may want to have it draw fragments, but that's going to be difficult And we are not We're going to assume the cursor is positioned correctly beforehand So this is just going to draw stuff on draw a line onto the screen until it reaches the end of the line And return a pointer to the next line With the cursor appropriately located Okay, that's not so difficult so as before as before we just give up if you if we run off the bottom of the uh Of the bottom of the screen as before Let's just actually copy all this code because most of this will be unmodified But we don't care about the cursor position because that's going to be handled with compute length here And we're actually going to be oh, you also don't care about this if we And we're only doing a single line at a time. So That goes here When we receive a new line, then we print a new line to go to the next line and break We use xo here for tabs. Okay, so Our render screen function Oh So because we don't have a clear to end of end of line or clear to end of screen command The only way we have to clear lines is to print spaces Which means that when we reach the end of line here rather than just printing a new line and terminating We really just we really want to Print spaces until we hit the new line. That's going to be slow But I don't think we can get away without it Is not to it So what we're going to do is our This is going to be the heavyweight render function That's going to render lots of stuff And we want to tell it where we want to start it from And it's going to start drawing at the current line And it's just going to keep going down the screen till it reaches the end So this is actually pretty straightforward. All it is is Draw one line keep going Just keep drawing lines to be around the screen however If we reach the end If we reach the end then we want to draw both the EOF stop drawing and start clearing to the end of the end of the screen so So let's have a I'm liking this less and less Be honest. Yeah, that's actually really simple So we don't have a con in it anymore, but we do have a con clear We don't have a render screen anymore, but We do have a render screen that works differently So con go to 00 and render screen from first line onwards We don't actually have cursor x and cursor y anymore Or a current line y anymore. Just line length. It does still exist Actually, I habitually make all these things you intate But I have a bit of a feeling and I will actually check this in a moment That this doesn't help Code size The ack tries very hard to do all arithmetic in words And I have a feeling that using uint8 everywhere just causes it to do lots of casting between Bytes and words See how this goes Not quite. What did I expected? see Reasonably sure bios con out is right Uh, this has okay. I know what's happened here This has completely failed to Uh read the file correctly So So you see we have carriage return characters Here is our end of file and then we have the garbage that appears Uh After the end of file character and the rest of the record Why do I have garbage? That's weird. Um Wait a minute. Where is our end of line character? Just to be true. Okay. That is that is right Yeah, we have 1a Oh, right What's happened? I know what's happened The reason why we're seeing the unprocessed control codes Is because this hasn't set up the buffers It hasn't set up first line correctly Which means the first line is defaulting to zero Which means it's reading zero page and what we're seeing Yeah, first slide zero. These are still These think they are Offset They are offsets This has Yes, because it's it's read the buffer that we read the file into Which starts at address 0080 which is 128 bytes in better so it has Drawn our text it's properly indented. It's then cleared this part of the screen and That bit hasn't worked right So clear to eos has actually Do we have a I think it has written some text To the screen Without updating the screen x position. Where are we? Yes, this is all okay so far This is status line. So it's expected not to change the Uh Okay, that's actually it looks like it's all right. So why has Clearing not work properly The cursor is in the wrong place Okay, so we are going to need To set current line and friends from inside Uh probably Render screen so It's because this tells us where we can start drawing when we want to do a partial redraw I still want to know why that did not Clear the screen properly What I believe it's done here is that it's just printed Lines of 80 characters but starting from here Because that Causes the wrap around overwriting the status bar. It could just be coincidence or something else has gone wrong interesting Don't know why that made a difference What does con new line do it just Print the return No, I don't know why that made a difference weird Okay, anyway, we are drawing we are entering the screen, but we also want to Position the cursor we have to have rendered the screen at least once We want to position the cursor on each line On each command So now that we have um Now that we have set our Current line and current line y We do So this will tell us the length of the current line We actually would just want the lengths from the current line to the cursor position This then allows us to go to Okay, now we need to compute the length of the line And this will involve a simplified version of the draw line code Because we don't actually need to draw anything which is nice So We don't need to worry about the end of the buffer because we're going to rely on stuff elsewhere Not allowing the cursor to be beyond the end of the buffer We Don't need to worry about new lines for the same reason This will require our cursor right routine to know about new lines We don't want to move the If you move the cursor across a new line Then we need to update the first line and Cursor y code That's going to be a little bit interesting. I think Whenever we hit a tab We want to round up x o But we always want to insert at least one space If it's a control code insert two positions Otherwise It's one Well, let's put the cursor in the right place We do not have any Controls yet. So if we got a command Then and this just increments the cursor position So we press l Nothing happens Oh Okay It's not behaving the way I expected That'll be my tab code is wrong So that's supposed to uh, that's supposed to round up I have done that wrong I think I want that to be nine Yes, so that should round up to the next Guaranteed next tab stop. All right l wants Yep, now put it in the right place And it can keep up with the repeat key, which is nice When we go over the new line, of course, it then goes All skew if so A cursor right routine so if If we are not at the end of the buffer and The character to the right of the cursor Is not a new line Move the cursor So this should put us to the last quote and no further Yes, good. That's a left If the cursor is not at the beginning of the buffer and The character to the left of the cursor is not A new line Move left Good That seems to be working Okay, now the interesting bit is Moving down So what does down do in vim? Uh, what does down do in vim? It moves you To the same offset On the line below so record the offset That we're currently at find Find the new line I'll do this Okay, we know we're not at a new line because left and right above didn't let us We might be pointing Wait a minute. Wait a minute. We can't point at the end of the buffer normally No, no, that's that stupid. We have to be able to point at the end of the buffer because of empty files So while We're not at the end of the buffer then uh And Okay, seek forwards until we see a new line We are now At this point we are now On the new line After the new line And then keep cursoring right until we Reach the offset Now The tricky bit is that we have now moved to a new Line So we both need to update first line But we also need to update We need to update our current line y because we're on a new line Now I Don't really want to do it here Because I want this to be generic so We actually keep track what we do this So if the current line we're on has changed Then we need to compute the new current line y position And the way we're going to do this is we start at the beginning of the the top of the page And we work down until we find the new current line Which may be above or below the old one so Actually, I'm going to Put this out of bound put this out of line. I think because this is This is actually pretty similar to render screen So this is actually doing exactly the same logic that render screen above did But it's not actually going to draw anything We don't know where the next line is so This is actually a bit gruesome Uh, so we need to in order to calculate the length of the line I need to scan forward to find the end of the line Our compute length here doesn't have a way to return the position Of the next line even though we can actually trivially figure it out by testing for a new line here Uh If we hit a new line Stop if the user provides next p Okay, that should make things a bit more straightforward so compute length takes input pointer End of pointer, which is like the end And where to put the length and advance that by the correct number of lines Now this doesn't take into account Scrolling which we're going to have to do tackle next But let's give this a try Okay j Yes, but not yes And I think it's kind of wedged Brilliant POS buffer start is always zero POS buffer end is a constant term and we could probably factor that out, but I won't worry about that for now uh So this will always return In p will be the line will be the character after the new line Which is like what we want and the new line itself has no width, which is what we want We could also put in an optimization here so that Uh, when you're moving the cursor down, we don't start scanning from the top of the page That will improve The common case a fair bit Though it occurs to me the other thing we could do Is to keep a table of the lengths of the lines on the screen Thus avoiding needing the computation at all Interesting, let's deal with that later Oh, yeah, but there was a thing I was going to do So current line y is used in a few places and it's a uint 8 So let us see how big our binary is six eight eight three bytes Six eight six five bytes. Yeah, okay, uint eights are not worth it But that's gone up interesting six eight six five Six eight five two smaller Six eight three seven a lot smaller interesting Sometimes it makes sometimes it makes the code smaller Uh, sometimes it does not Well, the main rule is between the behind this sort of thing is pick whatever makes the code clearer Rather than whichever is faster. The main thing I care about is these all need to be unsigned Because Magnitude comparisons for the four signed values are really expensive. Okay. Anyway, let's try. Let's take a look at this screen position stuff I am going to for debug purposes Steel I'm not going to steal. I'm actually going to write my own to be honest I need some debug routines And at some point we're going to want to print This is actually We are going to want to print numbers Okay, we press j That's interesting It did not call recompute screen position at all. Just double check my hj cursor down Uh I don't want to change first line. I want to change current line That's why So many edge cases. So so many edge cases down Right, it got the length of one line And then terminated. So I think that may have done the right thing I believe I mentioned before I'm testing on this k pro 2 because it's the slowest and creakiest emulator I can find So if my editor works on this it'll probably work on other things down I Think that's correct. I'm a little unconvinced by the delays But let's take out our test code and try it for reals This is Not even slightly right intriguing That's because this needs a plus one on it Because if the length is Does it need a plus one or do I want to round up? I think I want to round up because an 80 line long an 80 character long line Does an 80 character long line occupy one or two lines of screen? I think two So We do that Down and this needs to be current line y And this is just wrong Yes, if we arrive at the Line we're currently on Just stop now. Oh, it went down. I don't know why it's moving to the end of the line Can we move left? Yeah, so you do have cursor movement If we go down from here, nothing should happen No, it's in fact Goes horribly wrong. Yeah Cursor down which is here should actually check If it's at the end of the buffer Well, ideally it shouldn't do anything but given that we've already moved the cursor If we find we're at the end of the buffer then just Stop No, that's not going to help because cursor right knows it's at the end of the buffer and will refuse to advance So we're actually on the beginning of the next line or the end of the buffer Think to what we want here If we hit the end of the buffer just stop so now the cursor is pointing at the end of the buffer and Yeah, that should be fine. Right cursor downing is actually not the fastest So I think we're going to need to use our table of line lengths Oh, well, that shouldn't be too difficult. Let me just add cursor up This actually explains some of the oddities Now if we are at if we're at the end of the line there If we're at the end of the buffer, then of course we can't look at the next character So yeah, we are going to have to do this test. So while the cursor Is not at the beginning of the buffer and We are So Interesting Keep going backwards Until we are looking at a new line Until we're looking at the character before the new line No Move to the beginning of the current line now pre decrement so Keep going backwards until we see A new line that is beginning of the line before So if the document is empty then current line Buffer start and buffer end will all be the same So Let's try that So hopefully this should allow us to navigate around our document on a single page Down Looks promising up Not promising. Can we move? No, that seems sort of wedged Let's see if we can Move a bit more easily Let's see if we can move left and right down Right up that works Down Okay, let's see if the offsetting works. It's good Up has not worked at all That's in fact just moving left one, which is not right at all. So this produces the offset Skip back to the beginning of the current line. That's the character after the new line Keep working left. This is after the new line So Give up with for at the beginning of the buffer Back up so that we're looking at the new line Keep backing up until we hit the new line previously I am not keen on any of this logic. I wonder whether it might be worth allowing the cursor to be on new lines So down Yeah, that hasn't worked Yeah Because that would probably simplify a lot of the edge cases It also means that we can just keep going left and right I mean, it won't be standard, but it's not this isn't going to be standard. Anyway, it does mean that We will need to be aware When we cross line boundaries Okay, I need to take a break. I will think about that and come back again all right Dinner has been had I have thought about what I'm doing here I think I need to simplify considerably So ants editor which has basing this on Has the gap buffer And then it has the cursor position and the cursor position is not necessarily at the gap And I think that this is making things too complicated It's leading to complicated logic like this which is slow Because you know, this is an 8080 at two and a half mega hertz. It's it's you have no idea how slow this machine is But more importantly, it's leading to more complex invariance that I am not able to correctly maintain So what I'm going to do instead is decree that the cursor is always at the gap So this means that we no longer need a separate cursor position It also means That first line Is by definition Above the gap that is Let me think of directions Towards the beginning of the document that can actually just be a pointer because we know that This will always be true Likewise current line Is always like that So this should make a lot of things simpler so This means that as we move like character by character to the document We have to move bytes from one side of the gap to the other. I also believe that we need to Keep track of The lengths of the lines on the screen so that we can efficiently move from one to the next In particular, we need to be able to get the y position of the line Above the one we're currently at However, I could be wrong about that because we may be able to do Compute length here is pretty cheap So it may be possible to just work backwards from the current line Find out the previous line Compute the length of that and then use that to adjust the y position So I'm going to give it a try without Anyway, we no longer have pointer or pause We no longer have cursor These are all pointers Old current line. I don't know whether we'll actually need Let's try and make this work without it So compute length We give it a start pointer and an end pointer Now this one will remain untouched because we may need to we probably want to compute the length of a line that spans the gap Like the line that the cursor is currently in will span the gap. So we leave that Untouched likewise draw line does the same thing Render screen is going to change because render screen is going to start at a Offset It's going to start at a position which will be Above the gap. So this will be above the cursor or at the cursor so We start from the current y position and we keep rendering down If we find ourselves in the current line, then we know what the current line y position is We draw the line we keep going until we hit the end of the buffer Recompute screen position Now this will start at the first line and it will work down Rendering lines until We hit the current line So this this will recompute current line y based on first line and the Yep, okay So These will change because we now have no cursor So cursor left the if if we are Not at the beginning of the buffer and The character to the left of the gap where the cursor is is not a new line Then move one character From the start to the end Now these pointers all point at the beginning of the blocking question. So Uh, it's a pre decrement in both cases if We are not at the end of the buffer and Let's do it like this for consistency And the character to the right of the cursor is not a new line then Move a character from the end of the gap to the start now. This is not right because The cursor is not allowed to be Uh On a new line which this represents So in fact The new line we want to look for is there. We do not want to allow the cursor to move any further in this situation the The only situation where the cursor will actually be pointing at where the gap Where the character after the gap will be a new line is when we are appending to an existing line Which happens as a separate mode that we haven't implemented yet so uh So that is More correct. We want to ensure that there are at least two mm so if the If we're on the last line of the document and there is no trailing new line Then this will prevent us from um Approaching the end Also, that is wrong We'll leave it like that for the time being Okay, cursor down we wish to keep moving characters from One side of the gap to the other that is we want to pan down the document until We reach a new line so While there are still characters to the right of the cursor and The character to the right of the cursor is a new line then Okay, so the character to the right of the gap is either The end of the buffer or a new line. Yes, we wish to treat the end of the buffer as if it's a new line uh, however Would it be easier to make sure that the end of the buffer is always a new line? I think it would be so that we guarantee the buffer always terminates in a In a new line Yeah, let us do that so if the last character And I don't need to do this at all if we have read some data and The last character in the buffer is not a new line then Write out a new line. Okay, so this means that we cannot cursor right past the so this ensures We want to have The last byte in the buffer Being the new line Yes, so the gap cannot advance past that This means that buffer end is dereferenceable This will make life a lot simpler because now I can say Because now that is valid Because if this this condition is true Then gap end must be less than buffer end Which means that gap end zero is dereferenceable and gap end one is Either part of the main buffer or it's a terminating new line Oh, we also get to use a not equals rather than a A magnitude operator Because those are gruesome So for now with this We keep moving until the character at the end of immediately after the end of the buffer is A new line If we have in fact hit the end of the buffer Then we really want to back up one But in a zero length Line We still have to cope with zero length lines I'm trying to maintain this the the normal vi behavior where you can't put the cursor on the new line after a carry after a line But on a empty line That's going to be true now Stuff that I'm just going to do it this the I'm going to allow the cursor to be on a new line So Keep going right until we hit a new line Keep going right until we hit a new line if we have hit the end of the buffer give up right now Otherwise Move one more character so that we're now looking at the beginning of the next line Set the current line and then keep going right until we run out of offset Likewise when we go back again when we go backwards keep going left until we I keep going left until we Hit the new line No, sorry missing a step um Rewind back to the beginning of the Current line So we are now looking at the first character of the line So gap start minus one is the new line If we hit the beginning of the buffer give up Keep going backwards until Or keep going backwards while we have not reached the beginning of the end of the buffer and So we're now looking at the The character immediately after the new line for the previous line So and then we move right until we reach the offset Okay Location position we want to compute the length of The current line because that is now a pointer Cursor has gone away. That is now gap start. Yes. We do want old current line No, that does not need to be uh above the um It does not need to be above the gap because If we are actually moving up then the current line will be Bogus we just need to know it's changed So this will cause it to recompute the screen position on the first pass through Though I think render screen will do it for us. Yes render screen will do it for us Let's see what this does So we load It rendered Yeah, it in fact rendered wrong so Because the gap now represents our cursor Because we've loaded the file to the beginning of the buffer the gap is after the buffer. So the cursor is actually here so We do kind of want to move To the very beginning of the file. What is the So the the Sequences I know to go to the beginning and end of the document are g and 1g But I am pretty sure There are Sequences to go to the top and bottom that don't involve that Do I want to jump motion? I want an Up-down motion So shift g is go to line Control end or g Uh gg No, right. I'm just going to use g so I will implement the rest of go to line later But for now what it's going to do is just go to the top of the document Yep, I think that's all we need for that. We try it out and it still hasn't worked intriguing I will put some So just have some debugging. Yeah, this is vile This is the sort of thing you want printf for So what that will do is it will display on the status line the position of the gap Or rather the size of the The buffer before and after the gap. Yeah So there is nothing before the gap And 3 32 bytes after I can go right That's actually doing the right thing You can see as I move right lines are moving Bites are moving from the The bytes are moving across the gap So we should reach the end of the line and then it will stop I think it hasn't placed the cursor correctly Yeah, there we go So it does seem to be working. It just hasn't placed the cursor Compute length is booked for a start. We want to do that because we're passing in Gaps start as the End point Yeah, so what it was actually doing was it was counting the length of the line but not stopping at the cursor Which is why the cursor was stuck at the end of the line Keep doing that. Okay So we are moving right It's quite slow Because partly because it's drawing this let's try going down Yeah, yeah, see if it doesn't maintain the offset. Yes, that's okay. Good Let's take this off and see What that does to our performance It can keep up the repeat key Good Down looks good so far Okay Let's put some more text in our document Let's put a line here And we also want To insert some garbage so the tips Spans a couple of pages of So it's longer than one screen full of text. So let's see what this does Right So what this has done is it's hit the end of the line that's wrapped And then it's printed the new line to Move to the next line We don't need to do that because we wrapped So where is put see? Yeah, we do not want to call new line at this point All we need to do Is that Also has the advantage of being faster Much better So we go down Yep, and we are correctly spanning the multiple line It's slower Calculating the length of this is a lot Slower So up here it's quick Down here it's not That's okay Moving back and forward here is all right So can we Uh, actually let's just commit that shall we So that'll be compute length here being slow Now we could make these things static Or we could Try and remember the length of the lines The variables that are going to change most are like inp and endp Which are not easily made static Exo as well That's not better So if the We can always work forwards so I could put some logic in here that we detect whether we're moving down the line And we start from the current line which we have a pointer to Or we can record the lengths of the lines And I think that is what I would rather do So render screen Let's render screen gone Render screen here Starts at the beginning of the screen. Well, it starts from here Because we're going to use this to um Yes, so what we want to do is um No, that is going to That is going to store the The byte length of the line, which is not what I wanted What I want is put this back the way it was It's draw line here that's going to have to update the pointer So This is going to be the Y position that we started drawing the line When we finish drawing the line We record How long it was Render screen here Wipes the Uh The line length buffer from the point where you start drawing down So recompute screen position here Only work will only work if you are If you've previously called render so Right fetch the cached length of the line that starts at that position on the screen If there is no length then actually compute it So That Didn't work. Uh, this is not going to work because this is going to update in p to point at the next Line uh, this will This will point at the data for the next line But line length stores the Uh, the character length, which is not this the on-screen length, which is not the same Do we have to cache those as well? Let's rename those and let's and this is going to be the byte length Uh, I renamed this by mistake Status line length, okay So this will now contain the That should now contain the Uh The byte length of the lines on the screen now What this is all in aid of is moving rapidly around the screen if you need to page We're going to have to re-render and recompute these tables But we're expecting that I have thoughts of other representations of the data So rather than having a new line, it doesn't work Rather than separating lines with a new line, we can use, uh, length and data This will allow us to page very rapidly through the data Align at a time But I think that if you're looking at a long document, it won't be rapid enough And also that will only let us look forwards looking backwards is a lot harder Because we won't be able to find the beginning of a line Unless we know Where the beginning of a line previously is Wait a minute We don't care How long the line is In characters, we only care where it what what y position it starts at Because we also going to use the same information to decide Whether we need to re Render the lines below the one we're currently at So in fact what we want to store is We want to go from a position in memory to The y length is that true Well, this is flipping us to the bottom of the document The bottom of the page Because in p here is not actually lining up properly So it's never actually equal to current line. Therefore, we never stop We just keep going round until the this loop here throws us out So this suggests that current line But start p is the pointer of Of the beginning of the line you want to draw And in p is the pointer at the end So this should be right. So that should be like the correct length This should annotate the screen with the contents of the Line length cache Okay Looks okay except for this one, which is 115 that is not 115 characters long Because it's spanning the gap Right Yeah This is vile So we artificially adjust the start pointer here So that the line length here will come out correct I keep forgetting you have to press a spacebar to get past the stupid splash screen I wish I knew of a way to turn that off Okay down Oh, we're actually on the right place Fantastic. Yeah, you can see how slowly this is drawing. Is this going to work? Yes, it is Great So let's use that Ah spacebar Yep, that's nice and snappy and we can move back and forwards Good that looks like it's working So next thing to do is We've done some basic motion. Let's actually try Inserting text So this is going to involve re-rendering the current line We know the cursor is in the right place We will in fact Just do this Users pressed escape leave insert mode user has pressed delete So backspace Only backspace if like we can and we want to copy a We actually just want to Rewind over The character we last typed otherwise Insert the character after Processing the user input We do need to re-render the current line Which we do with Draw line Go to Current line y Draw line Current line and this will return the beginning of the next line This will update the line cache So we want to remember How long that was in lines If It's changed then Rerender the document Starting with the next line and we know the cursor is already there because draw line left us there. Okay Let's go to back here press i Insert mode Hmm That's actually working. Let's see what happens when we wrap It's like the tab stops are working and everything Okay, it's a little bit slow Yeah, that's not keeping up with my typing anymore But you know that's being bad that it's not bad at all Let me see if escape works We haven't updated the Okay, yeah We we're not updating the status bar, which is why it didn't go back to Uh Which is why the status bar didn't change when we exited. Okay, let's try typing some more I've pressed the delete key Yep Now I probably shouldn't let you Delete there. It's not really vi-ish, but Ha, yeah, okay Because it's ignoring it's not allowing you to delete the beginning of the line it ends up inserting the control code instead So it does that which is a bit weird That is not bad at all But we do need to put the cursor in the right place Which involves this So we actually want to do if c is um Else if C is eight Okay Let's give that a try and see if it works Okay, the cursor is actually in the right place We're not doing the traditional vi thing where we move the cursor back one After leaving insert mode that is deliberate. Okay. Now that's acting a bit weirdly But but it's correct. It's actually maintaining the offset when moving up from this two line line to this three line line But as this one contains tabs, it's a little bit weird Because a tab is logically a single character Okay, I kind of want to put something in the status bar to like tell you where you are but I don't think that's a good idea Because it will involve moving the cursor which will be expensive. Let us try Let's have some more movement keys. We want one of them That just takes us to the beginning of the line So this one should be easy and as we've just done this we can simply Okay Check this works Let's move right Home There you go Yeah, I wasn't expecting that to go wrong end This is definitely going to be vi-ish not vi Yep, keep scanning forwards until we see the new line This is another home. I think that's right Cursor up we home to the beginning of the current line. We step back one We home again to get to the beginning of the previous line And then we step forwards until we hit the Position we want a well end of line beginning of line that works fine does up still work Now I balked it In fact, it's hung. I know it hasn't it's just really confused. Okay, that condition is different. That's why it's down Up end of line beginning of line and a line beginning of line awesome Okay, there's a few more important ones w is word forward And b is word back Now these ones wrap on a slow system. They're absolutely vital So word right now, how do we define a word? Well, I believe that vim does it By a boundary between a space and a non space So what we want to do is we just want to keep going right until the previous character is a space and The next character Is not a space. So let's try it w Uh Did I get that right the previous character is a space And the next character is not a space So why is it stopped there? Ah, right. Okay In fact, it's more sophisticated than that. It doesn't use spaces it uses It looks like alnums I don't like alnums because I don't like these alnum Because it this libc uses a lookup table and it's a bit expensive but still Our editor is currently seven and a half k which isn't bad so What we actually want is an alnum on the left So if the thing on the left we want we continue looping if the thing on the left is not an alnum Or the thing on the right is an alnum So for this condition to be false Yeah I think that's better a w uh That's not quite what I wanted He's gone to a place. It won't go any further. Oh, yeah, we need we need to need to bump it Okay, so it's moving to the end of words successfully. Hey, why is that going there? That's not a word Now it is using spaces a word consists of a sequence of letters digit underscores or a sequence of other non-blank characters A capital word consists of a sequence of non-blank characters with white space Okay, we want this to be simple so Okay This is going to be even more complicated than I thought it was So we actually want to do it We want to move at least one space so we do this Because that guarantees us that left Is that let's just read left at the same time So right is Gap end. Okay so if left is alphanumeric and Right is not alphanumeric This is a word that will put us Now run this the other way around because you want to be on the beginning of words So this is a little bit messy right word. Yep Okay so that it's not line wrapping if the left is a space and The right Is not a space Then stop Now how do we handle new lines? If we pass a new line Then we need to update current line So we can look to see if left is a new line, but that doesn't tell us that it's Yeah, if left is a new line, then this means that Right is not a new line. Yep New line Let's try that. Yep, that works fine Now let's Skip relatively quickly It's keeping up with the repeat key which is Which is good enough Good that works Right word left That'd be the B character Yeah, I still don't really understand Why that makes such a difference to code size Okay, if Start is not equal to buffer start so we're moving backwards Naturally now we've refactored this now. We're moving characters to the right. So we want start end left start times one If this is more complex because When we pass the new line, we're not looking at the beginning of the line. We're looking at the end of the line so If right Is a new line then we just Indicate that we need to recompute the line start and if then We wish to And then we wish to wind the current line back to the beginning of the Current line So we want the character to the left of current line to be New line So word forwards Word backwards Yep, that works fine Good Good We are making progress. It's actually mostly the basic editor is mostly there Uh, and yeah, this is a previous Editor I looked at It's a pretty decent simple editor, but um It's all written in knrc and I had a lot of trouble making it compile Well, I'm proud I couldn't make it compile And I also suspected it was going to be rather big So hence the current project Okay, now it's a vi. So there is one feature we very much need Which is the command count. So we actually need to Actually, I'm not going to do it like that. So if C is a digit Then uh update the Command count Okay, do I want to attempt to Do I want to actually attempt to show this on the status line? Let's let's have go and see if it's fast enough Come up before I Okay Okay, so Let's try one two three. I think that's fast enough Spot the uint16 Um, because you're not actually It's going to be about half the number of characters in real life once I fixed it away And you're mostly only going to be doing one or two things So I think that's good enough Um, I think this will wipe the status line afterwards Okay, we now actually need to pass the count into The function so they can do, you know, the right thing Try to find that out as an unsigned And there should probably be a uint16, but Never mind. Yeah, I will make it a uint16. I mean an unsigned is a uint16 up with this uh architecture So home is easy because Count is ignored End is easy because the count is ignored left Now we actually The command count needs to default to one And these just turn into simple loops It's when we start getting to stuff like go to line that life against to get a bit interesting Which by the way, I should actually implement Right Yeah, the 8080 is like the worst language for doing this kind of The worst architect for doing this kind of thing in Due to having like lousy pointer support You can just about do loops like Uh, where's my you can just about do loops like this Uh Using two of the register pairs, but it's it's just really painful and the compiler can't manage it Okay now Inserting text Vi does actually support this I can say like in 10 insert Who escape and you get 10 of them But it's quite hard Because you end up with two different implementations of the code You've got the one where you're reading the user's input And then you have the second one where you duplicate what just got written I think that's doable actually So here's the first one And here will be the second one Now we wish to record Uh, this is actually harder than it looks if we're just inserting stuff. It's one thing But if the user's deleted stuff That's why We don't normally let you delete left. What does v? What does vim do for this? Uh Three insert It resets the count five insert Backspace said said No, that's actually Interesting. Okay. Well We'll give it a go Um, incidentally, I've spotted a problem with this code, which is if you type in a new line Then it doesn't work so We wish to record where the Where we started that's going to be Gap start I think that if we're backing up uh if we ever delete beyond the beginning of the The point where we insert Then, you know, just give up doing any repeated insertions now When the user pressed escape The text they just typed in the spans The memory from start p To gap start so we can actually just duplicate that Yes, we can do this so I was just going to do some mem copies actually, but um We do want to check for Actually, we do need to check here. This is the first point. We're inserting text. We need to check for end of Out of memory conditions so if Yeah, okay if If there is enough space for all the repetitions then destination is gap start source is dot p Size is len. We know they don't overlap. They're for mem copy is safe Gap start Okay, uh And we can actually put go to line in now. It doesn't not actually implemented ha That's all right Offsets So let's try going right five. Yep. Good 10 down 8 up good Let's try 10 insert foo escape It didn't we've got to be render Which reminds me that there's actually a very important Uh command I need to implement So I believe this The escape is octal, isn't it? So we want control l 12 Things always go wrong and it's really useful to have a A very simple command that just makes it re-render everything on the screen And we do that by calling render screen first line So we've this is going to be the third point where we're going to use this code. So this three lines of code here So we're actually We also want to do this Okay, well for a start we want to put this in as a common piece of code Actually, do we want to put this here? Yeah, I think we do so in fact So this will actually do a little bit more work each key press. So we need to be really careful about performance So if we do repeated inserts, then there's a pretty good chance that we are going to be um Changing the length of the line And yeah, we are going to have to come up with a way to do new lines here, you know what? New lines are more important. Let's lose this piece of code. We can always put it back again Make sure this all works a control l Hmm didn't do anything apart from, you know, locking up You don't need to go to the beginning because that will do it Damn it. That should work control l is hex 14 No, oxal 14 Um, I had actually pressed the caps lock key rather than the control key because the cabro control key At least in this emulator is in the wrong place Right. What happens if we actually type in some new lines? return Oh, of course it does I should have expected that you spot the cursor doing the right things it moves across the control code So So we are inserting a new line. This is going to Definitely change the height of the current line So we are going to need to re-render it from the current line so If we have space Insert the new line Go to the current line draw it The next line will of course be junk Actually draw line will return a pointer to the next line. So in fact If we do current line equals draw line that will update current line for us to the beginning of the next line We then flush The display length of that line the new line is drawn one below It's drawn below this one So we'll then fall out through here and this will attempt to render the line again Uh, we've moved on to the next slide. So this will attempt to render the next line And uh, which will then cause the rest of the screen to be refreshed. So let's give that a try Now this is all so the thing we have to do after this one is the really important bit which is scrolling And I think that's going to be a bit of a nightmare so insert Foo return It wasn't what I wanted And why is the cursor down there? Because it didn't split the line properly. That's why We don't want to return that we don't want to insert the carriage return We want to insert the new line because that triggers the that's the code we use internally for the new line Let's try this Okay, that wasn't really what I wanted Yeah, that hasn't rendered the rest of the line the rest the insertion bits working You know, we actually have to render everything anyway. So let's just do render screen We're in the right place. So we just do render screen current line. It just draws the rest of the screen We are then going to drop through here and We are going to drop through here and it's going to render it again, but let's just see if that works Yes, that is working. So why doesn't it re-render? Oh, yes, while we're at it display length can turn into display height So that will save a number of divisions. This is going to contain the number of lines only Which can of course never be zero It hasn't chopped that much off. We are up to 8k As a scrolling will probably add a chunk then we need to do file saving. Which is probably not that much work Um We're about to hit for the for the kpro. We're about to hit the 50k mark for text files So I'm Be nice to add stuff like search, but we're not going to get uh Regular expressions. It's just not a chance. It'd be nice to have stuff like change change word We certainly need a few delete operations. We need at least dd And we definitely need x but I think we can probably call it done at that point at least to begin with But we do need to make this work and make scrolling work that draws the current line We're in the right place So we should just fall through the rest of this code So foo return Okay, that is working Wonder what's different from last time Okay, never mind Okay, and we're just going to ignore the count. I think this is going to get Way too complicated At least until we get scrolling. Okay, all right. I need another rehydration break 570 lines. That's not bad I need another rehydration break and Then let's have a go at scrolling Okay Um, actually before I do scrolling. Let's just add a pen because it's easy in a real vi impending text involves Moving to the special place the right side of a line you can't get at but For us it's easy. All we do is Like so let's make sure that works And then we think about scrolling so we control the scroll position by the By first line which tells the system the address of the top line on the screen so append Okay, that didn't update the screen position Never mind. That's easily done so In order to scroll what we need to do is to nominate a new line To go at the top or the bottom The tricky part is that We have to have the start of a line at the top of the screen append So when we reach the bottom of the screen And we want to go one more down We need to probably figure out A line roughly in the middle of the screen Nominate that as the the top of the screen and then redraw so everything will then shift up half a page That's recently straightforward because you can start here and go down In the other direction it's tougher because Once we have scrolled to this point and you scroll up We need to work backwards which we're not very good at doing Until we have countered a certain number of lines And we need to trigger this when we go Off the bottom of the screen or off the top of the screen again Going off the top of the screen is easy Because we know that if Current line is less than first line. We're obviously off the top of the screen But going off the bottom of the screen is harder Because this line at the bottom of the screen may actually be quite long so if we go off the end then Yeah, you can see it's got confused. I'm now drawing into the status bar. Let's insert some What happened to when we reached the end of this? Also, we should come up with a way to avoid redrawing most of the line in this situation Oh, it wraps around to the top. That's interesting So that's just the terminal being weird Okay, now it's got very weird and I think it's hung So how do we do this? I think that for the For testing scrolling when we hit the bottom of the screen If the line Actually, let's see what vi does This is vim rather So let's make a big line big line so vi Insists at the top of the screen as a line You can't actually Scroll down any further. It just snaps to line 512 so off the bottom of the screen Uh, it's interesting. It won't even render it That's a good idea. That just avoids the issue completely of what happens when the cursor's there Yeah, and we can But we don't actually have that information. So We calculate the line lengths as we draw so By the time we realize that the line is off the bottom of the screen Then it's too late But we can stop the user putting the cursor there. That's straightforward We just check to see if you know the current The current Current line y plus the height of the current line Is greater than or equal to the height of the screen so We need to test for A case when we need to scroll And do the right thing so if The current line is less than the first line else if current line y Is greater than or equal to height because we know this means that the users just put the cursor down off the bottom of the screen or current line y plus display height current line y Display height is only valid after We've drawn the screen Okay, let's go with that for now if that is Okay, now in this So after any command we obviously need to test to see whether the cursor is moved We're also going to need to put it into insert text Obviously, we'll only be able to test the scroll off the bottom at the moment off by one error Oh, yes, uh cursor down Ah, right. Okay. Okay We actually need to do this from inside recompute screen position Because that's the thing that then that updates um cursor y and moves the screen So yep, that's actually reasonable Plus we ought to get testing for scrolling for free in a number of places so In fact We don't want a separate function to do this at all. We actually want to do it right here. So if Current line is less than first line Scroll down If It's then this if here So it is important to note that this will invalidate first line Because that that may change And it will also invalidate the all the various display caches Okay, we need to scroll up Good so scrolling Up We need Firstly, we need a new line It also occurs to me that scroll down here is then going to fall through into this code which is going to recompute the screen position But we're actually going to want to call render Render does not set the screen position, but it does update the caches So that's actually fine Scroll down will adjust the pointers and it will re-render the screen And then we go through this code and it will then compute the position to place the cursor This code Has to happen after we've done this And if we need to scroll then of course we have to do this bit again So this is actually going to need to be a loop. I hate loops like this It's they trying to put the conditional in the middle Okay, anyway So scrolling up So I think we actually want a A single function here Because what we want to do is to figure out We want to try and put the current line the one that's been pointed to here by current line Kind of in the middle of the screen. I think So in order to do that we essentially have to work backwards from the current line Calculating the lengths of lines Until we have counted about half the screen height So this is going to involve some fairly creative use of compute length Okay While line is not total height is less than Height over two Now we know we know that first line is above the gap. So this makes things really straightforward The first line is pointing at the character after a new line So Start back up to be pointing at the new line So this will then Back up to the new line and then back up to the Want to pre decrement And then keep going back until the previous until we're looking At the character after another new line if we want to So line end points at the new line for the current line We do this and now first line is pointing at the beginning of the previous line And line end is pointing to the end of it. This allows us to do Total height equals compute length first line line end null So that will Calculate the length of the line that we've just walked through Added to the total height Once we've done that We then Clear the screen And render From the first line 455 qualifier error Really draw line Returns a cont Current line is not const 48 qualifier error You see draw line really needs to return the type of the thing that got passed in 467 qualifier error Yeah, this makes no difference to the actual code This compiler is It doesn't know what to do with const other than just check them. Okay, let's go down One more Yeah, uh It's gone into an infinite loop where it's attempting to adjust the Um The scroll position, but it never actually manages it. So I bet compute length here is returning zero So if this ever gets ported to more sophisticated terminals We'll need a way to use Uh in certain delete lines to actually make things a bit faster I'm not going to worry about that now. So that's not actually printing any numbers, which means I do not believe it's going through here. Ah, right Yeah, first line was current line Just trying to move the it's current. Yeah, okay Because first line is actually at the top of the screen at this point So you can't scroll up any further But we keep insisting as it try to scroll so Hence the infinite loop Also, I'm aware that problems will probably occur if you have lines longer than one screen full And down Yep, that seemed to work Add a bit slow What's it doing? Well apart from Computing the lengths of lines Well, yes, we are successfully scrolling my debug Numbers aren't helping but that's dreadful. Why? Well, I suppose we are counting We are good. We are going through the Strings twice wants to move the cursor and wants to calculate the length Okay, I'm going to just Chop those out and hope that makes a difference By placing the scroll position in the middle of the screen. It means we can use the same routine in both directions Okay, that's way faster. Uh, the problem is obviously the debug tracing good That's reasonably nippy Nice if there was a way to flush the keyboard buffer after a render Actually, there is It's not very good Okay, um, why are we not getting the EOF at the bottom of this? Anyway, can we insert some text? Now we get an EOF Stupid emulator K pro keyboard Yeah, we're really going to have to do something about that It's Judging by the flickering it's ray drawing these two lines and this line and probably this line as well So that's a bug there Let's go up here a bit. Let's try and trim text here. That is just plainly not good enough But the scrolling does actually seem to work awesome. So EOF Okay So Yeah, why did we get an EOF at the end of that line? Because they that should not be possible No, that is fact perfectly possible um So in fact, we want to do We only want to draw The EOF marker If we're at the beginning of a line other than otherwise we just treat it like a new line Because like the character at buffer end is a new line So can I do 30 down? Have it work? Yes, I can So here is our end of file. So if I can search some text Okay Yeah, it's not really happy doing that So this is just edge conditions I'm inserting text before the last end of file character Which means of course that Now there is text before it then We don't draw it because like we're not the beginning of a line But we haven't drawn the line below Because we don't need to Even though a new line has just turned up because we're inserting text before the end of file marker So that's to be honest not too hot Uh We could probably apply Rules to make that work better, but I think it would be easier Not to I'm going to do this This is going to display the the bottom of the file in a slightly different style Which I think will be easier to work with So 40 down And here is our End of file thing so I can search some text So it's actually behaving exactly the way it did before but Due to the different style of Rendering It looks better So 100 down I need to find some Different stock text. I've typed that one a lot Okay, that's actually working good I just want to Um and recompute screen position here is going to Do the scrolling if necessary so we should get inserting Long lines of text for free We should get scrolling when inserting long amount of text for free insert So this is working because this line fits in these two lines. So hopefully As soon as we Yep, that worked awesome It has reset the status line. Uh, do I want to change? Let's try using go to zero zero for that We do want that one to be clear. We do want that one to be clear And we're still above 50k free space That's great. So how does that look? Um Is that better than clearing the screen? I think it looks nicer It's more obvious what's happening Just generally a bit smoother I mean, it's also going to be very slightly faster If you're using a real terminal If you're not using a real terminal then Clearing the screen is like completely quick Okay, that looks good Right, we need a few more commands in particular. We need X Which is which I use all the time like everyone has their own favorite set of Vi commands nobody uses the same set of vi commands so if uh, if we If we have reached the end of the line stop otherwise Delete the character To the right of the cursor Yeah, okay Uh, oh, yeah, I mean you want to redraw the current line This is common code So we can do redraw current line So what? Where are we doing this? Well, we can Okay, that should do that No, it doesn't So X That works just fine Let's delete several at once 10 x 20 x yep, good Uh dd now d capital d it takes a motion as a parameter And we can actually support several The one I use is Uh, so that's lowercase d So this is a two key sequence And you can use all kinds of different motions if you want the common ones are w for word and another d for line so So we read in a character Then we do the work Now if it's a Now if it's a word What does this do d w It deletes up until the next word boundary So we wish to While Gap end is not buffer end So essentially we want to delete characters until word boundary here is correct Okay delete, uh, yeah, if Okay, yes, we will to get the character on the left if We're at the beginning of the buffer Then let's call it A We assume that we're on a word. So let's just do that Otherwise it's the character to the left of the gap so While gap start is our gap end is not buffer end We always want to delete one character so We always want to delete one character, but we have to put the check there Yeah, okay. Yeah We want to delete up until the end of the buffer, but we also want to look at the character there I'm trying to avoid having two Two of these comparisons, you see Because that's like nasty Yeah, I don't think I can't I don't think I can So if gap end is not buffer end All right if gap end is buffer end Otherwise fetch the I don't have auto completion set up on this vim, but I do elsewhere Word boundary is called if word boundary left Gap end then Stop so I want to be got here, right delete word Nothing no nothing delete word No, so if there's white space beginning of the Line or the buffer then Actually, yeah, if we're at the beginning of the line gaps start here will be a new line So for consistency, let's make this a new line as well right But this hasn't worked. Hey, hasn't it worked? I think it could well have worked. It's just we didn't see the result because we forgot to redraw the current line delete word Well, it deleted it didn't delete what I wanted, but it deleted And something is confused because I can't go down. So let's try to redraw Okay. Yeah, that's actually deleted quite a lot Right We want to stop when we hit a new line So we're not going to allow the user to word delete off the end of the line Right, we can move by words So move to the middle of title delete word Okay, that's doing That's more or less right It is deleting words However Oh, no, that's correct. It is deleting the space following. Good. That's now working. We have delete word delete line Is a little more challenging Not that much more challenging actually And so we want to go to the beginning of the Line actually we're going to do this one out of line for reasons So all this is going to be is I think that's right. So you see this then allows us to just do cursor home redraw current line Uh, sorry cursor home delete rest of line The reason why we want to do this is because this also allows us to do Now we don't actually want to do redraw current line In this for this reason So actually we're going to cheat That is if we call with a counter zero then this means that Uh, then it's been called internally from somewhere else So we should be able to go into the l of title and do delete dollar No problem Uh, we go down to this long line and we do dd Is that right? Okay, that's actually left an empty line. We don't want that Let's just do a control l to make sure. Yep. It has done the right thing. Well, the wrong thing, but Yeah So this is actually So what this has done is it's cleared the line So we now need an extra condition which is If we're not at the end of the buffer Then delete the new line following. We know there's a new line because this has left it there So delete the version line dd What did that do? I don't think that redrew correctly Dd Yeah, that hasn't redrawn correctly. So I have to do a control l and it has actually worked but Why has that not worked? Shouldn't this figure it out automatically? We are the reason why it hasn't worked Is current line y hasn't changed and the Caps log But we need to flush the display height to force it to Recalculated And in fact, this is going to apply here as well So we can actually do Do that. Oh, wait a minute. Wait a minute Redraw current line is supposed to actually Yes, it draws the Yeah, that's rubbish. Uh, that's supposed to work. I know what's happening. Yeah So the current line is being Uh cleared Yeah, we don't need this after all the This clears the line This removes the next line But the line after is the same height as the line we just cleared So it doesn't think anything's changed So we do actually need to Do this So we should be able to go down to version dd Done good. Let's just make sure the first line works without doing anything bad. Yep Let's go down to the bottom of the file Can I delete this line that doesn't have anything in it? Nope. What about this one? Good That works just fine Excellent So we have some deletion work working. That's good We do need to make go to line work And this is pretty straightforward We go to the beginning of the Uh, the document and then we just count new lines so So while So line number is actually one based So We don't want to check Yeah, okay while Line number is one based so we do pre-decrement Recompute do we get recompute screen position for free? Yes, we do so all I need to do is that Let's save this go So there's actually a problem. It's not going to be quite like vi In real vi if you do Shift g with no count It goes to the bottom of the file. You need one g to go to the top But that's kind of not how our one is working Anyway five go Uh No No, no, we are there. It just didn't do anything 30 go Anyone okay, that hasn't done what I wanted to So what this is supposed to do is to Oh, we didn't set current line. Okay, so current line equals cap start It doesn't didn't know anything had changed So I think there is Once this works, there is one more command I need that was kind of weird Okay Five go That's not line five for go seven go Oh This is actually this is not working at all. What it's doing is it's very very slowly scanning to the end of the document So this quite slowly scans the beginning of the document So keep copying Lines Yeah, this should work. Okay. Go to line zero is in fact jumping to the end of the document. So we want go to line one to go to the beginning of the document Because after we've done insert file, we're looking at the end So if we do one go does it go to the top? Yes to go long pause end of the document Yeah, I'm not So enamored with that Ah, yes by decrementing line number here and also decrementing here. We would uh subtracting two each time round And yeah, that wasn't working. Okay. Let's go to 10 go Not bad 50 go End of the file one go 30 go Okay, I don't need that status line After all which is nice I want Yeah the default count So vi distinguishes between not having a count and having a default count of one So we could do this, but it would involve Uh We'd have to pass in zero to all the commands and then the commands would have to Do like Each one would have to set its own default Unless We could specify the default in a structure So this now becomes struct command It's a cb equals So go to line here is a default of zero so if Command count equals zero command count equals command default count and What did I call it that needs to just be this needs to be a const Yikes an equal conversion of into two pointer. Does it not like the Uh, that's because this is an array. I don't want that anymore so there is one uh We need one command for doing editing We need a few colon commands to allow us to save files, which is kind of important in an editor But there is one command that I just used quite a lot which reminded me of it Okay, shift g Okay, that was a bit slow Also, that's the wrong one Uh, there's one command which Kind of defines vi really which is dot Dot repeats the last thing you did And it is amazingly useful However, I am not really sure I can implement that in a reasonable fashion shift g I know why this is slow It's hitting the end of the buffer and then it's just spinning on that loop while a counter goes From max uh, you went max to zero And that will be why counting zero failed as well So for dot you have to store what you did and it's only some commands are stored And it's not just a math like motion commands are not stored Things like x are stored counts are stored But the real problematic one is insertions Are stored So I should now be able to press dot and have that Thing be inserted It is amazingly useful But also kind of problematic Um, so I would like to implement that but I'm not going to do it now because it's going to be a complete rabbit hole Anyway command I would need to implement is j Which is join join is The only blessed way to delete new lines between lines So we kind of need it And it is relatively straightforward. What we do is we Do not move the cursor just double check that Yep We do not move the cursor We Shunt a pointer off to the end of the line. Oh, yeah, and you notice what I do Uh, let me just So if I press shift j here There's a space So that's really straightforward Uh We We want to work right until We hit a new line Then we turn that new line into a space and We redraw for it to load So shift j No problem Just works It's a really good way to make kiddiesly long lines Which take a long time to render So let's actually just try making a line longer than the screen and see what happens I mean, it is pretty tough given that How long it's taking to redraw this? What's my doing? We have counts 10 j 10 j Oh We have in fact reached the end of the line around the end of the document So oh command. I forgot Oh and shift. Oh, I was just about to do that Okay, uh Let's insert Yeah, I can't be bothered to type that much So I need to type like 240 characters But join works. So let's actually put in Oh for Open below Which is um cursor Down one cursor home one We are now looking at the character after a new line So we wish to Insert a new line That's after the next one. But how do I force this to redraw from here? Oh, the current line has changed length. So Hmm, have I just discovered a problem with this code? I think I have Well, anyway, uh, the current line has changed length. Therefore I need to uh nerf thick display height cache Redraw it and insert The text now I have a bit of a suspicion that with very large lines Once they get bigger than the screen Then you will go into an infinite loop of redrawing As it tries to adjust the scroll position That would be bad. So Yeah, we're under 50k So shift o for open Didn't do what I wanted there I may need slightly more intelligent redrawing. I mean it's done the right thing But I was kind of hoping that that wouldn't trigger the redraw I think I may need some way to batch redraws together But given that redrawing Repopulates the line height. I'm not sure it would be safe Yeah, uh, okay Let's have a simple dirty flag. I think the screen is dirty draw current line is uh So the idea is that whenever we call BIOS con in we call this instead If I can remember what I called it get char and redraw And instead of calling redraw current line We do screen is dirty true So here these do not actually update the So we don't need this anymore and all these things We don't need this anymore Just say Okay, let's see if that actually made a difference. Oh, yeah, I know so That's it. What's it do for the the size? bigger, you know Okay, it seems to be That did not redraw It did the work but it didn't redraw. Okay, let's try open redraws twice. Why does it still redraw twice? Where is open gone? Insert text what does insert text do it just calls get char and redraw So it should not actually It should do the redraw at this point unless Recompute screen position is triggering a screen render, but I don't think it can interesting Anyway, where's my join gone? So that is setting screen is dirty to be true So you see I would then it would should Come back here to recompute screen. Ah redraw and get char Get char and redraw. Yeah So that's Oh, yeah, I'll be using old current line for anything anymore Current line No, let's lose it I'm actually amazed this is working as well as it is without proper screen refreshing Okay, let's go down to here and we do a join that didn't work That did work because the current line changed height. Okay, let's try open Hmm So this is also it is actually adding a new line below the Um Current one. Ah, this is going to bite me Uh, because we've moved stuff around what we haven't called recalculate position. Therefore Current line y is not correct So that's not actually doing what I wanted at all but Uh, recalculate position A recompute screen position May cause a scroll Which causes a screen render So you want to avoid calling recompute screen position where possible So this is actually doing the same logic that I need Which is it physically redraws the current line Yeah This causes current line y to be computed correctly But it comes from screen y here So I think what I actually need here is to Yeah, we haven't It's a current line y current line y is currently set We're from the the line that we were at before we went down So do we want to get rid of the screen dirty stuff? Is that just not going to work? Uh, or can we do More interesting things What are we using current line y for? From here on This thing insert text This thing in delete line And open below. So I think what I'm actually going to Do Is Let's give that a try So the reason for the plus plus in the um In open below Is because we've moved the cursor down one and we don't want to nuke the cache for the Uh, this line, which is unrelated to what we're actually doing. So open below Bingo this emulated keyboard is not much fun to type on to be honest That does seem to be working Good And yeah, I also wanted to check join Actually, I'm going to save a little bit of space that way So we want to join these Good that's working We've actually speed things What's that doing is that in an infinite loop? I just went down a few lines. Why would that be? Yeah, that's in an infinite loop Um, I think it is trying to adjust the scroll position, but why would it Doing that let's try that again Are in lower case Now we go down Okay, I think it's the line. I think the length of this line is wrong And it thinks it's off possibly off the bottom of the screen so this Merges the lines it calls the line height changed which nukes the Cache height of the current line and all the lines below And it calls screen is dirty screen is dirty causes redraw current line redraw current line Tries to draw the current line Which should be fine This will fire And it will cause the screen to be rendered We then recalculate the screen position which may cause Think that it may be calling recompute screen position with stale data I think that Dirty stuff is more trouble than it's worth Hmm So that did hit this Okay, I need a short break and I will think about this Okay, I'm going to lose all that screen dirty stuff Thanks for the magic of copious undo Because it's just over complicated and to be honest. I'm getting quite tired And I don't think I'm up to the complex logic. So let's just keep Getting rid of stuff until This I think that was here. Do we have Okay So the problem was that These weren't behaving quite right So Yeah, so we've taken the logic for home for open down. So it is actually moving the cursor to the right place But let's try join Yeah, and that's got the same problem we had before So we want a different solution to this it is The problem is that redrawing current line is not good enough if the Because there's the line below that is changing height So the obvious thing to do here is to Somehow Forced it to redraw from here down like it's joined. We know we're gonna have to Which we can actually do very easily because we've got the code Because we can just do this Current line y Called Render screen render screen like this that does forces a redraw So open below is actually going to happen in a very similar way So cursor down cursor home We know there's space because we do this so we insert the carriage return There's insert text gone we go To Current line y is not set. So we actually need to Redraw the current line does that set? Yeah, that recomputes the scr