; ed - line editor ; ; A text editor modeled after UNIX's ed, but simpler. The goal is to stay tight ; on resources and to avoid having to implement screen management code (that is, ; develop the machinery to have ncurses-like apps in Collapse OS). ; ; ed has a mechanism to avoid having to move a lot of memory around at each ; edit. Each line is an element in an doubly-linked list and each element point ; to an offset in the "scratchpad". The scratchpad starts with the file ; contents and every time we change or add a line, that line goes to the end of ; the scratch pad and linked lists are reorganized whenever lines are changed. ; Contents itself is always appended to the scratchpad. ; ; That's on a resourceful UNIX system. ; ; That doubly linked list on the z80 would use 7 bytes per line (prev, next, ; offset, len), which is a bit much. ; ; We sacrifice speed for memory usage by making that linked list into a simple ; array of pointers to line contents in scratchpad. This means that we ; don't have an easy access to line length and we have to move a lot of memory ; around whenever we add or delete lines. Hopefully, "LDIR" will be our friend ; here... ; ; *** Variables *** ; .equ ED_CURLINE ED_RAMSTART .equ ED_RAMEND @+2 edMain: ; because ed only takes a single string arg, we can use HL directly call ioInit ret nz ; diverge from UNIX: start at first line ld hl, 0 ld (ED_CURLINE), hl call bufInit .mainLoop: ld a, ':' call stdioPutC call stdioReadLine ; --> HL ; Now, process line. call printcrlf call cmdParse jp nz, .error ld a, (CMD_TYPE) cp 'q' jr z, .doQ cp 'w' jr z, .doW ; The rest of the commands need an address call edReadAddrs jr nz, .error ld a, (CMD_TYPE) cp 'i' jr z, .doI ; The rest of the commands don't allow addr == cnt push hl ; --> lvl 1 ld hl, (BUF_LINECNT) call cpHLDE pop hl ; <-- lvl 1 jr z, .error ld a, (CMD_TYPE) cp 'd' jr z, .doD cp 'a' jr z, .doA jr .doP .doQ: xor a ret .doW: ld a, 3 ; seek beginning call ioSeek ld de, 0 ; cur line .wLoop: push de \ pop hl call bufGetLine ; --> buffer in (HL) jr nz, .wEnd call ioPutLine jr nz, .error inc de jr .wLoop .wEnd: ; Set new file size call ioTell call ioSetSize ; for now, writing implies quitting ; TODO: reload buffer xor a ret .doD: ld (ED_CURLINE), de ; bufDelLines expects an exclusive upper bound, which is why we inc DE. inc de call bufDelLines jr .mainLoop .doA: inc de .doI: call stdioReadLine ; --> HL call bufScratchpadAdd ; --> HL ; insert index in DE, line offset in HL. We want the opposite. ex de, hl ld (ED_CURLINE), hl call bufInsertLine call printcrlf jr .mainLoop .doP: push hl call bufGetLine jr nz, .error call printstr call printcrlf pop hl call cpHLDE jr z, .doPEnd inc hl jr .doP .doPEnd: ld (ED_CURLINE), hl jp .mainLoop .error: ld a, '?' call stdioPutC call printcrlf jp .mainLoop ; Transform an address "cmd" in IX into an absolute address in HL. edResolveAddr: ld a, (ix) cp RELATIVE jr z, .relative cp EOF jr z, .eof ; absolute ld l, (ix+1) ld h, (ix+2) ret .relative: ld hl, (ED_CURLINE) push de ld e, (ix+1) ld d, (ix+2) add hl, de pop de ret .eof: ld hl, (BUF_LINECNT) dec hl ret ; Read absolute addr1 in HL and addr2 in DE. Also, check bounds and set Z if ; both addresses are within bounds, unset if not. edReadAddrs: ld ix, CMD_ADDR2 call edResolveAddr ld de, (BUF_LINECNT) ex de, hl ; HL: cnt DE: addr2 call cpHLDE jp c, unsetZ ; HL (cnt) < DE (addr2). no good ld ix, CMD_ADDR1 call edResolveAddr ex de, hl ; HL: addr2, DE: addr1 call cpHLDE jp c, unsetZ ; HL (addr2) < DE (addr1). no good ex de, hl ; HL: addr1, DE: addr2 cp a ; ensure Z ret