; ************************************************
; *                                              *
; *   Osborne-I Video Display Oriented Editor    *
; *                                              *
; *       (Based on "VDO" Text Editor)           *
; *                                              *
; ************************************************
;
; Copyright 1979 & 1981 by Creative Computer Services
;
; Changes for Osborne-1 and CP/M,
;               Copyright 1982 by Fritz Schneider
;
;
; This editor is based on a program published in
; BYTE magazine Vol. 7, No. 10. It was written by
; Richard Forbes of Creative Computer Services,
; P.O. Box 1327, Corvallis, OR  97330. Reproduction
; in any form of any part of this program except for
; personal (strictly non-commercial) use, is
; strictly forbidden without the prior express
; written consent of Creative Computer Services and
; of the author of this version:
;
;        Fritz Schneider
;        16016 Red Cedar Trail
;        Dallas TX, 75248
;
; This version is adapted to the CP/M system as
; used on the Osborne-I computer, using memory
; mapped video starting at location F000. Features
; have been added to make it more "Wordstar-like".
; It has be re-coded in 8080 mnemonics so that it
; may be assembled using the standard CP/M ASM.
;
         ORG   100H
;
;   Define symbols in the base page
;
BDOSep:  EQU   5
FCB:     EQU   05Ch
FCBDriv: EQU   FCB
FCBFnm:  EQU   FCB+1
FCBFtyp: EQU   FCB+9
FCBExt:  EQU   FCB+12
FCBRecs: EQU   FCB+15
FCBCur:  EQU   FCB+32
;
;  Define equates for Z-80 Instructions
;
BIT7A:   EQU   07FCBh
BIT7M:   EQU   07ECBh
NEG:     EQU   044EDh
LDI:     EQU   0A0EDh
LDIR:    EQU   0B0EDh
LDDR:    EQU   0B8EDh
CPIR:    EQU   0B1EDh
CPDR:    EQU   0B9EDh
AdcB:    EQU   04AEDh
SbcB:    EQU   042EDh
SbcD:    EQU   052EDh
LBIX:    EQU   046DDh
LCIX:    EQU   04EDDh
LIXD:    EQU   02ADDh
STIX:    EQU   022DDh
MVIX:    EQU   021DDh
POPIX:   EQU   0E1DDh
PUSHIX:  EQU   0E5DDh
INCIX:	 EQU   023DDh
DECIX:	 EQU   02BDDh
LDAIX:   EQU   07EDDh
STAIX:   EQU   077DDh
         JMP   EDIT      ;JUMP TO THE START
View:    DB    52      ; Viewable columns
Lines:   DB    23      ; Osborne screen length
;
; ******************
;
; BDOS
;
; Enter BDOS, but latch onto warm start for
; recovery purposes.
;
BDOS:    PUSH  H
         LHLD  0001
         SHLD  SAVE1
         LXI   H,BDOSws ; Replace warm start
         SHLD  0001
         POP   H
         CALL  BDOSep
         PUSH  H
         LHLD  SAVE1
         SHLD  0001
         POP   H
         RET
SAVE1:   DW    0
BDOSws:  LHLD  SAVE1    ; Restore warm start
         SHLD  0001
         CALL  Reset
         MVI   A,3      ; Give I/O message
         STA   EdErr
         JMP   Skp1Ed    ; Continue editing
;
; RESET - Initialize screen and machine after
;        wresting control away from CP/M
;
Reset:   POP   H         ; Get the return address
         LXI   SP,Stack  ; Set our stack pointer
         PUSH  H         ; Put return address back
         MVI   C,2       ; Write Clear character
         MVI   E,01Ah    ; and home cursor
         CALL  BDOS
         LXI   D,Header
         MVI   C,9       ; Print string
         CALL  BDOS
         CALL  SetFnm    ; Show file name
         LDA   InsFlag   ; Check heading
         ORA   A
         RZ
         LXI   H,'n '
         SHLD  DspIns+1
         RET
Header:  DB    1Bh,29h   ; Low intensity
Headorg: DB    'VDO - '
Headfnm: DB    '              '
         DB    ' Line '
Headlin: DB    '1    '
         DB    ' Col '
Headcol: DB    '1    '
         DB    'Insert '
HeadIns: DB    'off'
         DB    1bh,28h,0Ah,0Dh,'$'
DspFnm:  EQU   0F000h+Headfnm-Headorg
DspLin:  EQU   0F000h+Headlin-Headorg
DspCol:  EQU   0F000h+Headcol-Headorg
DspIns:  EQU   0F000h+HeadIns-Headorg
;
; ************************************************
;
; SetFnm
;
; Display the name in the FCB in the heading
;
SetFnm:  LXI   H,DspFnm  ; Show file name
         LDA   FCBDriv   ; Get drive
         ORA   A         ; Was it specified
         JZ    Skp1Fnm   ; No, skip it
         ADI   040h      ; Get drive name
         MOV   M,A       ; Store it
         INX   H         ; Bump pointer
         MVI   M,':'     ; Punctuate
         INX   H
Skp1Fnm  XCHG            ; Save Header pointer
         LXI   H,FCBFnm  ; First byte of Fname
         LXI   B,8       ; Length of file name
         MVI   A,' '     ; Get a blank
Lp1Fnm:  CMP   M         ; Is next byte a blank
         JZ    Skp2Fnm   ; Not a letter
         DW    LDI       ; Move letter to title
         JPE   Lp1Fnm    ; Loop for up to 8
Skp2Fnm: LXI   H,FCBFtyp ; Point at extension
         XCHG            ; Point at output again
         MVI   M,'.'     ; Punctuate it
         INX   H
         XCHG
         LXI   B,3
         DW    LDIR
         MVI   A,DspFnm+14-0F000h
         SUB   E        ; Prepare to blank fill
         RZ
         XCHG
Lp3Fnm:  MVI   M,Space
         INX   H
         DCR   A
         JNZ   Lp3Fnm
         RET
;
; *************************************************
;
; BCDCon
;
; Convert 16-bit number in HL to a one to five
; digit BCD number in the area pointed to by DE
;
BCDCon:  DW    MVIX,P10Tab ; Point Index at table
         PUSH  D           ; Save output pointer
BCDlp1:  DW    LBIX
         DB    1           ; Offset for LBIX
         DW    LCIX
         DB    0           ; Offset for LCIX
         MOV   A,C         ; Get low byte
         CPI   1           ; Clear carry flag
         JZ    BCDend
         DW    SbcB        ; Subtract from input
         JNC   BCDok       ; Got one in range
         DAD   B           ; Restore it
         DW    INCIX
         DW    INCIX
         JMP   BCDlp1      ; Try next one
;
BCDok:   MVI   A,'1'
         STAX  D           ; Set initial digit
BCDlp2:  DW    SbcB        ; Subtract again
         JC    BCDskp1     ; Went negative
         XCHG
         INR   M           ; Increment the digit
         XCHG
         JMP   BCDlp2
;
BCDskp1: DAD   B           ; Restore it
         INX   D           ; Bump output
         DW    INCIX
         DW    INCIX
         DW    LCIX
         DB    0
         DW    LBIX
         DB    1
         MOV   A,C
         CPI   1           ; Is this the last entry
         JZ    BCDend
         MVI   A,'0'
         STAX  D
         JMP   BCDlp2
;
BCDend:  MOV   A,L
         ORI   '0'
         STAX  D
         INX   D
         XCHG
         POP   B
         DW    SbcB      ; Number filled
         MVI   A,5       ; Number needed
         SUB   L         ; Number left to do
         RZ
         DAD   B         ; Restore pointer
BCDlp3:  MVI   M,' '     ; Clear field
         INX   H
         DCR   A
         JNZ   BCDlp3
         RET
;
P10Tab:  DW    10000,1000,100,10,1
;
;     These variables give the row and column to be
;     used when placing characters on the screen
Width:   DB    128       ; Width of virtual screen
SPos:    DW    0         ; Current screen position
CurCol:  DB    1         ; Current data column
CurLin:  DW    1         ; Current data line
;
UpLft:   LXI   H,0F080h  ; Point at start of screen
         SHLD  Spos 
         RET
;
;  Output a single byte to the screen and then
;  increment the screen position.
;
ChOut:   DW	LIXD,SPos ; Get screen position
	 DW	STAIX	; Store the byte
	 DB	0	; Displacement for STAIX
	 DW	INCIX	; Increment screen position
	 DW	STIX,Spos ; Save position away
         RET
;
; ***********************************************
;
; PrOut
; 
; Transfer byte in A to LST:
;
PrOut:   PUSH  H
         PUSH  D
         PUSH  B
         MOV   E,A      ; Get byte to send
         MVI   C,05     ; Function code
         CALL  BDOS
         POP   B
         POP   D
         POP   H
         RET
;
; *************************************************
;
; MSIn
;
; Read the current file in from the disk. HL points
; to previous byte. BC is maximum size, after
; blank compression. Flag Z says out of room. Flag
; C says Input error. When done, HL must point at 
; the last byte read.
;
MSIn:    PUSH  H
         PUSH  B
         LXI   H,0      ; Clear the fcb
         SHLD  FCBext
         SHLD  FCBext+2
         SHLD  FCBrecs
         SHLD  FCBCur
         LXI   D,FCB    ; Point at FCB
         MVI   C,0Fh    ; Find file
         CALL  BDOS
         CPI   0FFh     ; Was it good for you?
         JZ    MSIerr   ; No, bad file
         LXI   D,080h   ; Point at buffer
         MVI   C,1Ah    ; Set DMA address
         CALL  BDOS
MSIlp1:  LDA   FCBCur   ; Get curent record
         MOV   B,A
         LDA   FCBRecs  ; Is it maximal
         CMP   B
         JNZ   MSIskp1  ; No, go read another one
         CPI   128      ; Was it 128 records
         JNZ   MSInoex  ; No, end of file
         LXI   H,FCBext ; Point at the extent number
         INR   M        ; Increment it
         LXI   H,0      ; Clear the rest of the fcb
         SHLD  FCBext+2
         SHLD  FCBrecs
         SHLD  FCBCur
         LXI   D,FCB    ; Point at FCB
         MVI   C,0Fh    ; Find next extent
         CALL  BDOS
         CPI   0FFh     ; Was there another one?
         JNZ   MSIlp1   ; Yes, go read it
MSInoex: POP   D
         POP   H
         JMP   MSIeof
;
MSIskp1: LXI   D,FCB    ; Point at the FCB
         MVI   C,14h    ; Read sequential
         CALL  BDOS
         ORA   A        ; Any error
         JNZ   MSIerr   ; Yes, complain
         DW    MVIX,80h ; Point at the buffer
         POP   D        ; Get target count
         MVI   B,128    ; Input length
         POP   H        ; Get target address
MSIlp2:  DW    LDAIX    ; Get input byte
         DB    0        ;   (offset for LD A,(IX+n)
         CPI   01Ah     ; Is it EOF?
         JZ    MSIeof   ; Yes, all done
         CPI   00Ah     ; Is it line feed?
         JZ    MSIlf    ; Yes, throw it away
         ANI   07Fh     ; Throw away high bit
         CPI   00Dh     ; Is it CR
         JZ    MSIok    ; Yes, accept it
         CPI   Space    ; Is it a space?
         JNZ   MSIok    ; No, accept it
         DW    BIT7M    ; Is it compressed?
         JNZ   MSIcmp   ; Yes, add in another one
         MVI   A,081h   ; Single compressed blank
         JMP   MSIok
;
MSIcmp:  MVI   A,255
         CMP   M        ; Is it already maximal?
         JZ    MSIlf    ; Yes, TRUNCATE
         INR   M        ; No, bump the count
         JMP   MSIlf    ; Get next byte
;
MSIok:   INX   H        ; Increment output pointer
         MOV   M,A      ; Insert the byte
         DCX   D        ; Reduce target count
         XRA   A        ; Get a zero
         ORA   D
         ORA   E
         JZ    MSIfull  ; Out of space
MSIlf:   DW    INCIX    ; Increment source
         DCR   B        ; Reduce input count
         JNZ   MSIlp2   ; Keep moving
         PUSH  H
         PUSH  D
         JMP   MSIlp1   ; Get next block
;
MSIeof:  PUSH  H        ; Save the pointer
         MVI   C,010h   ; Close file
         LXI   D,FCB
         CALL  BDOS
         POP   H        ; Get next target byte
         MVI   A,CR
         CMP   M        ; Does it end with CR
         JZ    MSIskp2  ; Yes, return
         INX   H
         MOV   M,A      ; Store the CR
MSIskp2: ORI   1        ; Clear flags
         STA   Fileflg  ; Show load OK
         RET
;
MSIerr:  POP   B
         POP   B
         ORI   1        ; Clear flags
         STC            ; Set carry
         RET
;
MSIfull: XRA   A        ; Set Z flag
         RET
;
;  Flag set non-zero when a load has been done
; 
Fileflg: DB    0
;
; ***********************************************
;
; MSOut
;
; Write the storage pointed to by HL for a length
; of BC characters
;
MsOut:   PUSH  H
         DAD   B        ; First byte to not go out
         PUSH  H
         MVI   C,19h    ; Get logged disk 
         CALL  BDOS 
         PUSH  PSW 
         MVI   C,0Dh    ; Reset disks
         CALL  BDOS
         POP   PSW      ; Get logged disk back
         MVI   C,0Eh
         MOV   E,A
         CALL  BDOS
         LDA   FileFlg  ; Get status byte
         ORA   A        ; Is old file to be saved
         JZ    MSOdel   ; No, scratch it
         LXI   H,FCB    ; Make a copy of the FCB
         LXI   D,080h
         LXI   B,16     ; FCB length
         DW    LDIR     ; Copy it
         LXI   B,8+1    ; Copy it again
         LXI   H,FCB
         DW    LDIR
         LXI   B,3
         LXI   H,BAK
         DW    LDIR
         LXI   D,080h+16 ; Point at .BAK name
         MVI   C,13h    ; Delete old .BAK
         CALL  BDOS
         LXI   D,080h
         MVI   C,17h    ; Rename source file
         CALL  BDOS
         MVI   A,0      ; Clear FileFlg
         STA   Fileflg
         JMP   MSOmake
;
MSOdel:  LXI   D,FCB
         MVI   C,13h    ; Delete old file
         CALL  BDOS
;
MSOmake: LXI   D,FCB
         LXI   H,0      ; Clear the FCB
         SHLD  FCBext
         SHLD  FCBext+2
         SHLD  FCBrecs
         SHLD  FCBCur
         MVI   C,16h
         CALL  BDOS     ; Initialize new file
         MVI   C,0      ; Initialize GetNx
         POP   D        ; Get limit address
         POP   H        ; Get starting address
         MVI   B,128    ; Set record length
         DW    MVIX,080H ; Point at output buffer
MSOlp2:  CALL  GetNx
MSOlp3:  DW    STAIX    ; Move it to the output buffer
         DB    0        ;  (offset for STAIX)
         DW    INCIX    ; Increment output pointer
         DCR   B        ; Count the byte
         JNZ   MSOskp1  ; Skip if buffer not full
         STA   SaveA
         PUSH  B
         PUSH  D
         PUSH  H
         LXI   D,FCB
         MVI   C,15h    ; Write the block
         CALL  BDOS
         ORA   A
         POP   H
         POP   D
         POP   B
         LDA   SaveA
         JNZ   MSOfull  ; check for output error
         MVI   B,128
         DW    MVIX,080h 
MSOskp1: CPI   CR       ; Is it CR
         MVI   A,0Ah    ; Add a line feed
         JZ    MSOlp3
         MOV   A,H
         XRA   D        ; Near end yet?
         JNZ   MSOlp2
         MOV   A,L
         XRA   E        ; Check second byte
         JNZ   MSOlp2
         ORA   C        ; Is packed byte empty
         JNZ   MSOlp2
         ORA   B        ; Do I need EOF
         JZ    MSOskp2
         MVI   A,1Ah    ; Set an EOF
         DW    STAIX    ; At end
         DB    0        ;   (offset for STAIX)
MSOskp2: LXI   D,FCB
         MVI   C,15h    ; Write last block
         CALL  BDOS
         ORA   A
         JNZ   MSOfull
MSOcls:  LXI   D,FCB
         MVI   C,10h    ; Close file
         CALL  BDOS
         RET
;
MSOoerr: MVI   A,3
         STA   EdErr
         JMP   MSOcls
;
MSOfull: MVI   A,5
         STA   EdErr
         JMP   MSOcls
;
BAK:     DB    'BAK'
;
; ***********************************************
;
; GetStr
;
; Accept a string from the keyboard and store it
; in the buffer at location 80. Used for getting
; file names, find arguments and such.
;
GetStr:  LXI   H,080h   ; Point at the buffer
Lp1GS:   PUSH  H
         CALL  RptKy    ; Get the next input
         CALL  CASE
         DB    GSLen/3
         DW    Skp1GS   ; Otherwise case
GSstrt:  EQU   $
         DB    19
         DW    GSleft
         DB    8
         DW    GSleft
         DB    CR
         DW    GSCR
GSLen:   EQU   $-GSstrt
;   *** None of them return ***
;
Skp1GS:  POP   H        ; Discard return address
         POP   H        ; Get buffer pointer
         MOV   M,A      ; Store the byte
         INX   H        ; Move along
         CPI   Space    ; Is it control?
         JNC   Skp2GS   ; No, just output it
         ADI   'A'-1    ; Get control byte name
         PUSH  A
         MVI   A,'^'
         CALL  ChOut    ; Display the ^
         POP   A
Skp2GS:  CALL  ChOut    ; Display the byte
         JMP   Lp1GS
;
GSLeft:  POP   H
         POP   H        ; Get buffer pointer
         MVI   A,080h   ; Are we at start
         CMP   L
         JZ    Lp1GS
         DCX   H
         MOV   A,M      ; Get byte being retracted
         XCHG
         LHLD  SPos
         DCX   H
         CPI   Space
         JNC   Skp3GS   ; Is it control?
         DCX   H
Skp3GS:  SHLD  SPos
         MVI   A,Space
         CALL  ChOut
         MVI   A,Space
         CALL  ChOut
         SHLD  SPos
         XCHG
         JMP   Lp1GS
;
GSCR:    POP   H
         POP   H
         MOV   A,L
         SUI   080h     ; Compute input length
         RET
;
SPACE:   EQU   ' '   
CR:      EQU   13
TAB:     EQU   9
CURSR:   DW    0       ; Pointer to cursor position
HORIZ:   DB    1
VERT:    DB    1
HERE:    DW    65535
SaveA:   DB    0
EdErr:   DB    0
;
; *************************************************
;
; StrCr - Fill out with CRs at initialisation
;
StrCr:   LHLD  BegTx
         DCX   H
         MVI   M,CR
         LHLD  BDOSep+1 ; Get BDOS origin
         LDA   Lines    ; Calculate space to leave
         DW    NEG      ; Negate it
         ADI   250      ; 250-lines
         MOV   L,A
         DCR   H        ; Back by 256
         SHLD  AftCu
         DCX   H
         SHLD  EndTx
         LDA   LINES
         MOV   B,A
Lp1st:   INX   H
         MVI   M,CR
         DCR   B
         JNZ   Lp1st
         RET
InitH:   LHLD  EndTx
         INX   H
         SHLD  HERE
         RET
SetBC:   INR   C
         DCR   C
         RZ
         INR   B
         RET
;
;  SubDP:  Double precision subtraction
;          BC = HL - BC + 1
;
SubDP:   PUSH  H        ; Save initial value
         ADI   0        ; Clear carry flag
         DW    SbcB     ; Subtract
         MOV   B,H      ; Get result
         MOV   C,L
         INX   B        ; Adjust count
         POP   H
         RET
;
; ************************************************
;
BgCnt:   LHLD  BegTx    ; Count bytes before cursor
LCnt:    MOV   B,H
         MOV   C,L
         LHLD  BefCu
         JMP   SubDP
NdCnt:   LHLD  EndTx    ; Count bytes after cursor
RCnt:    PUSH  H
         LHLD  AftCu
         MOV   B,H
         MOV   C,L
         POP   H
         JMP   SubDP
;
; *************************************************
;
; GPCnt
;
; This subroutine counts the size of the cursor gap
; to see how many bytes will fit.
;
GPCnt:   LHLD  BefCu
         MOV   B,H
         MOV   C,L
         LHLD  AftCU
         DCX   H
         DCX   H
         JMP   SubDP
;
; *************************************************
;
; MoveL
;
; Move bytes across the cursor gap so that the gap
; moves to the left. HL points to where cursor will
; end up.
;
MoveL:   CALL  LCnt     ; Count bytes to move
         RC
         LHLD  AftCu
         DCX   H
         XCHG
         LHLD  BefCu
         DW    LDDR
         SHLD  BefCu
         XCHG
         INX   H
         SHLD  AftCu
         RET
;
;  MoveR  - Moves the other way
;
MoveR:   CALL  RCnt
         RC
         LHLD  BefCu
         INX   H
         XCHG
         LHLD  AftCu
         DW    LDIR
         SHLD  AftCu
         XCHG
         DCX   H
         SHLD  BefCu
         RET

;
; CrLft - Find CRs to left of cursor for a maximum
;         of E CRs.
;
CrLft:   CALL   BGCnt   ; Count bytes before cursor
         JNC    Skp1LF
         XRA    A
         SUI    1
         RET
Skp1LF:  LHLD   BefCu   ; Point before cursor
         MOV    A,M
         CPI    CR      ; Is cursor on a CR
         JNZ    Skp2LF
         MVI    A,1
         CMP    E
         JNZ    Skp2LF
         STC
         RET
Skp2LF:  MVI    A,CR
Lp3LF:   DW     CPDR    ; Scan for a CR
         JPO    Skp4LF  ; Skip if count exausted
         DCR    E       ; Decrease search count
         JNZ    Lp3LF   ; Keep going
         INX    H       ; Back up to the CR
         INX    H       ; Then in front of it
         XRA    A       ; Set flags off
         RET
Skp4LF:  INX    H       ; Back to first byte
         STC
         CMC            ; Set carry flag
         JZ     Skp5LF  ; Was first byte CR
         DCR    E       ; No, reduce count
         RET
Skp5LF   INX    H       ; Back after CR
         DCR    E       ; Was it the one we wanted?
         RZ
         DCX    H       ; No back in front of it
         DCR    E
         RET
;
;  CrRit - Find up to "E" CRs after the cursor.
;
CrRit:   CALL   NdCnt
         JNC    Skp1Ri
         XRA    A
         SUI    1
         RET
Skp1Ri:  MOV    D,E
         MVI    A,CR
         LHLD   AftCu
Lp2Ri:   DW     CPIR
         JPO    Skp3Ri
         DCR    E
         JNZ    Lp2Ri
         STC
         CMC
         RET
Skp3Ri:  MOV    A,D
         CMP    E
         JNZ    Skp4Ri
         STC
         RET
Skp4Ri:  LHLD   EndTx
         DCX    H
         MVI    A,CR
         LXI    B,65535
         DW     CPDR
         INX    H
         INX    H
         XRA    A
         ADI    1
         RET
;
;  Cursor positioning subroutines
;
TopV:    MVI    A,1
         JMP    Loadv
DecV:    PUSH   H
         LHLD   CurLin
         DCX    H
         SHLD   CurLin
Skp1DV:  POP    H
         LDA    Vert
         CPI    1
         JZ     Loadv
         DCR    A
         JMP    Loadv
IncV:    LDA    Vert
         PUSH   H
         LHLD   CurLin
         INX    H
         SHLD   Curlin
         LXI    H,Lines
         CMP    M
         POP    H
         JZ     Loadv
         INR    A
         JMP    Loadv
BotV:    LDA    Lines
Loadv:   STA    Vert
         RET
LftH:    MVI    A,1
         JMP    Loadh
DecH:    LDA    Horiz
         DCR    A
         RZ
         JMP    Loadh
TabH:    LDA    Horiz
         DCR    A
         ORI    7
         INR    A
         JMP    IncT 
IncH:    LDA    Horiz
IncT:    PUSH   H
         LXI    H,View
         CMP    M
         POP    H
         RNC
         INR    A
         JMP    Loadh
RitH:    LDA    View
Loadh:   STA    Horiz
         RET
;
; *************************************************
;
; Cmprs
;
; This subroutine combines any compressed space
; bytes which are adjacent to one another.
;
Cmprs:   CALL   InitH   ; Show HERE is now invalid
         CALL   BgCnt   ; Count bytes to left
         JC     Skp7Cm  ; Jump if none
         LHLD   BegTx   ; Point to start of text
         MOV    D,H
         MOV    E,L     ; Put it in DE
         CALL   SetBC   ; Use BC as two counters
Lp1Cm:   MOV    A,M     ; Get a data byte
         STAX   D       ; Copy it
Lp2Cm:   INX    H       ; Increment input pointer
         DCR    C       ; Count how many
         JNZ    Skp3Cm
         DCR    B
         JZ     Skp6Cm
Skp3Cm:  DW     Bit7A   ; Was it compressed space?
         JZ     Skp5Cm  ; No, all done
         DW     Bit7M   ; Is next byte compressed?
         JZ     Skp5Cm  ; No, no squeezing needed
         ADD    M       ; Yes, add the counts
         DW     Bit7A   ; Did they carry into bit7
         JZ     Skp4Cm  ; No, still short enough
         MVI    A,127   ; Yes, ARBITRARY TRUNCATION
Skp4Cm:  ORI    080h    ; Flag as compressed space
         STAX   D       ; Store it away
         JMP    Lp2Cm   ; Go look again
Skp5Cm:  INX    D       ; Increment output
         JMP    Lp1Cm   ; Go look at next
Skp6Cm:  XCHG           ; Save output pointer
         SHLD   BefCu   ;   in BefCu
Skp7Cm:  CALL   NdCnt   ; How many after cursor?
         RC
         LHLD   EndTx   ; Get last byte
         MOV    D,H     ; put it in DE
         MOV    E,L
         CALL   SetBc   ; Set up two byte counter
Lp8Cm:   MOV    A,M     ; Get input byte
         STAX   D       ; Store in output
Lp9Cm:   DCX    H       ; Step input address
         DCR    C       ; Reduce input count
         JNZ    Skp10C
         DCR    B
         JZ     Skp13C
Skp10C:  DW     Bit7A   ; Is it a compressed space
         JZ     Skp12C  ; No, move along
         DW     Bit7M   ; Is next one compressed?
         JZ     Skp12C  ; No, no squeeze needed
         ADD    M       ; Find new length
         DW     Bit7A   ; Did it overflow?
         JZ     Skp11C
         MVI    A,127   ; Yes, ARBITRARY TRUNCATION
Skp11C:  ORI    080h    ; Turn on compression flag
         STAX   D       ; Store output byte
         JMP    Lp9Cm   ; Repeat loop
Skp12C:  DCX    D       ; Decrement input pointer
         JMP    Lp8Cm   ; Repeat loop
Skp13C:  XCHG           ; Get output pointer
         SHLD   AftCu   ; Save as AftCu
         RET            ; Return to caller
;
; **********************************************
;
; SpaceQ
;
; Make sure that there is room for one more character
; of text. If not, set EdErr. If yes, set BC to how
; many characters will fit in the gap.
;
SpaceQ:  MOV   L,A     ; Save the A-Reg
         PUSH  H
         CALL  GpCnt   ; Count the GAP size
         JNC   Skp1Sp  ; Nothing in the gap??
         CALL  Cmprs   ; Look for adjacent blanks
         CALL  GpCnt   ; Check the gap again
         JNC   Skp1Sp  ; Room now?
         MVI   A,1     ; Set A to 1
         STA   EdErr   ; Set out of space err
Skp1Sp   POP   H       ; Restore HL
         MOV   A,L     ; Restore A
         RET
;
;*************************************************
;
; Insrt
;
; This subroutine places the byte in the A register
; to the left of the cursor.
;
Insrt:   CALL  SpaceQ  ; Check for room
         RC
         LHLD  BefCu   ; Get first part
         INX   H       ; Bump pointer
         MOV   M,A     ; Store the byte
         SHLD  BefCu   ; Store the pointer
         ORA   A       ; Clear any carry flag
         RET
;
; *************************************************
;
; Top
;
; Move the cursor to the beginning of the text
;
Top:     LHLD  BegTx    ; Point at start
         CALL  MoveL    ; Move there
         CALL  TopV     ; Adjust cursor
         CALL  LftH
         LXI   H,1
         SHLD  CurLin
         RET
;
; *************************************************
;
; Bottom
;
; Move the cursor to the last character of text
;
Bottom:  LHLD  EndTx     ; Point at end
         CALL  MoveR     ; Move there
         CALL  BotV      ; Adjust cursor
         CALL  RitH
CountEm: CALL  BgCnt     ; Find total bytes
         LXI   D,1
         JC    Skp1BOT   ; File is empty
         MVI   A,CR
         LHLD  BefCu
Lp1BOT:  DW    CPDR
         JNZ   Skp1BOT
         INX   D
         JPE   Lp1BOT
Skp1BOT: XCHG
         SHLD  CurLin
         RET
;
; *************************************************
;
; Up
;
; Move cursor to previous line start.
;
Up:      MVI   E,1      ; Number of CRs to find
         CALL  CrLft    ; Go look
         JNC   Skp1Up   ; Not a start of line
         RNZ            ; Return if at top
         MVI   E,2      ; Go back to prev line
         CALL  CrLft
         CALL  DecV     ; Move cursor up
Skp1Up:  CALL  LftH     ; Move cursor to front
         CALL  MoveL    ; Move the bytes across
         RET
;
; *************************************************
;
; Down
;
; Move to next line.
;
Down:    MVI   E,1      ; Number of CRs to find
         CALL  CrRit    ; Result in HL
         DCX   H        ; Reduce it
         JC    Skp1Dn   ; No more CRs
         CALL  IncV     ; Move cursor down
         CALL  LftH     ; Start of line
         JMP   MoveR    ; Move the bytes across
;
Skp1Dn:  RNZ            ; Already at bottom
         LHLD  EndTx    ; Get end
         CALL  RitH     ; Set Horiz=View
         MOV   A,M      ; Get that last byte
         CPI   CR       ; Is it A CR?
         JNZ   MoveR    ; No, curor ok
         CALL  IncV     ; Move cursor down
         JMP   MoveR    ; Move bytes across the gap
;
; ************************************************
;
; Left
;
; Move the cursor one to the left, splitting and
; recombining compressed spaces as necessary

Left:    CALL  SpaceQ   ; Any space left?
         RC
         CALL  BgCnt    ; Are we at front?
         RC
         LHLD  BefCu    ; Point at byte before cursor
         MOV   A,M      ; Pick it up
         DW    BIT7A    ; Is it compressed space?
         JZ    Skp1Lt   ; No, just move it
         DCR   M        ; Reduce number befor Cursor
         CPI   1+128    ; Was it a single blank?
         MVI   A,1+128  ; Set a single blank
         JNZ   Skp2Lt   ; Insert this one after
Skp1Lt:  DCX   H        ; Back the pointer up
         SHLD  BefCu    ; Store it away
Skp2Lt:  LHLD  AftCu    ; Point after cursor
         CPI   1+128    ; Inserting a space
         JNZ   Skp3Lt   ; No, go put the byte in
         DW    BIT7M    ; Is byte a space byte?
         JZ    Skp3Lt   ; No insert the space
         MOV   A,M      ; Get the byte
         CPI   255      ; Is it maximal?
         JZ    DecH     ; Yes, can't increment it
         INR   M        ; Bump the byte
         JMP   DecH     ; Move the cursor
;
Skp3Lt:  DCX   H        ; Back the pointer up
         MOV   M,A      ; Store the byte
         SHLD  AftCu    ; Update the pointer
	 CPI   Tab	; Was a Tab moved
	 JZ    RitH	; Yes, adjust cursor
         CPI   CR       ; Was a CR moved?
         JNZ   DecH     ; No, go move cursor
         CALL  RitH
         CALL  DecV     ; Adjust cursor position
         RET 
;
; ************************************************
;
; Right
;
; Move the cursor one to the right
;
Right:   CALL  SpaceQ   ; Any room left?
         RC
         CALL  NdCnt    ; Already at end?
         RC
         LHLD  AftCu    ; Point at next byte
         MOV   A,M      ; Get the byte
         DW    BIT7A    ; Is it compressed space
         JZ    Skp1Rt   ; No, ordinary byte
         DCR   M        ; Reduce count in memory
         CPI   1+128    ; Is it exhausted
         MVI   A,1+128  ; Yes, put one space in A
         JNZ   Skp2Rt   ; Still some left
Skp1Rt:  INX   H        ; Move pointer up
         SHLD  AftCu    ; Save it away
Skp2Rt:  LHLD  BefCu
         CPI   1+128    ; Inserting a space?
         JNZ   Skp3Rt
         DW    BIT7M    ; Is insertion point a space?
         JZ    Skp3Rt
         MOV   A,M
         CPI   255      ; Is it already maximal?
         RZ             ; Yes, ARBITRARY TRUNCATION
         INR   M        ; Add another space
         JMP   IncH     ; Bump the cursor over
;
Skp3Rt:  INX   H        ; Point at the byte
         MOV   M,A
         SHLD  BefCu
         CPI   Tab      ; Is it a Tab?
         JZ    TabH
         CPI   CR       ; Was it a carriage return?
         JNZ   IncH     ; No, just move cursor
         CALL  IncV
         JMP   LftH     ; adjust cursor position
;         
; ************************************************
;
; PageF
;
; Move the cursor ahead one page.
;
PageF:   LDA   Lines    ; Get number of lines
         INR   A        ; Lines + 1
         LXI   H,Vert   ; Point at cursor line #
         SUB   M        ; Lines - Vert + 1
         MOV   E,A
         MVI   D,0
         LHLD  CurLin
         DAD   D
         SHLD  CurLin
         CALL  CrRit    ; Point the many CRs down
         DCX   H        ; Back off one byte
         JC    Bottom
         JNZ   Bottom
         CALL  MoveR    ; Move the cursor gap
         CALL  TopV     ; Adjust the cursor
         CALL  LftH
         RET
;
; PageB
;
; Move the cursor back one page
;
PageB:   LDA   Vert     ; Get cursor line #
         LXI   H,Lines
         ADD   M        ; A = Vert + Lines
         MOV   E,A
         MVI   D,0
         LHLD  CurLin
         ORA   A        ; Clear carry flag
         DW    SbcD     ; Double subtract
         INX   H        ; Adjust for one origin
         SHLD  Curlin
         CALL  CrLft    ; Point that many CRs back
         JC    Top
         JNZ   Top
         CALL  MoveL    ; Move the cursor gap
         CALL  TopV     ; Adjust the cursor
         CALL  LftH
         RET
;
; ************************************************
;
; Find  / RepFind
;
; This subroutine positions the cursor on the next
; occurance of a given string.
;
FindStr: DB    0
         DS    127
;
Find:    CALL  Dspl
         DB    'String: ',0
         CALL  GetStr   ; Put the string in 80
         RZ             ; Ignore if null
         STA   FindStr
         LXI   D,FindStr+1
         MOV   C,A
         MVI   B,0
         LXI   H,080h
         DW    LDIR     ; Move the string in
;
RepFind: LDA   FindStr  ; Get the length
         ORA   A
         JZ    FindErr  ; No string specified
         CALL  NdCnt    ; Get number to scan
         JC    NotFnd   ; At end of file
         LXI   H,FindStr
         INX   B
         MOV   A,C
         SUB   M
         MOV   C,A
         JNC   Skp1Fi
         DCR   B
         JM    NotFnd   ; Too long to be found
Skp1Fi:  LHLD  AftCu    ; Point at trailing bytes
         INX   H        ; Start at next byte
Lp1Fi:   LDA   FindStr+1 ;Get first byte
         CPI   Space    ; Is it a blank?
         JZ    Skp2Fi   ; Yes, (Groan)
         DW    CPIR     ; Search for it
         JNZ   NotFnd
         PUSH  B
         MVI   C,0      ; Set up for GetNxt
         CALL  FindChk  ; Is it a hit?
         POP   B        ; Restore count
         JZ    Found
         MOV   A,B
         ORA   C
         JZ    NotFnd
         JMP   Lp1Fi
;
Skp2Fi:  MVI   C,0
Lp2Fi:   CALL  GetNx
         CPI   Space
         JNZ   Skp3Fi
         CALL  FindChk
         JZ    Found
Skp3Fi:  LDA   EndTx
         XRA   L
         JNZ   Lp2Fi
         LDA   EndTx+1
         XRA   H
         JNZ   Lp2Fi
NotFnd:  MVI   A,4
         STA   EdErr
         RET
;
Found:   DCX   H
         DCX   H
         CALL  MoveR    ; Move data past the cursor
         CALL  TopV     ; Set the line on top
         CALL  RitH     ; Adjust cursor
         CALL  IncV     ; Down one line
         JMP   CountEm  ; Adjust line number
;
FindErr: MVI   A,2
         STA   EdErr
         RET
;
FindChk: LXI   D,FindStr+2
         LDA   FindStr
         DCR   A
         RZ
         PUSH  B
         PUSH  H
Lp1FC:   MOV   B,A
         CALL  GetNx
         XCHG
         CMP   M
         MOV   A,B
         JNZ   FCRet
         XCHG
         INX   D
         DCR   A
         JNZ   Lp1FC
FCRet:   POP   H
         POP   B
         RET
;
; ************************************************
;
; IChar  / CtlP
;
; This subroutine inserts a character to the left
; of the cursor. If invalid, set EdErr=2.
; The CtlP entry stores the next character as is.
;
CtlP:    CALL  RptKy   ; Get a character
         JMP   Skp2IC
;
IChar:   CPI   SPACE   ; Is it a control code?
         JC    Skp1IC  ; Yes, less than a blank
         CPI   127     ; Is it DEL?
         JNZ   Skp2IC  ; No, go process it
Skp1IC:  MVI   A,2     ; Get error code
         STA   EdErr   ; Set the error byte
         RET
;
Skp2IC:  CALL  Insrt   ; Put the byte in
         RC            ; Return if full
         CALL  IncH    ; Move cursor
         JMP   ChkIns  ; See if insert is on
;
; ***********************************************
;
; ITab  - Insert a tab
;
ITab:    CALL  Insrt
         RC
         CALL  TabH    ; Adjust screen position
         JMP   ChkIns
;
; ************************************************
;
; ISpac
;
; Insert a space to the left of the cursor
;
ISpac:   CALL  BgCnt    ; Check for byte to left
         JC    Skp1IS   ; No, can't be a blank
         LHLD  BefCu    ; Yes, point at it
         DW    Bit7M    ; Is it a blank?
         JZ    Skp1IS
         MOV   A,M      ; Get the current count
         CPI   255      ; Is it maximal?
         RZ             ; Yes, ARBITRARY TRUNCATION
         INR   M        ; Bump the count
         JMP   Skp2IS   ; Go bump the cursor
;
Skp1IS:  MVI   A,1+128  ; Set code for one space
         CALL  Insrt    ; Put it in the text
         RC      
Skp2IS:  CALL  IncH     ; Go increment the cursor
ChkIns:  LXI   H,InsFlag ; Point at insert flag
         DW    BIT7M    ; Is it on
         RNZ            ; Yes, all done
         LHLD  AftCu    ; No, Look at the character
         MVI   A,CR
         CMP   M        ; Is it a CR?
         RZ             ; Yes, leave it
         JMP   EChar    ; No, delete it
;
; ***********************************************
;
; IToggl
;
; This subroutine toggles insert on and off
;
InsFlag: DB    00h      ; Insert starts out off
;
IToggl:  LXI   H,InsFlag ; Point at the flag
         DW    BIT7M    ; Is it on
         JZ    ITon     ; No, turn it on
         MVI   M,0      ; Yes, turn it off
         LXI   H,ITCoff
         JMP   Skp1Tg
ITon:    MVI   M,080h
         LXI   H,ITCon
Skp1Tg:  LXI   D,DspIns ; Point at heading message
         LXI   B,3
         DW    LDIR     ; Change it
         RET
;
ITCon:   DB    'on '
ITCoff:  DB    'off'
;
; ***********************************************
;
; ICR
;
; Insert a carriage return
;
ICR:     CALL  NdCnt    ; Are we at end?
         JC    ICRA     ; Yes, add a new line
         LDA   InsFlag
         DW    BIT7A    ; Is insert flag on?
         JZ    Down     ; No, just move cursor
ICRA:    MVI   A,CR     ; Get a CR
         CALL  Insrt    ; Put it in
         RC
         CALL  IncV     ; Move cursor down
         CALL  LftH     ; Move to start of line
         RET
;
; ***********************************************
;
; Del
;
; Erase the character to the left of the cursor
Del:     CALL Left      ; Move cursor
         RC             ; No delete at start
;
; ***********************************************
;
; EChar
;
; Erase the character to the right of the cursor
;
EChar:   CALL  NdCnt    ; How many are to right?
         RC             ; Nothing to erase
         LHLD  AftCu    ; Point at byte
         DW    BIT7M    ; Is it compressed space
         JZ    Skp1EC   ; No, ordinary byte
         DCR   M        ; Reduce space count
         MVI   A,128    ; Check for empty
         CMP   M
         RNZ            ; Done if still some blanks
Skp1EC   INX   H        ; Move up, past character
         SHLD  AftCu    ; Store updated value
         RET
;
; *************************************************
;
; ELine
;
; This subroutine erases from the cursor to the end
; of the line. If the cursor is at the beginning of
; the line, the whole line is deleted.
;
ELine:   MVI   E,1      ; # of CRs to be found
         CALL  CrRit    ; Find start of next line
         JNC   Skp1EL   ; If a line was found, skip
         RNZ            ; If at end of text, return
         LHLD  EndTx    ; Cursor is in last line
         MOV   A,M
         CPI   CR       ; Is last byte a CR?
         INX   H
         JNZ   Skp2EL   ; No
Skp1EL:  DCX   H        ; Point at trailing CR
         PUSH  H
         LHLD  BefCu    ; Point in front of cursor
         MOV   A,M
         POP   H        ; Restore pointer
         CPI   CR       ; Was cursor at col 1
         JNZ   Skp2EL   ; No, partial erase
         INX   H        ; Yes, delete CR also
Skp2EL:  SHLD  AftCu    ; Delete the line
         RET
;
; *************************************************
;
; Point
;
; Store this cursor location in HERE
;
Point:   CALL  Cmprs
         LHLD  BefCu    ; Get address of byte before
         INX   H        ; Get future address
         SHLD  Here
         RET
;
; **************************************************
;
; EPart
;
; Erase a portion of the text (from HERE to current
; cursor position).
;
EPart:   LHLD  Here     ; Get saved pointer
         CALL  LCnt     ; Be sure its right
         JNC   Skp1EP   ; Positive count is good
         MVI   A,2
         STA   EdErr    ; Give mesage 2
         RET
;
Skp1EP:  LHLD  Here
         DCX   H
         SHLD  BefCu    ; Delete back to HERE
         CALL  RitH     ; Adjust cursor
         JMP   CountEm
;
; *************************************************
;
HPos:    DB    0        ; Room left on this line
;
; DsByt
;
; Display one byte on the screen for messages rather
; than text.
;
DsByt:   CPI   CR       ; Is it a CR
         JZ    Skp1DB   ; Yes, skip
         DW    BIT7A    ; Check for compressed space
         JNZ   Skp3DB
         CALL  ChOut    ; Put it on the screen
         LDA   HPos     ; Get cursor position
         DCR   A
         STA   HPos     ; Put decremented value back
         RNZ
Skp2DB:  LDA   Width    ; It went to zero,
         STA   HPos     ; Set it back to width
         RET
;
Skp1DB:  LDA   HPos     ; Get room left
         MOV   B,A
Lp2DB:   MVI   A,Space
         CALL  ChOut    ; Put a blank on the screen
         DCR   B        ; Fill out the line
         JNZ   Lp2DB
         JMP   Skp2DB   ; Go reset HPos
;
Skp3DB:  ANI   07Fh     ; Turn off the flag
         MOV   B,A      ; Put number of blanks in B
         MOV   C,A
Lp4DB:   MVI   A,Space
         CALL  ChOut    ; Put a space on the screen
         DCR   B
         JNZ   Lp4DB    ; Count out how many
         LDA   HPos
         SUB   C        ; Reduce space left
         JZ    Skp5DB   ; Exactly full
         JNC   Skp6DB   ; Some left
Skp5DB:  MOV   C,A      ; Save difference
         LDA   Width
         ADD   C        ; Get what's on next line
Skp6DB:  STA   HPos
         RET
;
; *************************************************
;
; ClScr
;
; Clear the screen in anticipation of messages.
;
ClScr:   CALL  UpLft    ; Reset Cursor
         LDA   Width
         STA   HPos     ; Show empty line
         LDA   Lines    ; Output this many CRs
         MOV   D,A
Lp1CS:   MVI   A,CR
         CALL  DsByt    ; Clear one line
         DCR   D
         JNZ   Lp1Cs    ; Do the whole screen
         CALL  UpLft    ; Back to the top
         LDA   Width    ; Reset length
         STA   HPos
         RET
;
; *************************************************
;
; Dspla
;
; Display the text pointed to by HL. 00h is end.
;
Dspla:   MOV   A,M       ; Get the first byte
         INX   H         ; Increment
         ORA   A         ; Is it the end?
         RZ              ; Yes, return
         CALL  DsByt     ; Put it out
         JMP   Dspla     ; Do next one
;
; *************************************************
;
; Dspl
;
; Display the text which immediatly follows the CALL
;
Dspl:    POP   H        ; Get data location
         CALL  Dspla    ; Display it
         PCHL           ; Return to after text
;
; *************************************************
;
; GetNx
;
; This subroutine gets the next ASCII character (or
; CR) from memory.  HL and C registers are used for
; place keeping across repeated calls.
;
GetNx:   XRA    A
         CMP    C        ; Is this a continuation?
         JNZ    Skp1Gt
         MOV    A,M      ; Get next byte
         INX    H 
         DW     Bit7A
         JZ     Skp2Gt   ; Is it a compressed byte
         ANI    07Fh     ; Yes, turn off the flag
         MOV    C,A      ; Save number of spaces
Skp1Gt:  DCR    C        ; Reduce space count
         MVI    A,SPACE  ; Get a space
Skp2Gt:  CPI    CR       ; Set Z flag if CR
         RET
;
; *********************************************
;
; DsChr
;
; This subroutine displays a string on the screen.
;  INPUT: C = # of spaces to skip first
;         B = # of characters (unless CR first)
;         HL= First byte of string
;
DsChr:   MOV    A,C      ; Get the skip count
         ORA    A        ; Is there one?
         JZ     Skp2Dc
	 PUSH   B	 ; Save input count
	 MOV    B,C	; Get skip count
	 MVI	C,0	; Clear blank count
Lp1Dc:   CALL   GetNx
	 JZ	Skp1Dc	; Hit a CR
	 CPI	Tab	; Is it a Tab
	 CZ	Skp3Dc	; Yes, make adjustments
	 DCR	B	; Reduce the count
	 JNZ	Lp1Dc	; Skip al the bytes
	 MOV    A,C	; Save blank count
	 POP	B	; Get character count back
	 MOV    C,A	; Restore count
Skp2Dc:	 DW	LIXD,Spos ; Get screen position
Lp2Dc:   CALL   GetNx    ; Get data byte
         JZ	End2Dc	 ; Stop if CR
         CPI    Tab      ; Is it a tab
         CZ     Skp4Dc
	 DW	STAIX
	 DB	0	; Displacement for STAIX
	 DW	INCIX
         DCR    B        ; Reduce the count
         JNZ    Lp2Dc    ; Do next byte
End2Dc:	 DW	STIX,Spos ; Put screen position back
         RET
;
Skp1Dc:	 POP	B	; Restore count
	 RET
;
Skp3Dc:  LDA	Nskip	; Get original length
	 SUB	B	; How many have gone by?
	 JMP	Skp5Dc
;
Skp4Dc:  LDA    NSkip
         MOV    C,A      ; Get screen offset
	 DW	STIX,Spos
         LDA    Spos     ; Get low byte of position
         ADD    C        ; Get actual column
Skp5Dc:  ANI    7        ; Check column
         XRI    7        ; Invert it
         MOV    C,A      ; Set the blank count
	 MVI    A,Space	 ; Get the first one
	 RET
;
;    NSkip stores the screen offset of the display
NSkip:   DB    0
;
; *******************************************
;
; DsLns & DsLin
;
; This subroutine displays E bytes starting at (HL)
; DsLin accepts a skip count in C, and an available
; position count in B
;
DsLns:   LDA   Width    ; Get screen width
         MOV   B,A
         LDA   NSkip
         MOV   C,A
;
;     Display one line
;
DsLin:   CALL  DsChr
         MOV   A,B        ; Is count exhausted?
         ORA   A
         JZ    Skp2Dl
Lp1Dl:   MVI   A,SPACE    ; Fill the line with blanks
         CALL  ChOut      ; Send a blank
         DCR   B          ; Reduce count
         JNZ   Lp1Dl
Skp2Dl:  DCX   H          ; Find start of next line
         LXI   B,0FFFFh   ; Set BC maximal
         MVI   A,CR
         DW    CPIR
         DCR   E          ; Decrement line counter
         JNZ   DsLns      ; Keep going
         RET
;
; *******************************************
;
; DspTx
;
; This subroutine displays the text on the screen,
; placing the cursor as indicated by VERT & HORIZ.
; The position of the cursor within the text is
; indicated by BefCu and AftCu.
;
FrstC:   DW    0     ; Save address of top of screen
;
;  First make sure that there are enough lines and
;  characters to allow the current setting of VERT &
;  HORIZ. If not, adjust them to be within the text.
;
DspTx:   LDA   VERT
         MOV   E,A    ; Put VERT in E
         CALL  CrLft  ; Find start of line
         SHLD  FrstC
         JNC   Skp1Dp ; Was cursor valid?
         CALL  TopV   ; No, set it to 1
         JMP   Skp2Dp 
Skp1Dp:  LDA   VERT   ; Get cursor position again
         SUB   E      ; Find where line starts
         STA   VERT
Skp2Dp:  MVI   E,1    ; Number of CRs to find
         CALL  CrLft 
         JNC   Skp3Dp ; Was cursor valid?
         CALL  LftH   ; No, set HORIZ=1
         XRA   A
         STA   NSkip  ; Clear NSkip
         INR   A      ; Set column number
         STA   CurCol
         JMP   Skp6Dp
;
;     Use GetNx to count characters from beginning
;     of line to cursor. HL still points to start.
Skp3Dp:  MVI   C,0
         MVI   B,0
Lp4Dp:   CALL  GetNx   ; Uncompress next char
         CPI   Tab     ; Is it a tab
         JNZ   Skp4Dp  ; No, nothing needed
         MOV   A,B
         ORI   7       ; Round up
         MOV   B,A     ; Put it back
Skp4Dp:  INR   B       ; Count it
         XRA   A
         CMP   C       ; Is C = 0?
         JNZ   Lp4Dp   ; No, get another
         PUSH  H
         PUSH  B
         CALL  LCnt    ; Compare HL to (BefCu)
         POP   B
         POP   H
         JNC   Lp4Dp   ; Keep looking for cursor
;
;     Compute cursor offset in line.
         LDA   Horiz
         MOV   C,A
         INR   B
         MOV   A,B
         STA   CurCol  ; Save column number
         SUB   C
         JC    Skp5Dp  ; Does it fit?
         STA   NSkip   ; Yes, use it
         JMP   Skp6Dp
Skp5Dp:  MOV   A,B     ; Get character count +1
         STA   Horiz
         XRA   A
         STA   NSkip   ; Clear NSkip
;
; Now display the text
Skp6Dp:  CALL  UpLft   ; Start in home position
         LHLD  FrstC   ; Get starting byte
         LDA   Vert    ; Get cursor line
         DCR   A
         JZ    Skp7Dp  ; Skip if it was 1
         MOV   E,A     ; Get the line count
         CALL  DsLns   ; Go display them
Skp7Dp:  LDA   Horiz
         DCR   A
         JZ    Skp8Dp  ; Was column 0?
         MOV   B,A
         LDA   NSkip   ; Get screen offset
         MOV   C,A
         CALL  DsChr   ; Go show next character
Skp8Dp:  LHLD  Spos    ; Point at Next screen byte
         SHLD  Cursr   ; Save it
         LDA   Width
         LXI   H,Horiz ; Point at Horiz
         INR   A
         SUB   M
         JZ    Skp9Dp  ; End of line?
         MOV   B,A     ; Get remaining characters
         MVI   C,0     ; No skip
         MVI   E,1
         LHLD  AftCu   ; Point at trailing text
         CALL  DsLin   ; Display the data
         JMP   Skp10D
;
;   Fill with CRs at end of text
Skp9Dp:  MVI   E,1
         CALL  CrRit   ; Look for CR
         JNC   Skp10D  ; Not found
         LHLD  EndTx
         INX   H
Skp10D:  LDA   Vert
         MOV   E,A
         LDA   Lines
         SUB   E       ; Lines-Vert
         JZ    Skp11D
         MOV   E,A     ; Lines to clear
         CALL  DsLns
Skp11D:  LXI   D,DspLin ; Point into header
         LHLD  CurLin  ; Get the line number
         CALL  BCDCon
         LXI   D,DspCol ; Update header
         LDA   CurCol
         MOV   L,A
         MVI   H,0
         CALL  BCDCon  ; Show it in decimal
         LHLD  Cursr   ; Point at cursor byte
         MVI   A,128   ; Get a cursor bit
         ORA   M       ; Or in the data
         MOV   M,A     ; Store it back
         RET
;
; ************************************************
;
; Exit / Quit
;
; Return to CP/M ... With or without saving
;
Exit:    CALL  SAVE     ; Save the file
         LDA   EdErr    ; Was it ok?
         ORA   A
         RNZ            ; No, don't quit
Quit:    MVI   C,02     ; Console output
         MVI   E,01Ah   ; Clear screen
         CALL  BDOS
         MVI   C,0      ; Restart
         JMP   0        ; All done
;
; *************************************************
;
; Load
;
; This subroutine reads text from the disk into 
; memory at the location specified by the cursor.
;
Load:    CALL  Cmprs    ; Get rid of adjacent blanks
         CALL  GpCnt    ; Find the amount of storage
         JC    Skp1Ld   ; No room?
         LHLD  BefCu    ; Show starting point
         CALL  MSIn     ; Read the text in
         JNZ   Skp2Ld   ; Jump if it worked
Skp1Ld:  MVI   A,1
         STA   EdErr    ; Set out of room flag
         RET
;
Skp2Ld:  JNC   Skp3Ld   ; Jump if no I/O error
         MVI   A,3
         STA   EdErr    ; Set I/O error flag
         RET
;
Skp3Ld:  XCHG
         LHLD  BefCu    ; Save original BefCu
         XCHG
         SHLD  BefCu    ; Set new value
         XCHG
         INX   H        ; Point at first byte loaded
         CALL  MoveL    ; Move the cursor
         JMP   CountEm  ; Update top line
;
; **************************************************
;
; Save
;
; Write the whole file out to the disk.
;
Save:    CALL  Top      ; Start at top of file
         CALL  Cmprs
         CALL  NdCnt    ; Count number of bytes
         RC
         LHLD  AftCu    ; Point at first byte
         JMP   MSOut    ; Write it out
;
; **********************************************
;
; Write
;
; This subroutine writes a portion of the text out
; to the current file.
;
Write:   LHLD  Here     ; Get starting point
         CALL  LCnt     ; Count the bytes
         LHLD  Here     ; Get the start again
         JNC   MsOut    ; Continue if positive
         MVI   A,2      ; Complain
         STA   EdErr
         RET
;
; ************************************************
;
; NewName
;
; Accept a new file name to be used for Load/Save.
;
NewName: CALL  Dspl
         DB    'File name (end with RETURN): ',0
         CALL  GetStr   ; Ask for input
         RZ             ; Return if no input
         CPI   15       ; Check length
         JNC   NNerr
         ADI   080h     ; Point past name
         MOV   L,A
         MVI   H,0
         MVI   A,08Fh
         SUB   L
Lp1NN:   MVI   M,Space  ; Pad it out with blanks
         INX   H
         DCR   A
         JNZ   Lp1NN
         LXI   H,090h   ; Point at build area
         MVI   M,0      ; Clear drive number
         INX   H
         MVI   M,' '    ; Blank out the name
         LXI   D,092h
         LXI   B,10     ; Number of blanks needed
         DW    LDIR     ; Propagate them
         LXI   D,091h   ; Point at name again
         LXI   H,080h   ; Point at input
         LDA   081h     ; See if drive specified
         CPI   ':'
         JNZ   Lp2NN
         LDA   080h
         SUI   'A'
         JC    NNerr
         CPI   16       ; See if it's in range
         JC    Skp1NN
         SUI   'a'-'A'  ; Try upper case
         JC    NNerr
         CPI   16
         JNC   NNerr
Skp1NN:  INR   A
         STA   090h     ; Set drive
         LXI   H,082h   ; Point after drive
Lp2NN:   MOV   A,M      ; Get next byte
         CPI   '.'      ; Is it a dot
         JZ    NNDot
         CPI   Space
         JZ    NNSpace
         CPI   'a'      ; Check for lower case
         JC    Skp2NN
         SUI   'a'-'A'  ; Upcase it
Skp2NN:  CPI   '!'
         JC    NNerr    ; Space or CTRL
         CPI   '9'+1
         JC    Skp3NN   ; Numeric is OK
         CPI   'A'
         JC    NNerr
         CPI   'Z'+1
         JNC   NNerr
Skp3NN:  STAX  D        ; Put it in the buffer
         MVI   A,09Ch   ; Validate length
         CMP   E
         JZ    NNerr
         INX   D
         INX   H
         JMP   Lp2NN
;
NNDot:   MVI   A,099h   ; See if second dot
         CMP   E
         JC    NNerr
         MVI   E,099h   ; Move to extension
         INX   H
         JMP   Lp2NN
;
NNSpace: LXI   D,FCB
         LXI   H,090h   ; Copy the build area
         LXI   B,12
         DW    LDIR
         XRA   A
         STA   FileFlg
         CALL  SetFnm
         RET
;
NNerr:   MVI   A,2
         STA   EdErr
         RET
;
; ************************************************
;
; Print
;
; Output the text to the printer. Upon return, the
; cursor is at the top
;
Print:   CALL  Top
         CALL  Cmprs
         CALL  Dspl
         DB    'Printer Setup codes '
         DB    '(then RETURN): ',0
         CALL  Getstr
         JZ    Skp0Pr   ; Skip if none
         MOV   C,A      ; Get the count
         LXI   H,080h   ; Point at setup
Lp0Pr:   MOV   A,M      ; Get a byte
         CALL  PrOut    ; Put it out
         INX   H
         DCR   C
         JNZ   Lp0Pr    ; Put them all out
Skp0Pr:  CALL  NdCnt    ; Count the bytes
         RC
         LHLD  AftCu    ; Point at first one
         MVI   C,0      ; Initialize GetNx
Lp1Pr:   CALL  GetNx    ; Get a character
         JNZ   Skp2Pr   ; Is it a CR
         CALL  PrOut    ; Yes, output it
         MVI   A,0Ah    ; Add a line feed
Skp2Pr:  CALL  PrOut    ; Output the text
         XRA   A        ; Get a zero
         CMP   C        ; Is it compressed space
         JNZ   Lp1Pr    ; Yes keep doing them
         XCHG
         LHLD  EndTx    ; Get end pointer
         XCHG
         MOV   A,E
         SUB   L        ; Subtract low bytes
         MOV   A,D
         SBB   H        ; Subtract high bytes
         JNC   Lp1Pr    ; Loop if more to go
         RET
;
; *********************************************
;
;  RptKy
;
; Get the next key stroke from CP/M
;
RptKy:   MVI   C,6
         MVI   E,0FFh  ; Request status
         CALL  BDOS
         ORA   A       ; Was a character given?
         JZ    RptKy   ; No, keep looking
         RET
;
; ***********************************************
;
; Case
;
; This subroutine is used to select a subroutine
; according to the contents of the A register.
;
;  Calling sequence:
;     CALL Case
;     DB   # of entries in list
;     DW   Default subroutine if no match
;     DB   First A-reg value to match
;     DW   Subroutine to call if match
;     .
;     .      (required number of DB/DW pairs
;     .
;   <return point from subroutines>
;
Case:    POP   H         ; Get beginning of list
         MOV   B,M       ; Get the count
         INX   H         ; Point at default
         MOV   E,M       ; Get first byte
         INX   H
         MOV   D,M       ; Get second byte
         INX   H         ; Move to first list entry
Lp1Ca:   CMP   M         ; Did it match?
         INX   H         ; Point at subroutine
         JNZ   Skp2Ca    ; No match here
         MOV   E,M       ; Got a match - get address
         INX   H
         MOV   D,M       ; Get second byte
         JMP   Skp3Ca    ; Go wade through the rest
;
Skp2Ca:  INX   H         ; No match, skip addr
Skp3Ca:  INX   H         ; Skip second byte of addr
         DCR   B         ; Count the entries
         JNZ   Lp1Ca     ; Go through rest of list
         XCHG            ; Swap subr and return
         PUSH  D         ; Store return address
         PCHL            ; Go do it
;
; *************************************************
;
; Menu
;
; This subroutine implements the "MENU" mode, and is
; called if the ESC key is pressed. It displays the
; menu, waits for a selection, and executes the
; request. Then it returns to normal editing mode.
;
Menu:    CALL  ClScr    ; Clear the screen
         CALL  Dspl     ; Display the menu
         DB    CR,128+2
         DB    'Enter one of the following letters:'
         DB    CR,CR,128+5
         DB    'T  Top',128+23
         DB    'P  Print',CR,128+5
         DB    'B  Bottom',128+20
         DB    'F  Find a string',CR,CR,128+5
         DB    'L  Load a new file',128+11
         DB    'S  Save',CR,128+5
         DB    'N  Set file name for I/W/S/X',128+1
         DB    'I  Insert it here',CR,CR,128+5
         DB    'H  Block starts Here',cr,128+5
         DB    'W  Write block to disk',128+7
         DB    'D  Delete block',CR,CR,128+5
         DB    'Q  Abandon Edit',128+14
         DB    'X  Save and Quit',CR,CR,128+2
         DB    'While editing, these CTRL '
         DB    'codes may be used:'
         DB    CR,128+5
         DB    '^G - Delete Char',128+7
         DB    '^Y or ^T - Delete line'
         DB    CR,128+5
         DB    '^C - Page down',128+9
         DB    '^R - Page up'
         DB    CR,128+5
         DB    '^V - Toggle Insert',128+5
         DB    '^F - Repeat find'
         DB    CR,128+5
         DB    '^P - Enter CTRL code',128+3
         DB    '^N - Insert a CR'
         DB    CR,128+5
         DB    '^- - Delete char before cursor'
         DB    CR,0
         CALL  RptKy    ; Get the selection key
         ANI   0DFh     ; Upcase it
         CALL  Case     ; See what he wants
         DB    MnuLen/3 ; Number of cases
         DW    Retrn    ; Ignore if unknown
MnuStrt: EQU   $
         DB    'T'
         DW    Top
         DB    'B'
         DW    Bottom
         DB    'F'
         DW    Find
         DB    'I'
         DW    Load
         DB    'S'
         DW    Save
         DB    'W'
         DW    Write
         DB    'N'
         DW    NewName
         DB    'P'
         DW    Print
         DB    'H'
         DW    Point
         DB    'D'
         DW    EPart
         DB    'Q'
         DW    Quit
         DB    'X'
         DW    Exit
         DB    'L'
         DW    Restrt
MnuLen:  EQU   $-MnuStrt
Retrn:   RET
;
; *************************************************
;
; Restrt
;
; Clear it all out and start over.
;
Restrt:  MVI   A,0         ; Clear current file
         STA   FCBDriv
         MVI   B,FCBExt-FCBFnm
         LXI   H,FCBFnm
ResLp:   MVI   M,' '
         INX   H
         DCR   B
         JNZ   ResLp
         CALL  Top         ; Start at top of file
;
; *************************************************
; 
;  EDIT
;
;  This is the main section of the text editor.
;  Upon entering the editor, the carriage returns
;  needed in the text area are stored there.  The
;  main loop of the program follows, which does the 
;  following:
;     Display the text on the screen
;     Get the next byte from the keyboard
;     Determine which subroutine is to be called
;     Call the appropriate subroutine.
;
EDIT:    CALL  RESET       ; Reset all peripherals
         CALL  StrCR       ; Store CRs
	 LDA   FCBFnm	   ; Get first byte of name
	 CPI   ' '	   ; Is it a blank
         JNZ   EDIT2       ; Yes, no file to load
         CALL  ClScr       ; Clear the screen
         CALL  NewName     ; Get a file name  
EDIT2:   CALL  Load        ; Get input file
Lp1Ed:   XRA   A
         STA   EdErr       ; Clear error indicator
;
;        If the cursor is to the left of HERE, set
;        HERE to appear invalid.
         LHLD  Here
         MOV   C,L
         MOV   B,H
         LHLD  BefCu
         INX   H
         CALL  SubDp         ; Take the difference
         JNC   Skp2Ed        ; Skip if > 0
         CALL  InitH         ; Reinitialize HERE
;
;    Display the text on the screen
Skp2Ed:  CALL  DspTx
;
;    Get the next key from the keyboard
         CALL  RptKy
;
;    Look for a match in the list of control codes
         CALL  CASE
         DB    Tab1L/3  ; Number of table entries
         DW    IChar    ; Default : Insert character
BegTab1: DB    12       ; ->
         DW    Right
         DB    8        ; <-
         DW    Left
         DB    10       ; Down
         DW    Down
         DB    11       ; Up
         DW    Up
         DB    4        ; -> (WordStar)
         DW    Right
         DB    19       ; <- (WordStar)
         DW    Left
         DB    24       ; Down (WordStar)
         DW    Down
         DB    5        ; Up (WordStar)
         DW    Up
         DB    3        ; ^C - Scroll forward
         DW    PageF
         DB    18       ; ^R - Scroll back
         DW    PageB
         DB    Space    ; Space
         DW    ISpac
         DB    Tab      ; Tab
         DW    Itab
         DB    CR       ; CR
         DW    ICR
         DB    6        ; ^F - Repeat find
         DW    Repfind
         DB    14       ; ^N - Insert CR
         DW    ICRA
         DB    7        ; ^G - Delete Char
         DW    EChar
         DB    31       ; ^_ - Delete Prev Char
         DW    Del
         DB    127      ; DEL - Delete Prev Char
         DW    Del
         DB    25       ; ^Y - Delete line
         DW    Eline
         DB    20       ; ^T - Delete line
         DW    Eline 
         DB    22       ; ^V - Toggle insert
         DW    IToggl
         DB    16       ; ^P - Enter control code
         DW    CtlP
         DB    27       ; ESC - HELP
         DW    MENU
Tab1L:   EQU   $-BegTab1

;
;    If no errors occurred, repeat the main loop
Skp1ED:  LDA   EdErr
         ORA   A        ; Check for zero
         JZ    Lp1Ed
;
;    Display the beginning of the error message
         CALL  ClScr
         CALL  Dspl
         DB    CR,CR,CR
         DB    10+128     ; 10 spaces
         DB    'Error:'
         DB    2+128
         DB    0          ; End of text
         LDA   EdErr      ; Get error code back
         ADD   A          ; Double it
         MOV   L,A
         MVI   H,0
         LXI   D,ErrTab
         DAD   D
         MOV   A,M        ; Get low byte of Msg Addr
         MOV   E,A
         INX   H
         MOV   A,M
         MOV   D,A
         XCHG
         CALL  Dspla
         CALL  Dspl
         DB    CR,CR,128+10
         DB    'Waiting ...'
         DB    0
         CALL  RptKy
         JMP   Lp1ED
;
ErrTab   DW    0,MSG1,MSG2,MSG3,MSG4,MSG5
;
MSG1:    DB    'File too big',0
MSG2:    DB    'Wrong key',0
MSG3:    DB    'I/O Failure',0
MSG4:    DB    'String not found',0
MSG5:    DB    'Disk full',0
;
;    From here on is the data area
;
BegTx:   DW    TEXTORG
BefCu:   DW    TEXTORG-1
AftCu:   DW    0
EndTx:   DW    0
Stack:   EQU   $+40
TEXTORG: EQU   $+41
         END
TEXTORG
BefCu:   DW    TEXTORG-1
AftCu